[
  {
    "path": ".all-contributorsrc",
    "content": "{\n  \"projectName\": \"rmx-cli\",\n  \"projectOwner\": \"Kiliman\",\n  \"repoType\": \"github\",\n  \"repoHost\": \"https://github.com\",\n  \"files\": [\n    \"README.md\"\n  ],\n  \"imageSize\": 100,\n  \"commit\": false,\n  \"commitConvention\": \"none\",\n  \"contributors\": [\n    {\n      \"login\": \"kiliman\",\n      \"name\": \"Kiliman\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/47168?v=4\",\n      \"profile\": \"https://kiliman.dev/\",\n      \"contributions\": [\n        \"code\",\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"revelt\",\n      \"name\": \"Roy Revelt\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/8344688?v=4\",\n      \"profile\": \"https://codsen.com/os/\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"kentcdodds\",\n      \"name\": \"Kent C. Dodds\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1500684?v=4\",\n      \"profile\": \"https://kentcdodds.com/\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"kirandash\",\n      \"name\": \"Kiran Dash\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/13310363?v=4\",\n      \"profile\": \"http://bgwebagency.in/\",\n      \"contributions\": [\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"andrewcohen\",\n      \"name\": \"Andrew Cohen\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1016046?v=4\",\n      \"profile\": \"https://github.com/andrewcohen\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"courdek\",\n      \"name\": \"Andrew Coppola\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/319738?v=4\",\n      \"profile\": \"https://github.com/courdek\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"KnisterPeter\",\n      \"name\": \"Markus Wolf\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/327445?v=4\",\n      \"profile\": \"https://about.me/knisterpeter\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"wKovacs64\",\n      \"name\": \"Justin Hall\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/1288694?v=4\",\n      \"profile\": \"https://justinrhall.dev/\",\n      \"contributions\": [\n        \"code\",\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"fweinaug\",\n      \"name\": \"Florian Weinaug\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/17765766?v=4\",\n      \"profile\": \"http://florianweinaug.de/\",\n      \"contributions\": [\n        \"code\",\n        \"bug\"\n      ]\n    }\n  ],\n  \"contributorsPerLine\": 7\n}\n"
  },
  {
    "path": ".gitignore",
    "content": "/node_modules\ndist\nassets"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"semi\": false,\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\",\n  \"arrowParens\": \"avoid\"\n}"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# 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 (#22)\n\n## 🚀 v0.4.14\n\n- 🐛 Fix svg-sprite component generation (#18)\n- 🐛 Fix svg-sprite's sprite option (#20)\n\n## 🚀 v0.4.13\n\n- 🔨 Allow outputFolder to be an absolute path (#15)\n\n## 🚀 v0.4.12\n\n- 🔨 Update CLI to specify sprite and types filenames\n\n## 🚀 v0.4.11\n\n- 🔨 Do not generate files or log to console if files are same as existing\n\n## 🚀 v0.4.10\n\n- 🔨 Export string array of icon names and generate type union from it (#13)\n\n## 🚀 v0.4.9\n\n- 🐛 Fix broken publish\n\n## 🚀 v0.4.8\n\n- 🔨 Export IconName in default svg template #12\n\n## 🚀 v0.4.7\n\n- 🐛 Fix hardcoded sprite import (#11)\n\n## 🚀 v0.4.6\n\n- 🔨 Add --template argument for custom generation\n- 🔨 Strip width and height from SVG\n\n## 🚀 v0.4.5\n\n- 🔨 Update component generation with --components flag for named exports\n\n## 🚀 v0.4.4\n\n- 🔨 Update React sprite component import\n\n## 🚀 v0.4.3\n\n- 🔨 Export components for each icon instead of using sprite id\n\n## 🚀 v0.4.2\n\n- 🔨 Update handling of solid vs outline icons to be automatic\n\n## 🚀 v0.4.1\n\n- 🔨 Add support for using `currentColor` for `stroke` and `fill` icons\n\n## 🚀 v0.4.1\n\n- 🔨 Add support for using `currentColor` for `stroke` and `fill` icons\n\n## 🚀 v0.4.0\n\n- ✨ Add new `svg-sprite` command\n\n## 🚀 v0.3.6\n\n- ✨ Add new `version` command\n- 🐛 Check if command exists before attempting to load\n\n## 🚀 v0.3.5\n\n- 🔨 Check for `module` property as well as `type === module` for ESM packages\n\n## 🚀 v0.3.4\n\n- 🔨 Add @ts-ignore and eslint-disable to generated file to ignore \"errors\"\n\n## 🚀 v0.3.3\n\n- 🔨 Remove timestamp on generated file `gen-remix` due to spurious diffs\n\n## 🚀 v0.3.2\n\n- 🐛 Fix override exports [#5](https://github.com/kiliman/rmx-cli/issues/5)\n- 🐛 Ensure exports for overrides use the correct export type [#6](https://github.com/kiliman/rmx-cli/issues/6)\n\n## 🚀 v0.3.1\n\n- 🐛 Fix argument parsing\n\n## 🚀 v0.3.0\n\n- ✨ Add `gen-remix` command\n\n## 🚀 v0.2.3\n\n- 🔨 Update `rmx-cli` usage\n\n## 🚀 v0.2.2\n\n- 🐛 Update `get-esm-packages` to check for _package.json_ before loading\n\n## 🚀 v0.2.1\n\n- 🐛 Fix commandPath for Windows [#2](https://github.com/kiliman/rmx-cli/issues/2)\n\n## 🚀 v0.2.0\n\n- ✨ Command `get-esm-packages` to scan for ESM package to add to\n  _remix.config.js_ `serverDependenciesToBundle`\n\n## 🚀 v0.1.4\n\n- 🐛 Fix files path for dist folder\n- 🔨 Use realpath to resolve symlink\n- 🐛 Fix backup copy and use timestamp\n\n## 🚀 v0.1.1\n\n- 🐛 Fix shebang\n\n## 🚀 v0.1.0\n\n- 🎉 Intial version\n- ✨ Command to eject a Remix app from Remix App Server to Express\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2022 Michael J. Carter <kiliman@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# rmx-cli\n\n<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->\n\n[![All Contributors](https://img.shields.io/badge/all_contributors-9-orange.svg?style=flat-square)](#contributors-)\n\n<!-- ALL-CONTRIBUTORS-BADGE:END -->\n\nA CLI tool for Remix applications. Future versions will support adding external\ncommands.\n\n## 🛠 Installation\n\n```bash\nnpm install -D rmx-cli\n```\n\n# Commands\n\n## 🎁 svg-sprite ✨ NEW\n\nGenerate SVG sprites recursively from `SOURCE_FOLDER`. It generates the sprite file,\nas well as a React component to create the icon by specifying the fully-typed icon name.\nIt also exports the `href` of the sprite file to use in the Remix `links` export.\n\nThe `OUTPUT_PATH` can be a folder or a filename. If it is a filename, that will be used\nas the base name if there are multiple source folders. For example:\n_components/icons/icon.tsx_ will generate an _icons.tsx_ and _icons.svg_ file for every\nsource folder.\n\nIf you want to generate a React component for _each_ icon, then add the `--components`\nargument. Then you can import the named icon directly.\n\n> NOTE: The React component name will be the filename in TitleCase\n\nYou can specify a custom template file that will be used as the base for the generated\nReact component. The typed `IconNames` and exported components will be be appended to this\ntemplate file. An array of icon names is also exported: `export const iconNames = [\"...\"] as const`\n\nHere's a sample template file:\n\n```ts\nimport { type SVGProps } from 'react'\nimport { cn } from '~/utils/misc'\nimport href from './sprite.svg'\nexport { href }\n\nconst sizeClassName = {\n  font: 'w-font h-font',\n  xs: 'w-3 h-3',\n  sm: 'w-4 h-4',\n  md: 'w-5 h-5',\n  lg: 'w-6 h-6',\n  xl: 'w-7 h-7',\n} as const\n\ntype Size = keyof typeof sizeClassName\n\nexport default function Icon({\n  icon,\n  size = 'font',\n  className,\n  ...props\n}: SVGProps<SVGSVGElement> & { icon: IconName; size?: Size }) {\n  return (\n    <svg\n      {...props}\n      className={cn(sizeClassName[size], 'inline self-center', className)}\n    >\n      <use href={`${href}#${icon}`} />\n    </svg>\n  )\n}\n```\n\n```\nnpx rmx-cli svg-sprite SOURCE_FOLDER OUTPUT_PATH [--components]\n        [--template=TEMPLATE_FILE]\n        [--components-template=TEMPLATE_FILE]\n        [--fill=COLOR] [--stroke=COLOR]\n\nSOURCE_FOLDER: folder containing .svg files\nOUTPUT_PATH: output path for sprite file and components\n\n* If OUTPUT_PATH ends with .tsx, then use this as the base filename\n  (default: icon.tsx)\n\n--sprite=FILENAME: base filename of sprite file (default: icon.svg)\n--types=FILENAME : base filename of IconType export file\n                   if present, will not generate component file\n--components     : generate named components for each icon\n--template=TEMPLATE_FILE: use custom template file\n--fill=COLOR     : specify fill color or \"keep\" to keep original colors\n                   default is \"currentColor\"\n--stroke=COLOR   : specify stroke color or \"keep\" to keep original colors\n                   default is \"currentColor\"\n```\n\n### Usage\n\n_Example:_\n\n```bash\nnpx rmx-cli svg-sprite assets/svg app/components/icons\n```\n\n```ts\n// import default Icon component and specify the icon by name\n// import the href to the sprite file to use in `links` export\nimport {\n  default as RadixIcon,\n  href as radixIcons,\n} from \"~/components/radixicons\";\n\n<RadixIcon icon=\"bookmark\" className=\"text-red-500 h-6 w-6\" />\n<RadixIcon icon=\"envelope-open\" className=\"text-green-500 h-6 w-6\" />\n\n// OR import named icon components (using --components flag)\nimport {\n  ArchiveBoxIcon,\n  ArrowDownIcon,\n  CakeIcon,\n  href as outline24Icons,\n} from \"~/components/heroicons/24/outline\";\n\n// generate <link rel=\"preload\"> for the sprite file\nexport const links: LinksFunction = () => [\n  { rel: \"preload\", href: outline24Icons, as: \"image\" },\n  { rel: \"stylesheet\", href: tailwindCss },\n];\n\n// control color and size using className\n<ArchiveBoxIcon className=\"text-red-500 h-6 w-6\" />\n<ArrowDownIcon className=\"text-green-500 h-6 w-6\" />\n<CakeIcon className=\"text-blue-500 h-6 w-6\" />\n```\n\n<img src=\"./images/svg-sprite.png\" style=\"max-width:400px\">\n\n## 🪂 eject-ras\n\nEject your Remix project from Remix App Server to Express\n\n```bash\nnpx rmx-cli eject-ras\n```\n\n## 📦 get-esm-packages\n\nScan for ESM package to add to _remix.config.js_ `serverDependenciesToBundle`\n\n```bash\nnpx rmx-cli get-esm-packages [package-name ...]\n```\n\n### Usage\n\n```bash\n  Example:\n    npx rmx-cli get-esm-packages @remix-run/node @remix-run/react\n```\n\n## 🏷️ version\n\nList all Remix package versions installed in node_modules\n\n```bash\nnpx rmx-cli version\n```\n\n## 🚀 gen-remix\n\nTHis script will generate a _remix.ts_ file which re-exports all exports\nfrom specified packages. This essentially works like the _magic_ `remix`\npackage from early Remix.\n\nWhy is this useful?\n\n1. Go back to importing from one file instead of adapter specific packages. If you ever switch adapters, just re-generate the _remix.ts_ file.\n2. 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.\n3. Add `\"postinstall\": \"rmx gen-remix\"` to _package.json_ to ensure the file is regenerated when upgrading Remix packages.\n\n### Usage\n\n```bash\nUsage:\n    $ npx rmx gen-remix [options]\n\n  Options:\n    --config PATH       Config path (default: ./gen-remix.config.json)\n    --packages PACKAGES List of packages to export\n    --output PATH       Output path (default: ./app/remix.ts)\n\n  Example:\n    rmx gen-remix --packages @remix-run/node @remix-run/react\n```\n\n### Config\n\nYou can also include an optional config (defaults to _gen-remix.config.json_) where you can specify overrides.\n\n```json\n{\n  \"exports\": [\"packageA\", \"packageB\"],\n  \"overrides\": {\n    \"<source-package>\": [\n      \"<original-package>\": {\n        \"<original-export>\": \"<new-source-export>\",\n        ...\n      },\n      \"<original-package>\": {\n        \"<original-export>\": \"<new-source-export>\",\n        ...\n      }\n    ],\n    ...\n  }\n}\n```\n\n### Example config:\n\nThis config replaces the Remix `json`, `redirect`, `useActionData`, etc. with the versions for [`remix-typedjson`](https://github.com/kiliman/remix-typedjson).\n\n```json\n{\n  \"exports\": [\"@remix-run/node\", \"@remix-run/react\", \"remix-typedjson\"],\n  \"overrides\": {\n    \"remix-typedjson\": {\n      \"@remix-run/node\": {\n        \"json\": \"typedjson\",\n        \"redirect\": \"redirect\"\n      },\n      \"@remix-run/react\": {\n        \"useActionData\": \"useTypedActionData\",\n        \"useFetcher\": \"useTypedFetcher\",\n        \"useLoaderData\": \"useTypedLoaderData\"\n      }\n    }\n  }\n}\n```\n\n## 😍 Contributors\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <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>\n    <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>\n    <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>\n    <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>\n    <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>\n    <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>\n    <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>\n  </tr>\n  <tr>\n    <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>\n    <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>\n  </tr>\n</table>\n\n<!-- markdownlint-restore -->\n<!-- prettier-ignore-end -->\n\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"rmx-cli\",\n  \"version\": \"0.4.16\",\n  \"description\": \"A CLI for remix-run\",\n  \"author\": \"Michael J. Carter <kiliman@gmail.com> (https://kiliman.dev/)\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/cli.js\",\n  \"bin\": {\n    \"rmx\": \"dist/cli.js\"\n  },\n  \"files\": [\n    \"dist/**/*.js\",\n    \"README.md\",\n    \"LICENSE.md\",\n    \"CHANGELOG.md\"\n  ],\n  \"scripts\": {\n    \"clean\": \"rimraf dist\",\n    \"prebuild\": \"npm run clean\",\n    \"build\": \"esbuild --platform=node --format=cjs src/*.ts src/**/*.ts --outdir=dist\",\n    \"contributors:add\": \"all-contributors add\",\n    \"contributors:generate\": \"all-contributors generate\",\n    \"prepublish\": \"npm run build\",\n    \"prestart\": \"npm run build\",\n    \"start\": \"node dist/cli.js\"\n  },\n  \"keywords\": [\n    \"remix\",\n    \"cli\",\n    \"development\",\n    \"scaffolding\",\n    \"generator\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/kiliman/rmx-cli.git\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^17.0.30\",\n    \"@types/node-fetch\": \"^2.5.7\",\n    \"all-contributors-cli\": \"^6.20.0\",\n    \"esbuild\": \"^0.14.38\",\n    \"esbuild-register\": \"^3.3.2\",\n    \"prettier\": \"^2.6.2\",\n    \"rimraf\": \"^3.0.2\",\n    \"typescript\": \"^4.6.4\"\n  },\n  \"dependencies\": {\n    \"@npmcli/package-json\": \"^2.0.0\",\n    \"node-fetch\": \"^2.6.7\"\n  }\n}\n"
  },
  {
    "path": "src/cli.ts",
    "content": "#!/usr/bin/env node\n\nimport * as fs from 'fs'\nimport * as path from 'path'\n\nmain().catch(console.error)\n\nasync function main() {\n  if (process.argv.length < 3) {\n    console.error('Usage: npx rmx-cli <command>')\n    process.exit(1)\n  }\n  const cliPath = path.dirname(fs.realpathSync(process.argv[1]))\n  const commandName = process.argv[2]\n\n  if (!fs.existsSync(path.join(cliPath, 'commands', `${commandName}.js`))) {\n    console.error('Unknown command: ' + commandName)\n    process.exit(1)\n  }\n\n  // add file:// prefix for windows imports\n  const commandPath =\n    'file://' + path.join(cliPath, 'commands', `${commandName}.js`)\n  const command = (await import(commandPath)).default\n  const args = process.argv.slice(3)\n  await command.default(args)\n}\n"
  },
  {
    "path": "src/commands/eject-ras.ts",
    "content": "// @ts-expect-error\nimport PackageJson from '@npmcli/package-json'\nimport * as fs from 'fs'\nimport fetch from 'node-fetch'\nimport * as path from 'path'\n\nexport default async function () {\n  console.log('🚀 Ejecting from Remix App Server...')\n\n  // download package.json from express template\n  await download(\n    'https://raw.githubusercontent.com/remix-run/remix/main/templates/express/package.json',\n    './__eject-ras__/package.json',\n  )\n  const pkgJsonExpress = await PackageJson.load(\n    path.resolve(process.cwd(), './__eject-ras__'),\n  )\n\n  // backup original package.json\n  const ts = new Date().toISOString().substring(0, 19).replace(/[-T:]/g, '')\n  fs.copyFileSync(\n    path.resolve(process.cwd(), './package.json'),\n    path.resolve(process.cwd(), `./package-${ts}.json`),\n  )\n  // get package.json\n  const pkgJson = await PackageJson.load(process.cwd())\n\n  // update dependencies\n  const remixVersion = pkgJson.content.dependencies['@remix-run/node']\n\n  // install missing dependencies\n  const dependencies = getDependencies(pkgJson.content.dependencies)\n  dependencies.uninstall('@remix-run/serve')\n  for (const [name, version] of Object.entries(\n    pkgJsonExpress.content.dependencies,\n  )) {\n    if (!dependencies.content[name]) {\n      dependencies.install(name, version === '*' ? remixVersion : version)\n    }\n  }\n\n  // install missing devDependencies\n  const devDependencies = getDependencies(pkgJson.content.devDependencies)\n  for (const [name, version] of Object.entries(\n    pkgJsonExpress.content.devDependencies,\n  )) {\n    if (!devDependencies.content[name]) {\n      devDependencies.install(name, version === '*' ? remixVersion : version)\n    }\n  }\n\n  // update scripts\n  const scripts = { ...pkgJson.content.scripts }\n  for (const [name, command] of Object.entries(\n    pkgJsonExpress.content.scripts,\n  )) {\n    if (name.startsWith('dev') || name.startsWith('start')) {\n      console.log(`📝 updating script ${name}`)\n      scripts[name] = command\n    }\n  }\n\n  // save package.json\n  pkgJson.update({\n    dependencies: dependencies.content,\n    devDependencies: devDependencies.content,\n    scripts,\n  })\n\n  await pkgJson.save()\n\n  // download express server startup file\n  console.log('📦 downloading express server startup file')\n  await download(\n    'https://raw.githubusercontent.com/remix-run/remix/main/templates/express/server.js',\n    './server.js',\n  )\n  // cleanup files\n  fs.unlinkSync(path.resolve(process.cwd(), './__eject-ras__/package.json'))\n  fs.rmdirSync(path.resolve(process.cwd(), './__eject-ras__'))\n\n  console.log('🏁 Ejecting from Remix App Server... Done!\\n')\n  console.log('🔨 run npm install to update your dependencies')\n}\n\nfunction getDependencies(dependencies: Record<string, string>) {\n  const newDependencies = { ...dependencies }\n  return {\n    uninstall(name: string) {\n      console.log(`🔥 uninstalling ${name}`)\n      delete newDependencies[name]\n    },\n    install(name: string, version: string) {\n      console.log(`📦 installing ${name}@${version}`)\n      newDependencies[name] = version\n    },\n    content: newDependencies,\n  }\n}\n\nasync function download(url: string, filepath: string) {\n  const response = await fetch(url)\n  const file = await response.text()\n  const filedir = path.dirname(path.resolve(process.cwd(), filepath))\n  if (!fs.existsSync(filedir)) {\n    fs.mkdirSync(filedir, { recursive: true })\n  }\n  fs.writeFileSync(filepath, file)\n}\n"
  },
  {
    "path": "src/commands/gen-remix.ts",
    "content": "import fs from 'fs'\nimport path from 'path'\nimport packageJson from '../../package.json'\n\ntype ConfigType = {\n  exports: string[]\n  overrides: OverridesType\n}\n\ntype OverridesType = Record<string, SourcePackageOverrides>\ntype SourcePackageOverrides = Record<string, TargetPackageOverrides>\ntype TargetPackageOverrides = Record<string, string>\n\ntype PackageExports = {\n  version: string\n  values: string[]\n  types: string[]\n}\ntype ExportOverrides = { source: string; target: string; type: ExportType }\ntype ExportType = 'value' | 'type'\n\nfunction usage() {\n  console.log(`\n  Usage:\n    $ gen-remix [options]\n\n  Options:\n    --config PATH       Config path (default: ./gen-remix.config.json)\n    --packages PACKAGES List of packages to export\n    --output PATH       Output path (default: ./app/remix.ts)\n  `)\n}\n\nexport default async function () {\n  let configPath = `gen-remix.config.json`\n  let outputPath = './app/remix.ts'\n  let packages: string[] = []\n  let capture = null\n  for (let i = 2; i < process.argv.length; i++) {\n    const arg = process.argv[i]\n    if (arg.startsWith('--')) {\n      capture = null\n      switch (arg) {\n        case '--config':\n          configPath = process.argv[++i]\n          break\n        case '--output':\n          outputPath = process.argv[++i]\n          break\n        case '--packages':\n          capture = packages\n          break\n        default:\n          usage()\n          break\n      }\n    } else if (capture) {\n      capture.push(arg)\n    }\n  }\n  let config: ConfigType\n  if (!fs.existsSync(configPath)) {\n    if (!packages.length) {\n      usage()\n      return\n    }\n    config = { exports: packages, overrides: {} }\n  } else {\n    config = JSON.parse(fs.readFileSync(configPath, 'utf8'))\n  }\n  console.log('🚀 Generating remix.ts exports...')\n\n  // read package exports\n  const exports: Record<string, PackageExports> = {}\n  for (const packageName of config.exports) {\n    console.log(`📦 ${packageName}`)\n    const packageJson = JSON.parse(\n      fs.readFileSync(`node_modules/${packageName}/package.json`, 'utf8'),\n    )\n    exports[packageName] = {\n      version: packageJson.version,\n      values: [],\n      types: [],\n    }\n    let typings = packageJson.typings\n    if (!typings) {\n      typings = path.dirname(packageJson.main) + '/index.d.ts'\n    }\n    const content = fs.readFileSync(\n      path.join(`node_modules/${packageName}`, typings),\n      'utf8',\n    )\n    const lines = content\n      .replace(/\\n/g, ' ')\n      .replace(/(export\\s+(type\\s*)?{)/g, '\\n$1')\n      .split('\\n')\n      .filter(line => line.trim().length > 0)\n    for (let line of lines) {\n      const match = line.match(/^export(\\s+type)?\\s*{(.*)}/)\n      if (match) {\n        const list = match[1] ? 'types' : 'values'\n        const exportList = match[2]\n          .split(',')\n          .map(s => s.trim())\n          .filter(Boolean)\n        for (let exportName of exportList) {\n          const alias = exportName.match(/^(\\w+)\\s+as\\s+(\\w+)$/)\n          if (alias) {\n            exportName = alias[2]\n          }\n          exports[packageName][list].push(exportName)\n        }\n      }\n    }\n  }\n  // overrides: {\n  //   \"<source-package>\": [\n  //     \"<original-package>\": {\n  //       \"<original-export>\": \"<new-source-export>\",\n  //       ...\n  //     },\n  //     \"<original-package>\": {\n  //       \"<original-export>\": \"<new-source-export>\",\n  //       ...\n  //     }\n  //   ],\n  //   ...\n  // }\"\n\n  let exportOverrides: ExportOverrides[] = []\n  const hasOverrides = config.overrides && Object.keys(config.overrides).length\n\n  if (hasOverrides) {\n    for (const sourcePackage of Object.keys(config.overrides)) {\n      const sourceOverrides = config.overrides[sourcePackage]\n\n      exportOverrides = [\n        ...exportOverrides,\n        ...Object.values(sourceOverrides)\n          .flatMap(overrides => Object.entries(overrides))\n          .map(([target, source]) => ({\n            source,\n            target,\n            type: (exports[sourcePackage].values.includes(source)\n              ? 'value'\n              : 'type') as ExportType,\n          })),\n      ]\n    }\n  }\n\n  // write remix.ts\n  let output = `// @ts-nocheck\n/* eslint-disable */\n// This file was generated by gen-remix.ts v${packageJson.version}\\n`\n  for (const packageName of Object.keys(exports)) {\n    output += `\\n// ${packageName}@${exports[packageName].version}`\n  }\n  if (hasOverrides) {\n    output += `\\n\\n// import overrides`\n    for (const sourcePackage of Object.keys(config.overrides)) {\n      const sourceOverrides = config.overrides[sourcePackage]\n      let imports: string[] = []\n      // import source values\n      for (const originalPackage of Object.keys(sourceOverrides)) {\n        imports = [\n          ...imports,\n          ...Object.values(sourceOverrides[originalPackage]),\n        ]\n      }\n      imports.sort()\n      output += `\\nimport {\\n${imports\n        .map(\n          i =>\n            `  ${exports[sourcePackage].types.includes(i) ? 'type ' : ''}${i},`,\n        )\n        .join('\\n')}\\n} from \"${sourcePackage}\";`\n    }\n  }\n  let allExports: string[] = []\n  output += `\\n\\n// export packages`\n  for (const packageName of Object.keys(exports)) {\n    console.log(`📦 ${packageName}`)\n    const currValues = exports[packageName].values.filter(\n      // eslint-disable-next-line no-loop-func\n      e =>\n        !exportOverrides.some(({ target }) => e === target) &&\n        !allExports.includes(e),\n    )\n    currValues.sort()\n    allExports = [...allExports, ...currValues]\n    output += `\\nexport {\\n${currValues\n      .map(e => `  ${e},`)\n      .join('\\n')}\\n} from \"${packageName}\";`\n\n    if (exports[packageName].types.length) {\n      const currTypes = exports[packageName].types.filter(\n        // eslint-disable-next-line no-loop-func\n        e =>\n          !exportOverrides.some(({ target }) => e === target) &&\n          !allExports.includes(e),\n      )\n      currTypes.sort()\n      allExports = [...allExports, ...currTypes]\n      output += `\\nexport type {\\n${currTypes\n        .map(e => `  ${e},`)\n        .join('\\n')}\\n} from \"${packageName}\";`\n    }\n  }\n  if (hasOverrides) {\n    output += `\\n\\n// export overrides`\n    const overrideValues = exportOverrides.filter(\n      ({ type }) => type === 'value',\n    )\n    if (overrideValues.length) {\n      overrideValues.sort()\n      output += `\\nexport {\\n${overrideValues\n        .map(\n          ({ source, target }) =>\n            `  ${source === target ? source : `${source} as ${target}`},`,\n        )\n        .join('\\n')}\\n};`\n    }\n    const overrideTypes = exportOverrides.filter(({ type }) => type === 'type')\n    if (overrideTypes.length) {\n      overrideTypes.sort()\n      output += `\\nexport type {\\n${overrideTypes\n        .map(\n          ({ source, target }) =>\n            `  ${source === target ? source : `${source} as ${target}`},`,\n        )\n        .join('\\n')}\\n};`\n    }\n  }\n\n  console.log(`📝 Writing ${outputPath}...`)\n  fs.writeFileSync(outputPath, output, 'utf8')\n  console.log('🏁 Done!')\n}\n"
  },
  {
    "path": "src/commands/get-esm-packages.ts",
    "content": "import * as fs from 'node:fs'\n\nexport default function (args: string[]) {\n  let esmPackages: Set<string> = new Set()\n\n  function getDependencies(packageName: string) {\n    const packageJsonFilename = `./node_modules/${packageName}/package.json`\n    if (!fs.existsSync(packageJsonFilename)) {\n      console.log(`⚠️ ${packageName} package.json not found`)\n      return\n    }\n    const json = fs.readFileSync(packageJsonFilename, 'utf8')\n    const packageJson = JSON.parse(json)\n\n    if (packageJson.type === 'module' || packageJson.module) {\n      if (esmPackages.has(packageName)) return\n      console.log(`📦 ${packageName}`)\n      esmPackages.add(packageName)\n      const dependencies = packageJson.dependencies || {}\n      Object.keys(dependencies).forEach(dependency => {\n        getDependencies(dependency)\n      })\n    }\n  }\n\n  args.forEach(packageName => {\n    getDependencies(packageName)\n  })\n  console.log(\n    '\\n🔨 Add the following dependencies to your serverDependenciesToBundle\\n',\n  )\n  console.log(\n    Array.from(esmPackages.values())\n      .sort()\n      .map((packageName: string) => `\"${packageName}\"`)\n      .join(',\\n'),\n  )\n}\n"
  },
  {
    "path": "src/commands/svg-sprite.ts",
    "content": "import * as fs from 'node:fs'\nimport * as path from 'node:path'\n// @ts-expect-error\nimport svgParser from '../libs/svg-parser'\n\nlet rootFolder = ''\nlet outputFolder = ''\nlet template: string | null = null\nlet namedComponents = false\nlet componentsTemplate: string | null = null\nlet component = 'icon.tsx'\nlet sprite = 'icon.svg'\nlet types = ''\nlet fillColor = 'currentColor'\nlet strokeColor = 'currentColor'\n\nexport default function (args: string[]) {\n  // verify arguments\n  if (args.length >= 2) {\n    rootFolder = normalizeFolder(args[0])\n    let outputPath = args[1]\n    // if output folder has extension, then treat this as the base name\n    if (outputPath.endsWith('.tsx')) {\n      outputFolder = path.dirname(outputPath)\n      component = path.basename(outputPath)\n    } else {\n      outputFolder = normalizeFolder(outputPath)\n    }\n    for (let i = 2; i < args.length; i++) {\n      if (args[i].startsWith('--template=')) {\n        const templateFilename = args[i].substring('--template='.length)\n        template = fs.readFileSync(templateFilename, 'utf8')\n      } else if (args[i].startsWith('--component=')) {\n        sprite = args[i].substring('--component='.length)\n      } else if (args[i].startsWith('--sprite=')) {\n        sprite = args[i].substring('--sprite='.length)\n      } else if (args[i].startsWith('--types=')) {\n        types = args[i].substring('--types='.length)\n      } else if (args[i] === '--components') {\n        namedComponents = true\n      } else if (args[i].startsWith('--components-template=')) {\n        const templateFilename = args[i].substring(\n          '--components-template='.length,\n        )\n        componentsTemplate = fs.readFileSync(templateFilename, 'utf8')\n      } else if (args[i].startsWith('--fill=')) {\n        fillColor = args[i].substring('--fill='.length)\n      } else if (args[i].startsWith('--stroke=')) {\n        strokeColor = args[i].substring('--stroke='.length)\n      }\n    }\n  } else {\n    console.log(\n      `\nUsage: npx rmx-cli svg-sprite SOURCE_FOLDER OUTPUT_PATH\n               [--sprite=FILENAME] [--types=FILENAME]\n               [--components] [--template=TEMPLATE_FILE]\n               [--fill=COLOR] [--stroke=COLOR]\n\nSOURCE_FOLDER: folder containing .svg files\nOUTPUT_PATH: output path for sprite file and components\n\n* If OUTPUT_PATH ends with .tsx, then use this as the base filename\n  (default: icon.tsx)\n\n--sprite=FILENAME: base filename of sprite file (default: icon.svg)\n--types=FILENAME : base filename of IconType export file\n                   if present, will not generate component file\n--components     : generate named components for each icon\n--template=TEMPLATE_FILE: use custom template file\n--fill=COLOR     : specify fill color or \"keep\" to keep original colors\n                   default is \"currentColor\"\n--stroke=COLOR   : specify stroke color or \"keep\" to keep original colors\n                   default is \"currentColor\"\n`.trimStart(),\n    )\n    process.exit(1)\n  }\n\n  // generate sprites for any .svg files in the root folder recursively\n  generateSprites(rootFolder)\n}\n\n// queue up console.log calls so we can print them after files are \"generated\"\nconst logs: any[] = []\nfunction log(...args: any[]) {\n  logs.push(args)\n}\nfunction flushLogs() {\n  if (logs.length === 0) return\n  logs.forEach(args => console.log(...args))\n  logs.length = 0\n}\n\nfunction normalizeFolder(folder: string) {\n  let fullPath = path.resolve(folder)\n  // remove cwd from path and leading slash\n  const cwd = ensureSlash(process.cwd())\n  let normalized = fullPath\n  // check if normalized starts with cwd (both have to end with /, otherwise\n  // the check might be wrong, e.g. /tmp/test is cwd and /tmp/test2 is fullPath)\n  if (normalized.startsWith(cwd)) {\n    normalized = normalized.replace(cwd, '')\n  }\n  return normalized\n}\n\nfunction ensureSlash(input: string): string {\n  if (!input.endsWith(path.sep)) {\n    input = input + path.sep\n  }\n  return input\n}\n\nfunction generateSprites(folder: string) {\n  // any .svg files in this folder?\n  const svgFiles = fs\n    .readdirSync(folder)\n    .filter(file => file.endsWith('.svg'))\n    .map(file => path.join(folder, file))\n  if (svgFiles.length > 0) {\n    generateSprite(folder, svgFiles)\n  }\n\n  // recurse through folders looking for .svg files\n  const folders = fs\n    .readdirSync(folder, { withFileTypes: true })\n    .filter(dirent => dirent.isDirectory())\n    .map(dirent => path.join(folder, dirent.name))\n\n  folders.forEach(folder => {\n    generateSprites(folder)\n  })\n}\n\nfunction generateSprite(folder: string, files: string[]) {\n  // folder is something like: assets/svg/heroicons/20/solid\n  // spriteOutputFolder: components/svg/heroicons/20/solid\n\n  const spriteOutputFolder = folder.replace(rootFolder, outputFolder)\n  const spriteOutput = path.join(spriteOutputFolder, path.basename(sprite))\n\n  log(`📝 Generating sprite for ${folder}`)\n\n  // create output folder if it doesn't exist\n  if (!fs.existsSync(spriteOutputFolder)) {\n    fs.mkdirSync(spriteOutputFolder, { recursive: true })\n  }\n  const strip = true\n  const trim = false\n\n  let { svgElement } = svgParser.iterateFiles(\n    files,\n    strip,\n    trim,\n    fillColor,\n    strokeColor,\n  )\n\n  svgElement = svgParser.wrapInSvgTag(svgElement)\n\n  const exists = fs.existsSync(spriteOutput)\n  let hasChanges = !exists // if sprite doesn't exist, then we have changes\n  if (exists) {\n    // read existing sprite file\n    const existingSprite = fs.readFileSync(spriteOutput, 'utf8')\n    if (existingSprite !== svgElement) {\n      hasChanges = true\n    }\n  }\n  if (hasChanges) {\n    // write sprite file\n    fs.writeFileSync(spriteOutput, svgElement, 'utf8')\n    flushLogs()\n  }\n\n  if (types) {\n    generateTypesFile(spriteOutputFolder, files)\n    return\n  }\n\n  generateReactComponent(spriteOutputFolder, files)\n}\n\nfunction generateTypesFile(spriteOutputFolder: string, files: string[]) {\n  let icons = files.map(file => path.basename(file, '.svg'))\n  let output = `export const iconNames = [\n${icons.map(icon => `  \"${icon}\",`).join('\\n')}\n] as const;\n\nexport type IconName = typeof iconNames[number];\n`\n  icons.forEach(icon => log(`✅ ${icon}`))\n\n  let typesPath = path.join(spriteOutputFolder, types)\n  const exists = fs.existsSync(typesPath)\n  let hasChanges = !exists // if types doesn't exist, then we have changes\n  if (exists) {\n    // read existing types file\n    const existing = fs.readFileSync(typesPath, 'utf8')\n    if (existing !== output) {\n      hasChanges = true\n    }\n  }\n  if (hasChanges) {\n    fs.writeFileSync(typesPath, output, 'utf8')\n    flushLogs()\n  }\n}\nfunction generateReactComponent(spriteOutputFolder: string, files: string[]) {\n  let icons = files.map(file => path.basename(file, '.svg'))\n  const spritePath = path.basename(component, '.tsx') + '.svg'\n  let output =\n    template ??\n    `\nimport { type SVGProps } from \"react\";\nimport href from \"./${spritePath}\";\nexport { href };\n\nexport default function Icon({ icon, ...props}: SVGProps<SVGSVGElement> & { icon: IconName }) {\n  return (\n    <svg {...props}>\n      <use href={\\`\\${href}#\\${icon}\\`} />\n    </svg>\n  );\n}\n`\n  // if user didn't specify types file then inline them\n  if (!types) {\n    // add type IconName for each icon file\n    output += `\nexport const iconNames = [\n${icons.map(icon => `  \"${icon}\",`).join('\\n')}\n] as const;\nexport type IconName = typeof iconNames[number];`\n  }\n  icons.forEach(icon => log(`✅ ${icon}`))\n\n  // if user wants named components, generate them\n  if (namedComponents) {\n    output += '\\n'\n    icons.forEach(icon => {\n      // convert kebab case to title case\n      const componentName = icon.replace(/(^|-|_)([a-z0-9])/g, g =>\n        g!.at(-1)!.toUpperCase(),\n      )\n      output += '\\n'\n      output +=\n        componentsTemplate\n          ?.replace(/{{icon}}/g, icon)\n          .replace(/{{componentName}}/g, componentName) ??\n        `export const ${componentName}Icon = (props: SVGProps<SVGSVGElement>) => <Icon icon=\"${icon}\" {...props} />;`\n    })\n  }\n  const componentPath = path.join(spriteOutputFolder, component)\n  output = output.trimStart()\n\n  const exists = fs.existsSync(componentPath)\n  let hasChanges = !exists // if sprite doesn't exist, then we have changes\n  if (exists) {\n    // read existing sprite file\n    const existing = fs.readFileSync(componentPath, 'utf8')\n    if (existing !== output) {\n      hasChanges = true\n    }\n  }\n  if (hasChanges) {\n    // write sprite file\n    fs.writeFileSync(componentPath, output, 'utf8')\n    flushLogs()\n  }\n}\n"
  },
  {
    "path": "src/commands/version.ts",
    "content": "import * as fs from 'node:fs'\n\nexport default function (args: string[]) {\n  // read package.json\n  const packageJsonFilename = `./package.json`\n  const packageJson = fs.readFileSync(packageJsonFilename, 'utf8')\n  const packageJsonParsed = JSON.parse(packageJson)\n\n  // get all dependencies\n  const allDependencies = [\n    ...Object.keys(packageJsonParsed.dependencies || {}),\n    ...Object.keys(packageJsonParsed.devDependencies || {}),\n  ]\n  // filter @remix-run/* and react-router* packages\n  const re = /^(remix|@remix-run\\/\\w+|react-router(-dom)?)$/\n  const filteredDependencies = allDependencies.filter(packageName =>\n    re.test(packageName),\n  )\n\n  filteredDependencies.forEach(packageName => {\n    const version = getVersion(packageName)\n    if (version) {\n      console.log(`📦 ${packageName} version: ${version}`)\n    }\n  })\n\n  function getVersion(packageName: string) {\n    const packageJsonFilename = `./node_modules/${packageName}/package.json`\n    if (!fs.existsSync(packageJsonFilename)) {\n      console.log(`⚠️ ${packageName} package.json not found`)\n      return\n    }\n    const json = fs.readFileSync(packageJsonFilename, 'utf8')\n    const packageJson = JSON.parse(json)\n\n    return packageJson.version\n  }\n}\n"
  },
  {
    "path": "src/libs/svg-parser.ts",
    "content": "// @ts-nocheck\n// https://github.com/jannicz/svg-icon-sprite/blob/master/scripts/svg-parser.lib.js\n\nconst fs = require('fs')\nconst path = require('path')\n\n/**\n * Library for generating SVG sprites (using symbol technique)\n *\n * @author Jan Suwart\n * @licence MIT\n */\nconst svgParserLib = {\n  fgRed: '\\x1b[31m',\n  fgGreen: '\\x1b[32m',\n  fgYellow: '\\x1b[33m',\n  fgReset: '\\x1b[0m',\n\n  /**\n   * @param {string} file - full path of current file\n   * @param {string} name - name of current file\n   * @return {string} the created symbol element as string\n   */\n  createSymbol: (file, name) => {\n    if (!file || !name) {\n      throw new Error('No file found at ' + name)\n    } else if (!file.includes('<svg')) {\n      throw new Error('No SVG node found in ' + name)\n    }\n\n    let symbolEl = file\n      .replace(/<\\?xml.*?\\?>/, '')\n      .replace(/ id=\".*?\"/, '')\n      .replace(/ version=\".*?\"/, '')\n      .replace(/ xmlns=\".*?\"/, '')\n      .replace(/ xmlns:xlink=\".*?\"/, '')\n      .replace('<svg', `<symbol id=\"${name}\"`)\n      .replace('</svg>', '</symbol>\\n')\n\n    return symbolEl\n  },\n\n  /**\n   * Removes fill and stroke attributes while preserving fill=\"none\" to allow hollow elements\n   * @param {string} svg - input SVG as string\n   * @param {string} fillColor - new fill color (keep to preserve existing color)\n   * @param {string} strokeColor - new stroke color (keep to preserve existing color)\n   * @return {string} stripped SVG as string\n   */\n  stripProperties: (svg, fillColor, strokeColor) => {\n    const widthHeight = /\\s+(width|height)=\".*?\"/gim\n\n    svg = svg.replace(widthHeight, '')\n    if (fillColor !== 'keep') {\n      // replace existing fill/stroke attributes with the new color\n      const fill = /\\s+(fill)=\"((?!(none|currentColor)).*?)\"/gim\n      svg = svg.replace(fill, ` $1=\"${fillColor}\"`)\n    }\n    if (strokeColor !== 'keep') {\n      // replace existing fill/stroke attributes with the new color\n      const stroke = /\\s+(stroke)=\"((?!(none|currentColor)).*?)\"/gim\n      svg = svg.replace(stroke, ` $1=\"${strokeColor}\"`)\n    }\n\n    return svg\n  },\n\n  makeSolid: svg => {\n    return svg.replace(/fill=\"none\"/g, 'fill=\"currentColor\"')\n  },\n\n  /**\n   * Removes all whitespaces to reduce file size\n   */\n  removeWhitespaces: svg => {\n    return svg\n      .replace(/\\n/g, '')\n      .replace(/[\\t ]+\\</g, '<')\n      .replace(/\\>[\\t ]+\\</g, '><')\n      .replace(/\\>[\\t ]+$/g, '>')\n  },\n\n  /**\n   * Wraps the passed elements string into a SVG structure\n   * @param {string} elements - concatenated symbols\n   * @return {string}\n   */\n  wrapInSvgTag: elements => {\n    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`\n  },\n\n  /**\n   * Iterates the file array, retrieves each file and applies lib functions\n   * @param {{name: string, path: string}[]} files - List containing file references and names\n   * @param {boolean} [strip] - whether to remove fill/stroke attributes\n   * @param {boolean} [trim] - whether to remove all whitespace\n   * @param {string} fillColor - new fill color (keep to preserve existing color)\n   * @param {string} strokeColor - new stroke color (keep to preserve existing color)\n   * @return {{elementsChanged: number, svgElement: string}} final SVG sprite as string\n   */\n  iterateFiles: (files, strip, trim, fillColor, strokeColor) => {\n    let svgElement = ''\n    let elementsChanged = 0\n\n    files.forEach(file => {\n      let name = path.basename(file, '.svg')\n      try {\n        let svg = fs.readFileSync(file, 'utf8')\n        let symbolEl = svgParserLib.createSymbol(svg, name)\n\n        if (strip) {\n          symbolEl = svgParserLib.stripProperties(\n            symbolEl,\n            fillColor,\n            strokeColor,\n          )\n        }\n\n        svgElement += symbolEl\n        elementsChanged++\n      } catch (e) {\n        console.warn(\n          svgParserLib.fgRed + 'Could not parse',\n          name,\n          'because of error:' + svgParserLib.fgReset,\n          e.message,\n          '- file skipped!',\n        )\n      }\n    })\n\n    if (trim) {\n      svgElement = svgParserLib.removeWhitespaces(svgElement)\n    }\n\n    return { svgElement, elementsChanged }\n  },\n\n  readFile: fileObj => {\n    return fs.readFileSync(fileObj.path, 'utf8')\n  },\n\n  /**\n   * Writes the string into a folder\n   * @param {string} fullFileName - folder and filename\n   * @param {string} outputString - the output that should be written\n   * @return {Promise} holding the write error if failed, true otherwise\n   */\n  writeIconsToFile: (fullFileName, outputString) => {\n    fs.writeFileSync(fullFileName, outputString, 'utf8')\n  },\n\n  /**\n   * Reads the directory and returns array of files that match filetype (i.e. .svg)\n   * @param {string} dirname - full directory name relative to the working directory of the script\n   * @param {string} filetype - substring that the file should be tested for, i.e. '.svg'\n   * @return {Promise} containing the file list as array\n   */\n  readDirectory: (dirname, filetype) => {\n    return new Promise((resolve, reject) => {\n      let fileList = []\n\n      fs.readdir(dirname, (error, files) => {\n        if (error) {\n          reject(error)\n        } else {\n          files.forEach(file => {\n            if (file.includes(filetype)) {\n              fileList.push(file)\n            }\n          })\n\n          resolve(fileList)\n        }\n      })\n    })\n  },\n}\n\nmodule.exports = svgParserLib\n"
  },
  {
    "path": "test/package-copy.json",
    "content": "{\n  \"name\": \"remix-template-remix\",\n  \"private\": true,\n  \"description\": \"\",\n  \"license\": \"\",\n  \"sideEffects\": false,\n  \"scripts\": {\n    \"build\": \"remix build\",\n    \"dev\": \"remix dev\",\n    \"start\": \"remix-serve build\"\n  },\n  \"dependencies\": {\n    \"@remix-run/node\": \"^1.4.1\",\n    \"@remix-run/react\": \"^1.4.1\",\n    \"@remix-run/serve\": \"^1.4.1\",\n    \"react\": \"^17.0.2\",\n    \"react-dom\": \"^17.0.2\"\n  },\n  \"devDependencies\": {\n    \"@remix-run/dev\": \"^1.4.1\",\n    \"@remix-run/eslint-config\": \"^1.4.1\",\n    \"@types/react\": \"^17.0.24\",\n    \"@types/react-dom\": \"^17.0.9\",\n    \"eslint\": \"^8.11.0\",\n    \"typescript\": \"^4.5.5\"\n  },\n  \"engines\": {\n    \"node\": \">=14\"\n  }\n}\n"
  },
  {
    "path": "test/package.json",
    "content": "{\n  \"name\": \"remix-template-remix\",\n  \"private\": true,\n  \"description\": \"\",\n  \"license\": \"\",\n  \"sideEffects\": false,\n  \"scripts\": {\n    \"build\": \"remix build\",\n    \"dev\": \"remix build && run-p dev:*\",\n    \"start\": \"cross-env NODE_ENV=production node ./server.js\",\n    \"dev:node\": \"cross-env NODE_ENV=development nodemon ./server.js --watch ./server.js\",\n    \"dev:remix\": \"remix watch\"\n  },\n  \"dependencies\": {\n    \"@remix-run/express\": \"^1.4.1\",\n    \"@remix-run/node\": \"^1.4.1\",\n    \"@remix-run/react\": \"^1.4.1\",\n    \"compression\": \"^1.7.4\",\n    \"cross-env\": \"^7.0.3\",\n    \"express\": \"^4.17.1\",\n    \"morgan\": \"^1.10.0\",\n    \"react\": \"^17.0.2\",\n    \"react-dom\": \"^17.0.2\"\n  },\n  \"devDependencies\": {\n    \"@remix-run/dev\": \"^1.4.1\",\n    \"@remix-run/eslint-config\": \"^1.4.1\",\n    \"@types/react\": \"^17.0.24\",\n    \"@types/react-dom\": \"^17.0.9\",\n    \"eslint\": \"^8.11.0\",\n    \"nodemon\": \"^2.0.15\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"typescript\": \"^4.5.5\"\n  },\n  \"engines\": {\n    \"node\": \">=14\"\n  }\n}\n"
  },
  {
    "path": "test/server.js",
    "content": "const path = require(\"path\");\nconst express = require(\"express\");\nconst compression = require(\"compression\");\nconst morgan = require(\"morgan\");\nconst { createRequestHandler } = require(\"@remix-run/express\");\n\nconst BUILD_DIR = path.join(process.cwd(), \"build\");\n\nconst app = express();\n\napp.use(compression());\n\n// http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header\napp.disable(\"x-powered-by\");\n\n// Remix fingerprints its assets so we can cache forever.\napp.use(\n  \"/build\",\n  express.static(\"public/build\", { immutable: true, maxAge: \"1y\" })\n);\n\n// Everything else (like favicon.ico) is cached for an hour. You may want to be\n// more aggressive with this caching.\napp.use(express.static(\"public\", { maxAge: \"1h\" }));\n\napp.use(morgan(\"tiny\"));\n\napp.all(\n  \"*\",\n  process.env.NODE_ENV === \"development\"\n    ? (req, res, next) => {\n        purgeRequireCache();\n\n        return createRequestHandler({\n          build: require(BUILD_DIR),\n          mode: process.env.NODE_ENV,\n        })(req, res, next);\n      }\n    : createRequestHandler({\n        build: require(BUILD_DIR),\n        mode: process.env.NODE_ENV,\n      })\n);\nconst port = process.env.PORT || 3000;\n\napp.listen(port, () => {\n  console.log(`Express server listening on port ${port}`);\n});\n\nfunction purgeRequireCache() {\n  // purge require cache on requests for \"server side HMR\" this won't let\n  // you have in-memory objects between requests in development,\n  // alternatively you can set up nodemon/pm2-dev to restart the server on\n  // file changes, but then you'll have to reconnect to databases/etc on each\n  // change. We prefer the DX of this, so we've included it for you by default\n  for (let key in require.cache) {\n    if (key.startsWith(BUILD_DIR)) {\n      delete require.cache[key];\n    }\n  }\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ES2019\"],\n    \"esModuleInterop\": true,\n    \"moduleResolution\": \"Node\",\n    \"target\": \"ES2019\",\n    \"module\": \"ES2020\",\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"skipLibCheck\": true,\n    \"declaration\": true,\n    \"jsx\": \"react-jsx\",\n    \"importHelpers\": true,\n    \"resolveJsonModule\": true\n  },\n  \"exclude\": [\"node_modules\"],\n  \"include\": [\"src/**/*.ts\", \"src/**/*.tsx\", \"test/**/*.ts\"]\n}\n"
  }
]