[
  {
    "path": ".dockerignore",
    "content": ".cache\nbuild\nnode_modules\n"
  },
  {
    "path": ".editorconfig",
    "content": "# editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_size = 2\nindent_style = space\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n# Markdown syntax specifies that trailing whitespaces can be meaningful,\n# so let’s not trim those. e.g. 2 trailing spaces = linebreak (<br />)\n# See https://daringfireball.net/projects/markdown/syntax#p\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".eslintignore",
    "content": "node_modules\nbuild\npublic/build\nshopify-app-remix\n*/*.yml\n.shopify\n"
  },
  {
    "path": ".eslintrc.cjs",
    "content": "/** @type {import('@types/eslint').Linter.BaseConfig} */\nmodule.exports = {\n  root: true,\n  extends: [\n    \"@remix-run/eslint-config\",\n    \"@remix-run/eslint-config/node\",\n    \"@remix-run/eslint-config/jest-testing-library\",\n    \"prettier\",\n  ],\n  globals: {\n    shopify: \"readonly\"\n  },\n};\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "* @shop/dev_experience\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, gender identity and expression, level of experience,\nnationality, personal appearance, race, religion, or sexual identity and\norientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n- Using welcoming and inclusive language\n- Being respectful of differing viewpoints and experiences\n- Gracefully accepting constructive criticism\n- Focusing on what is best for the community\n- Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n- The use of sexualized language or imagery and unwelcome sexual attention or\n  advances\n- Trolling, insulting/derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or electronic\n  address, without explicit permission\n- Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at opensource@shopify.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct/\n\n[homepage]: https://www.contributor-covenant.org\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# How to contribute\n\nThe Shopify Remix app template is an open source project. We want to make it as easy and transparent as possible to contribute. If we are missing anything or can make the process easier in any way, please let us know by [opening an issue](https://github.com/Shopify/shopify-app-template-remix/issues/new).\n\n## Code of conduct\n\nWe expect all participants to read our [code of conduct](https://github.com/Shopify/shopify-app-template-remix/.github/CODE_OF_CONDUCT.md) to understand which actions are and aren’t tolerated.\n\n## Open development\n\nAll work on the Shopify Remix app template happens directly on GitHub. Both team members and external contributors send pull requests which go through the same review process.\n\n## Bugs\n\n### Where to find known issues\n\nWe track all of our issues in GitHub and [bugs](https://github.com/Shopify/shopify-app-template-remix/labels/Bug) are labeled accordingly. If you are planning to work on an issue, avoid ones which already have an assignee, where someone has commented within the last two weeks they are working on it, or the issue is labeled with [fix in progress](https://github.com/Shopify/shopify-app-template-remix/labels/fix%20in%20progress). We will do our best to communicate when an issue is being worked on internally.\n\n### Reporting new issues\n\nTo reduce duplicates, look through open issues before filing one. When [opening an issue](https://github.com/Shopify/shopify-app-template-remix/issues/new?template=ISSUE.md), complete as much of the template as possible.\n\n## Your first pull request\n\nWorking on your first pull request? You can learn how from this free video series:\n\n[How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github)\n\nTo help you get familiar with our contribution process, we have a list of [good first issues](https://github.com/Shopify/shopify-app-template-remix/labels/good%20first%20issue) that contain bugs with limited scope. This is a great place to get started.\n\nIf you decide to fix an issue, please check the comment thread in case somebody is already working on a fix. If nobody is working on it, leave a comment stating that you intend to work on it.\n\nIf somebody claims an issue but doesn’t follow up for more than two weeks, it’s fine to take it over but still leave a comment stating that you intend to work on it.\n\n### Sending a pull request\n\nWe’ll review your pull request and either merge it, request changes to it, or close it with an explanation. We’ll do our best to provide updates and feedback throughout the process.\n\n### Contributor License Agreement (CLA)\n\nEach contributor is required to [sign a CLA](https://cla.shopify.com/). This process is automated as part of your first pull request and is only required once. If any contributor has not signed or does not have an associated GitHub account, the CLA check will fail and the pull request is unable to be merged.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "---\nname: '🐛 Bug Report'\nabout: Something isn't working\nlabels: 'Type: Bug 🐛'\n---\n\n# Issue summary\n\nBefore opening this issue, I have:\n\n- [ ] Upgraded to the latest version of the `@shopify` packages\n  - Affected `@shopify/shopify-*` package and version:\n  - Node version:\n  - Operating system:\n- [ ] Set `{ logger: { level: LogSeverity.Debug } }` in my configuration\n- [ ] Found a reliable way to reproduce the problem that indicates it's a problem with the package\n- [ ] Looked for similar issues in this repository\n- [ ] Checked that this isn't an issue with a Shopify API\n  - If it is, please create a post in the [Shopify community forums](https://community.shopify.com/c/partners-and-developers/ct-p/appdev) or report it to [Shopify Partner Support](https://help.shopify.com/en/support/partners/org-select)\n\n<!--\nWrite a short description of the issue here.\n\nWe can only fix issues for which there is a clear reproduction scenario.\nThe more context you can provide, the easier it becomes for us to investigate and fix the issue.\n-->\n\n## Expected behavior\n\nWhat do you think should happen?\n\n## Actual behavior\n\nWhat actually happens?\n\n## Steps to reproduce the problem\n\n1.\n1.\n1.\n\n## Debug logs\n\n```\n// Paste any relevant logs here\n```\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--\n  ☝️How to write a good PR title:\n  - Prefix it with [Feature] (if applicable)\n  - Start with a verb, for example: Add, Delete, Improve, Fix…\n  - Give as much context as necessary and as little as possible\n  - Prefix it with [WIP] while it’s a work in progress\n-->\n\n### WHY are these changes introduced?\n\nFixes #0000 <!-- link to issue if one exists -->\n\n<!--\n  Context about the problem that’s being addressed.\n-->\n\n### WHAT is this pull request doing?\n\n<!--\n  Summary of the changes committed.\n  Before / after screenshots appreciated for UI changes.\n-->\n\n### Test this PR\n\n```bash\nshopify app init --template=https://github.com/Shopify/shopify-app-template-remix#<your-branch-name>\n```\n\n### Checklist\n\n- [ ] I have made changes to the `README.md` file and other related documentation, if applicable\n- [ ] I have added an entry to `CHANGELOG.md`\n- [ ] I'm aware I need to create a new release when this PR is merged\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: github-actions\n    directory: \"/\"\n    schedule:\n      interval: weekly\n  # Enable version updates for npm\n  - package-ecosystem: 'npm'\n    # Look for `package.json` and `lock` files in the `root` directory\n    directory: '/'\n    # Check the npm registry for updates every day (weekdays)\n    schedule:\n      interval: 'weekly'\n    # Dependabot defaults to 5 open pull requests at a time\n    open-pull-requests-limit: 100\n\n    # Cooldown is the number of days after a release to wait until opening a PR\n    # This gives us more confidence changes can be merged because changes have been community tested.\n    # See: https://github.blog/changelog/2025-07-01-dependabot-supports-configuration-of-a-minimum-package-age/\n    cooldown:\n      default-days: 14\n      semver-major-days: 30\n      semver-minor-days: 14\n      semver-patch-days: 14\n\n    groups:\n\n      # Group together PRs of dependant packages\n      prisma:\n        patterns:\n          - 'prisma'\n          - '@prisma/client'\n      react:\n        patterns:\n          - 'react'\n          - 'react-dom'\n          - '@types/react'\n          - '@types/react-dom'\n      vite:\n        patterns:\n          - 'vite'\n          - 'vite-tsconfig-paths'\n      remix:\n        patterns:\n          - '@remix-run/dev'\n          - '@remix-run/fs-routes'\n          - '@remix-run/node'\n          - '@remix-run/react'\n          - '@remix-run/eslint-config'\n          - '@remix-run/route-config'\n\n      # Group all patch updates not accounted for in prior groups in a single PR.\n      # This reduces the number of PRs to review and rebase.\n      patch-updates:\n        patterns:\n          - \"*\"\n        update-types:\n          - \"patch\"\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "on: [push, pull_request]\n\nname: CI\n\njobs:\n  CI:\n    name: CI_Node_${{ matrix.version }}\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        version: [20.19.0, 22, 24]\n    steps:\n      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n      - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0\n        with:\n          node-version: ${{ matrix.version }}\n      - name: Install\n        run: yarn install\n"
  },
  {
    "path": ".github/workflows/cla.yml",
    "content": "name: Contributor License Agreement (CLA)\n\non:\n  pull_request_target:\n    types: [opened, synchronize]\n  issue_comment:\n    types: [created]\n\njobs:\n  cla:\n    runs-on: ubuntu-latest\n    if: |\n      (github.event.issue.pull_request\n        && !github.event.issue.pull_request.merged_at\n        && contains(github.event.comment.body, 'signed')\n      )\n      || (github.event.pull_request && !github.event.pull_request.merged)\n    steps:\n      - uses: Shopify/shopify-cla-action@v1\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          cla-token: ${{ secrets.CLA_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/close-waiting-for-response-issues.yml",
    "content": "name: Close Waiting for Response Issues\non:\n  schedule:\n    - cron: \"30 1 * * *\"\n  workflow_dispatch:\njobs:\n  check-need-info:\n    runs-on: ubuntu-latest\n    steps:\n      - name: close-issues\n        uses: actions-cool/issues-helper@45d75b6cf72bf4f254be6230cb887ad002702491 # v3.6.3\n        with:\n          actions: \"close-issues\"\n          token: ${{ secrets.GITHUB_TOKEN }}\n          labels: \"Waiting for Response\"\n          inactive-day: 14\n          body: |\n            We are closing this issue because we did not hear back regarding additional details we needed to resolve this issue. If the issue persists and you are able to provide the missing clarification we need, you can respond here or create a new issue.\n\n            We appreciate your understanding as we try to manage our number of open issues.\n"
  },
  {
    "path": ".github/workflows/convert-to-js.yml",
    "content": "name: Create Javascript conversion PR\n\non:\n  push:\n    branches:\n      - main\n\n  workflow_dispatch:\n\njobs:\n  convert-ts-files:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout Repo\n        uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0\n\n      - name: Create lock file\n        run: touch yarn.lock\n\n      - name: Setup Node.js\n        uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0\n        with:\n          node-version: 22.12.x\n          cache: 'yarn'\n\n      - name: Install dependencies\n        run: yarn add -W --dev @shopify/eslint-plugin --ignore-engines\n\n      - name: Create temporary tsconfig file\n        run: |\n          echo '{\n            \"include\": [\"./app/**/*\", \"*.ts\", \"*.tsx\", \".graphqlrc.ts\"],\n            \"compilerOptions\": {\n              \"strict\": true,\n              \"removeComments\": false,\n              \"skipLibCheck\": true,\n              \"isolatedModules\": true,\n              \"noEmitOnError\": true,\n              \"jsx\": \"preserve\",\n              \"module\": \"ES2022\",\n              \"moduleResolution\": \"bundler\",\n              \"target\": \"ES2022\",\n              \"paths\": {\n                \"~/*\": [\"./app/*\"]\n              }\n            }\n          }' > tsconfig.js.json\n\n      - name: Transpile to Javascript\n        run: yarn tsc -p tsconfig.js.json\n\n      - name: Remove Typescript files\n        run: |\n          find app \\( -name \"*.ts\" -o -name \"*.tsx\" \\) -delete\n          find . \\( -name \".graphqlrc.ts\" -o -name \"tsconfig.js.json\" -o -name \"vite.config.ts\" \\) -delete\n\n      - name: Run prettier\n        run: yarn prettier -w \"app/**/*.{js,jsx}\" \".graphqlrc.js\" \"vite.config.js\"\n\n      - name: Run ESLint\n        run: |\n          yarn lint \"app/**/*.{js,jsx}\" \".graphqlrc.js\" \"vite.config.js\" --fix --no-cache --ignore-pattern \"\\!.graphqlrc.js\" --plugin @shopify/eslint-plugin --rule '{\n            \"import/order\": \"error\",\n            \"import/newline-after-import\": \"error\",\n            \"padding-line-between-statements\": [\"error\",\n              { \"blankLine\": \"always\", \"prev\": [\"const\", \"let\", \"var\"], \"next\": \"*\"},\n              { \"blankLine\": \"any\",    \"prev\": [\"const\", \"let\", \"var\"], \"next\": [\"const\", \"let\", \"var\"]}\n              { \"blankLine\": \"always\", \"prev\": \"*\", \"next\": \"return\" },\n              { \"blankLine\": \"always\", \"prev\": \"*\", \"next\": \"export\" },\n              { \"blankLine\": \"never\",  \"prev\": \"export\", \"next\": \"export\" },\n              { \"blankLine\": \"always\", \"prev\": \"*\", \"next\": \"block-like\" },\n              { \"blankLine\": \"always\", \"prev\": \"block-like\", \"next\": \"*\" }\n            ]}'\n\n      - name: Prepare files for git\n        run: |\n          git config user.name GitHub\n          git config user.email noreply@github.com\n          git fetch\n          git restore --staged package.json\n          git restore package.json\n\n      - name: Stage changes to files\n        run: |\n          git add .\n          git checkout -b temp_javascript_updates\n          git commit -m \"Convert template to Javascript\"\n          git checkout javascript\n          git pull\n          git checkout -\n          git rebase -m -X theirs javascript\n          git push -f origin temp_javascript_updates:javascript_updates\n\n      - name: Create Javascript PR\n        run: |\n          gh pr view --json mergedAt -q \".mergedAt\" javascript_updates | grep -E \"^$\" || \\\n          gh pr create -B javascript -H javascript_updates --title 'Convert template to Javascript' --body 'This is an automated PR that converts the latest changes from Typescript to Javascript'\n        env:\n          GH_TOKEN: ${{ github.token }}\n"
  },
  {
    "path": ".github/workflows/remove-labels-on-activity.yml",
    "content": "name: Remove Waiting Labels\non:\n  issue_comment:\n    types: [created]\n  workflow_dispatch:\njobs:\n  remove-labels-on-activity:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0\n      - uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0 # v1.2.0\n        if: contains(github.event.issue.labels.*.name, 'Waiting for Response')\n        with:\n          labels: |\n            Waiting for Response\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\n.DS_Store\n\n/.cache\n/build\n/app/build\n/public/build/\n/public/_dev\n/app/public/build\n/prisma/dev.sqlite\n/prisma/dev.sqlite-journal\ndatabase.sqlite\n\n.env\n.env.*\n\npackage-lock.json\nyarn.lock\npnpm-lock.yaml\n\n/extensions/*/dist\n\n# Ignore shopify files created during app dev\n.shopify/*\n.shopify.lock\n"
  },
  {
    "path": ".graphqlrc.ts",
    "content": "import fs from \"fs\";\nimport { ApiVersion } from \"@shopify/shopify-api\";\nimport { shopifyApiProject, ApiType } from \"@shopify/api-codegen-preset\";\nimport type { IGraphQLConfig } from \"graphql-config\";\n\nfunction getConfig() {\n  const config: IGraphQLConfig = {\n    projects: {\n      default: shopifyApiProject({\n        apiType: ApiType.Admin,\n        apiVersion: ApiVersion.July25,\n        documents: [\n          \"./app/**/*.{js,ts,jsx,tsx}\",\n          \"./app/.server/**/*.{js,ts,jsx,tsx}\",\n        ],\n        outputDir: \"./app/types\",\n      }),\n    },\n  };\n\n  let extensions: string[] = [];\n  try {\n    extensions = fs.readdirSync(\"./extensions\");\n  } catch {\n    // ignore if no extensions\n  }\n\n  for (const entry of extensions) {\n    const extensionPath = `./extensions/${entry}`;\n    const schema = `${extensionPath}/schema.graphql`;\n    if (!fs.existsSync(schema)) {\n      continue;\n    }\n    config.projects[entry] = {\n      schema,\n      documents: [`${extensionPath}/**/*.graphql`],\n    };\n  }\n\n  return config;\n}\n\nconst config = getConfig();\n\nexport default config;\n"
  },
  {
    "path": ".npmrc",
    "content": "engine-strict=true\n@shopify:registry=https://registry.npmjs.org\n"
  },
  {
    "path": ".prettierignore",
    "content": "package.json\n.shadowenv.d\n.vscode\nnode_modules\nprisma\npublic\n.shopify\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    \"graphql.vscode-graphql\",\n    \"shopify.polaris-for-vscode\",\n  ]\n}\n"
  },
  {
    "path": ".vscode/mcp.json",
    "content": "{\n    \"servers\": {\n        \"shopify-dev-mcp\": {\n            \"command\": \"npx\",\n            \"args\": [\"-y\", \"@shopify/dev-mcp@latest\"]\n        }\n    }\n}"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# @shopify/shopify-app-template-remix\n\n## 2025.12.11\n\n- [#1201](https://github.com/Shopify/shopify-app-template-remix/pull/1201) Update `@shopify/shopify-app-remix` to v4.1.0 and `@shopify/shopify-app-session-storage-prisma` to v8.0.0, add refresh token fields (`refreshToken` and `refreshTokenExpires`) to Session model in Prisma schema, and adopt the `expiringOfflineAccessTokens` flag for enhanced security through token rotation. See [expiring vs non-expiring offline tokens](https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens/offline-access-tokens#expiring-vs-non-expiring-offline-tokens) for more information.\n\n## 2025.10.01\n\n**Remix is now React Router.** As of [React Router v7](https://remix.run/blog/merging-remix-and-react-router), Remix and React Router have merged.\n\nFor new projects, use the **[Shopify App Template - React Router](https://github.com/Shopify/shopify-app-template-react-router)** instead.\n\nTo migrate your existing Remix app, follow the **[migration guide](https://github.com/Shopify/shopify-app-template-react-router/wiki/Upgrading-from-Remix)**.\n\n## 2025.08.16\n- [#52](https://github.com/Shopify/shopify-app-template-remix/pull/1153) Use `ApiVersion.July25` rather than `LATEST_API_VERSION` in `.graphqlrc`.\n\n## 2025.07.07\n- [#1103](https://github.com/Shopify/shopify-app-template-remix/pull/1086) Remove deprecated .npmrc config values\n\n## 2025.06.12\n- [#1075](https://github.com/Shopify/shopify-app-template-remix/pull/1075) Add Shopify MCP to [VSCode configs](https://code.visualstudio.com/docs/copilot/chat/mcp-servers#_enable-mcp-support-in-vs-code)\n\n## 2025.06.12\n-[#1082](https://github.com/Shopify/shopify-app-template-remix/pull/1082) Remove local Shopify CLI from the template. Developers should use the Shopify CLI [installed globally](https://shopify.dev/docs/api/shopify-cli#installation).\n## 2025.03.18\n-[#998](https://github.com/Shopify/shopify-app-template-remix/pull/998) Update to Vite 6\n\n## 2025.03.01\n- [#982](https://github.com/Shopify/shopify-app-template-remix/pull/982) Add Shopify Dev Assistant extension to the VSCode extension recommendations\n\n## 2025.01.31\n- [#952](https://github.com/Shopify/shopify-app-template-remix/pull/952) Update to Shopify App API v2025-01\n\n## 2025.01.23\n\n- [#923](https://github.com/Shopify/shopify-app-template-remix/pull/923) Update `@shopify/shopify-app-session-storage-prisma` to v6.0.0\n\n## 2025.01.8\n\n- [#923](https://github.com/Shopify/shopify-app-template-remix/pull/923) Enable GraphQL autocomplete for Javascript\n\n## 2024.12.19\n\n- [#904](https://github.com/Shopify/shopify-app-template-remix/pull/904) bump `@shopify/app-bridge-react` to latest\n-\n## 2024.12.18\n\n- [875](https://github.com/Shopify/shopify-app-template-remix/pull/875) Add Scopes Update Webhook\n## 2024.12.05\n\n- [#910](https://github.com/Shopify/shopify-app-template-remix/pull/910) Install `openssl` in Docker image to fix Prisma (see [#25817](https://github.com/prisma/prisma/issues/25817#issuecomment-2538544254))\n- [#907](https://github.com/Shopify/shopify-app-template-remix/pull/907) Move `@remix-run/fs-routes` to `dependencies` to fix Docker image build\n- [#899](https://github.com/Shopify/shopify-app-template-remix/pull/899) Disable v3_singleFetch flag\n- [#898](https://github.com/Shopify/shopify-app-template-remix/pull/898) Enable the `removeRest` future flag so new apps aren't tempted to use the REST Admin API.\n\n## 2024.12.04\n\n- [#891](https://github.com/Shopify/shopify-app-template-remix/pull/891) Enable remix future flags.\n\n## 2024.11.26\n- [888](https://github.com/Shopify/shopify-app-template-remix/pull/888) Update restResources version to 2024-10\n\n## 2024.11.06\n\n- [881](https://github.com/Shopify/shopify-app-template-remix/pull/881) Update to the productCreate mutation to use the new ProductCreateInput type\n\n## 2024.10.29\n\n- [876](https://github.com/Shopify/shopify-app-template-remix/pull/876) Update shopify-app-remix to v3.4.0 and shopify-app-session-storage-prisma to v5.1.5\n\n## 2024.10.02\n\n- [863](https://github.com/Shopify/shopify-app-template-remix/pull/863) Update to Shopify App API v2024-10 and shopify-app-remix v3.3.2\n\n## 2024.09.18\n\n- [850](https://github.com/Shopify/shopify-app-template-remix/pull/850) Removed \"~\" import alias\n\n## 2024.09.17\n\n- [842](https://github.com/Shopify/shopify-app-template-remix/pull/842) Move webhook processing to individual routes\n\n## 2024.08.19\n\nReplaced deprecated `productVariantUpdate` with `productVariantsBulkUpdate`\n\n## v2024.08.06\n\nAllow `SHOP_REDACT` webhook to process without admin context\n\n## v2024.07.16\n\nStarted tracking changes and releases using calver\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM node:18-alpine\nRUN apk add --no-cache openssl\n\nEXPOSE 3000\n\nWORKDIR /app\n\nENV NODE_ENV=production\n\nCOPY package.json package-lock.json* ./\n\nRUN npm ci --omit=dev && npm cache clean --force\n# Remove CLI packages since we don't need them in production by default.\n# Remove this line if you want to run CLI commands in your container.\nRUN npm remove @shopify/cli\n\nCOPY . .\n\nRUN npm run build\n\nCMD [\"npm\", \"run\", \"docker-start\"]\n"
  },
  {
    "path": "README.md",
    "content": "# Shopify App Template - Remix\n\n> [!NOTE]\n> **Remix is now React Router.** As of [React Router v7](https://remix.run/blog/merging-remix-and-react-router), Remix and React Router have merged.\n> \n> For new projects, use the **[Shopify App Template - React Router](https://github.com/Shopify/shopify-app-template-react-router)** instead.\n> \n> To migrate your existing Remix app, follow the **[migration guide](https://github.com/Shopify/shopify-app-template-react-router/wiki/Upgrading-from-Remix)**.\n\nThis is a template for building a [Shopify app](https://shopify.dev/docs/apps/getting-started) using the [Remix](https://remix.run) framework.\n\nRather than cloning this repo, you can use your preferred package manager and the Shopify CLI with [these steps](https://shopify.dev/docs/apps/getting-started/create).\n\nVisit the [`shopify.dev` documentation](https://shopify.dev/docs/api/shopify-app-remix) for more details on the Remix app package.\n\n## Quick start\n\n### Prerequisites\n\nBefore you begin, you'll need the following:\n\n1. **Node.js**: [Download and install](https://nodejs.org/en/download/) it if you haven't already.\n2. **Shopify Partner Account**: [Create an account](https://partners.shopify.com/signup) if you don't have one.\n3. **Test Store**: Set up either a [development store](https://help.shopify.com/en/partners/dashboard/development-stores#create-a-development-store) or a [Shopify Plus sandbox store](https://help.shopify.com/en/partners/dashboard/managing-stores/plus-sandbox-store) for testing your app.\n4. **Shopify CLI**: [Download and install](https://shopify.dev/docs/apps/tools/cli/getting-started) it if you haven't already.\n```shell\nnpm install -g @shopify/cli@latest\n```\n\n### Setup\n\n```shell\nshopify app init --template=https://github.com/Shopify/shopify-app-template-remix\n```\n\n### Local Development\n\n```shell\nshopify app dev\n```\n\n\n\nLocal development is powered by [the Shopify CLI](https://shopify.dev/docs/apps/tools/cli). It logs into your partners account, connects to an app, provides environment variables, updates remote config, creates a tunnel and provides commands to generate extensions.\n\n### Authenticating and querying data\n\nTo authenticate and query data you can use the `shopify` const that is exported from `/app/shopify.server.js`:\n\n```js\nexport async function loader({ request }) {\n  const { admin } = await shopify.authenticate.admin(request);\n\n  const response = await admin.graphql(`\n    {\n      products(first: 25) {\n        nodes {\n          title\n          description\n        }\n      }\n    }`);\n\n  const {\n    data: {\n      products: { nodes },\n    },\n  } = await response.json();\n\n  return nodes;\n}\n```\n\nThis template comes preconfigured with examples of:\n\n1. Setting up your Shopify app in [/app/shopify.server.ts](https://github.com/Shopify/shopify-app-template-remix/blob/main/app/shopify.server.ts)\n2. Querying data using Graphql. Please see: [/app/routes/app.\\_index.tsx](https://github.com/Shopify/shopify-app-template-remix/blob/main/app/routes/app._index.tsx).\n3. Responding to webhooks in individual files such as [/app/routes/webhooks.app.uninstalled.tsx](https://github.com/Shopify/shopify-app-template-remix/blob/main/app/routes/webhooks.app.uninstalled.tsx) and [/app/routes/webhooks.app.scopes_update.tsx](https://github.com/Shopify/shopify-app-template-remix/blob/main/app/routes/webhooks.app.scopes_update.tsx)\n\nPlease read the [documentation for @shopify/shopify-app-remix](https://www.npmjs.com/package/@shopify/shopify-app-remix#authenticating-admin-requests) to understand what other API's are available.\n\n## Deployment\n\n### Application Storage\n\nThis template uses [Prisma](https://www.prisma.io/) to store session data, by default using an [SQLite](https://www.sqlite.org/index.html) database.\nThe database is defined as a Prisma schema in `prisma/schema.prisma`.\n\nThis use of SQLite works in production if your app runs as a single instance.\nThe database that works best for you depends on the data your app needs and how it is queried.\nYou can run your database of choice on a server yourself or host it with a SaaS company.\nHere's a short list of databases providers that provide a free tier to get started:\n\n| Database   | Type             | Hosters                                                                                                                                                                                                                               |\n| ---------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| MySQL      | SQL              | [Digital Ocean](https://www.digitalocean.com/products/managed-databases-mysql), [Planet Scale](https://planetscale.com/), [Amazon Aurora](https://aws.amazon.com/rds/aurora/), [Google Cloud SQL](https://cloud.google.com/sql/docs/mysql) |\n| PostgreSQL | SQL              | [Digital Ocean](https://www.digitalocean.com/products/managed-databases-postgresql), [Amazon Aurora](https://aws.amazon.com/rds/aurora/), [Google Cloud SQL](https://cloud.google.com/sql/docs/postgres)                                   |\n| Redis      | Key-value        | [Digital Ocean](https://www.digitalocean.com/products/managed-databases-redis), [Amazon MemoryDB](https://aws.amazon.com/memorydb/)                                                                                                        |\n| MongoDB    | NoSQL / Document | [Digital Ocean](https://www.digitalocean.com/products/managed-databases-mongodb), [MongoDB Atlas](https://www.mongodb.com/atlas/database)                                                                                                  |\n\nTo use one of these, you can use a different [datasource provider](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#datasource) in your `schema.prisma` file, or a different [SessionStorage adapter package](https://github.com/Shopify/shopify-api-js/blob/main/packages/shopify-api/docs/guides/session-storage.md).\n\n### Build\n\nRemix handles building the app for you, by running the command below with the package manager of your choice:\n\nUsing yarn:\n\n```shell\nyarn build\n```\n\nUsing npm:\n\n```shell\nnpm run build\n```\n\nUsing pnpm:\n\n```shell\npnpm run build\n```\n\n## Hosting\n\nWhen you're ready to set up your app in production, you can follow [our deployment documentation](https://shopify.dev/docs/apps/deployment/web) to host your app on a cloud provider like [Heroku](https://www.heroku.com/) or [Fly.io](https://fly.io/).\n\nWhen you reach the step for [setting up environment variables](https://shopify.dev/docs/apps/deployment/web#set-env-vars), you also need to set the variable `NODE_ENV=production`.\n\n### Hosting on Vercel\n\nUsing the Vercel Preset is recommended when hosting your Shopify Remix app on Vercel. You'll also want to ensure imports that would normally come from `@remix-run/node` are imported from `@vercel/remix` instead. Learn more about hosting Remix apps on Vercel [here](https://vercel.com/docs/frameworks/remix).\n\n```diff\n// vite.config.ts\nimport { vitePlugin as remix } from \"@remix-run/dev\";\nimport { defineConfig, type UserConfig } from \"vite\";\nimport tsconfigPaths from \"vite-tsconfig-paths\";\n+ import { vercelPreset } from '@vercel/remix/vite';\n\ninstallGlobals();\n\nexport default defineConfig({\n  plugins: [\n    remix({\n      ignoredRouteFiles: [\"**/.*\"],\n+     presets: [vercelPreset()],\n    }),\n    tsconfigPaths(),\n  ],\n});\n```\n\n## Troubleshooting\n\n### Database tables don't exist\n\nIf you get this error:\n\n```\nThe table `main.Session` does not exist in the current database.\n```\n\nYou need to create the database for Prisma. Run the `setup` script in `package.json` using your preferred package manager.\n\n### Navigating/redirecting breaks an embedded app\n\nEmbedded Shopify apps must maintain the user session, which can be tricky inside an iFrame. To avoid issues:\n\n1. Use `Link` from `@remix-run/react` or `@shopify/polaris`. Do not use `<a>`.\n2. Use the `redirect` helper returned from `authenticate.admin`. Do not use `redirect` from `@remix-run/node`\n3. Use `useSubmit` or `<Form/>` from `@remix-run/react`. Do not use a lowercase `<form/>`.\n\nThis only applies if your app is embedded, which it will be by default.\n\n### Non Embedded\n\nShopify apps are best when they are embedded in the Shopify Admin, which is how this template is configured. If you have a reason to not embed your app please make the following changes:\n\n1. Ensure `embedded = false` is set in [shopify.app.toml`](./shopify.app.toml). [Docs here](https://shopify.dev/docs/apps/build/cli-for-apps/app-configuration#global).\n2. Pass `isEmbeddedApp: false` to `shopifyApp()` in `./app/shopify.server.js|ts`.\n3. Change the `isEmbeddedApp` prop to `isEmbeddedApp={false}` for the `AppProvider` in `/app/routes/app.jsx|tsx`.\n4. Remove the `@shopify/app-bridge-react` dependency from [package.json](./package.json) and `vite.config.ts|js`.\n5. Remove anything imported from `@shopify/app-bridge-react`.  For example: `NavMenu`, `TitleBar` and `useAppBridge`.\n\n### OAuth goes into a loop when I change my app's scopes\n\nIf you change your app's scopes and authentication goes into a loop and fails with a message from Shopify that it tried too many times, you might have forgotten to update your scopes with Shopify.\nTo do that, you can run the `deploy` CLI command.\n\nUsing yarn:\n\n```shell\nyarn deploy\n```\n\nUsing npm:\n\n```shell\nnpm run deploy\n```\n\nUsing pnpm:\n\n```shell\npnpm run deploy\n```\n\n### My shop-specific webhook subscriptions aren't updated\n\nIf you are registering webhooks in the `afterAuth` hook, using `shopify.registerWebhooks`, you may find that your subscriptions aren't being updated.  \n\nInstead of using the `afterAuth` hook, the recommended approach is to declare app-specific webhooks in the `shopify.app.toml` file.  This approach is easier since Shopify will automatically update changes to webhook subscriptions every time you run `deploy` (e.g: `npm run deploy`).  Please read these guides to understand more:\n\n1. [app-specific vs shop-specific webhooks](https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-subscriptions)\n2. [Create a subscription tutorial](https://shopify.dev/docs/apps/build/webhooks/subscribe/get-started?framework=remix&deliveryMethod=https)\n\nIf you do need shop-specific webhooks, please keep in mind that the package calls `afterAuth` in 2 scenarios:\n\n- After installing the app\n- When an access token expires\n\nDuring normal development, the app won't need to re-authenticate most of the time, so shop-specific subscriptions aren't updated. To force your app to update the subscriptions, you can uninstall and reinstall it in your development store. That will force the OAuth process and call the `afterAuth` hook.\n\n### Admin created webhook failing HMAC validation\n\nWebhooks subscriptions created in the [Shopify admin](https://help.shopify.com/en/manual/orders/notifications/webhooks) will fail HMAC validation. This is because the webhook payload is not signed with your app's secret key.  There are 2 solutions:\n\n1. Use [app-specific webhooks](https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-subscriptions) defined in your toml file instead (recommended)\n2. Create [webhook subscriptions](https://shopify.dev/docs/api/shopify-app-remix/v1/guide-webhooks) using the `shopifyApp` object.\n\nTest your webhooks with the [Shopify CLI](https://shopify.dev/docs/apps/tools/cli/commands#webhook-trigger) or by triggering events manually in the Shopify admin(e.g. Updating the product title to trigger a `PRODUCTS_UPDATE`).\n\n### Incorrect GraphQL Hints\n\nBy default the [graphql.vscode-graphql](https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql) extension for VS Code will assume that GraphQL queries or mutations are for the [Shopify Admin API](https://shopify.dev/docs/api/admin). This is a sensible default, but it may not be true if:\n\n1. You use another Shopify API such as the storefront API.\n2. You use a third party GraphQL API.\n\nin this situation, please update the [.graphqlrc.ts](https://github.com/Shopify/shopify-app-template-remix/blob/main/.graphqlrc.ts) config.\n\n### First parameter has member 'readable' that is not a ReadableStream.\n\nSee [hosting on Vercel](#hosting-on-vercel).\n\n### Admin object undefined on webhook events triggered by the CLI\n\nWhen you trigger a webhook event using the Shopify CLI, the `admin` object will be `undefined`. This is because the CLI triggers an event with a valid, but non-existent, shop. The `admin` object is only available when the webhook is triggered by a shop that has installed the app.\n\nWebhooks triggered by the CLI are intended for initial experimentation testing of your webhook configuration. For more information on how to test your webhooks, see the [Shopify CLI documentation](https://shopify.dev/docs/apps/tools/cli/commands#webhook-trigger).\n\n### Using Defer & await for streaming responses\n\nTo test [streaming using defer/await](https://remix.run/docs/en/main/guides/streaming) during local development you'll need to use the Shopify CLI slightly differently:\n\n1. First setup ngrok: https://ngrok.com/product/secure-tunnels\n2. Create an ngrok tunnel on port 8080: `ngrok http 8080`.\n3. Copy the forwarding address. This should be something like: `https://f355-2607-fea8-bb5c-8700-7972-d2b5-3f2b-94ab.ngrok-free.app`\n4. In a separate terminal run `yarn shopify app dev --tunnel-url=TUNNEL_URL:8080` replacing `TUNNEL_URL` for the address you copied in step 3.\n\nBy default the CLI uses a cloudflare tunnel. Unfortunately it cloudflare tunnels wait for the Response stream to finish, then sends one chunk.\n\nThis will not affect production, since tunnels are only for local development.\n\n### Using MongoDB and Prisma\n\nBy default this template uses SQLlite as the database. It is recommended to move to a persisted database for production. If you choose to use MongoDB, you will need to make some modifications to the schema and prisma configuration. For more information please see the [Prisma MongoDB documentation](https://www.prisma.io/docs/orm/overview/databases/mongodb).\n\nAlternatively you can use a MongDB database directly with the [MongoDB session storage adapter](https://github.com/Shopify/shopify-app-js/tree/main/packages/apps/session-storage/shopify-app-session-storage-mongodb).\n\n#### Mapping the id field\n\nIn MongoDB, an ID must be a single field that defines an @id attribute and a @map(\"\\_id\") attribute.\nThe prisma adapter expects the ID field to be the ID of the session, and not the \\_id field of the document.\n\nTo make this work you can add a new field to the schema that maps the \\_id field to the id field. For more information see the [Prisma documentation](https://www.prisma.io/docs/orm/prisma-schema/data-model/models#defining-an-id-field)\n\n```prisma\nmodel Session {\n  session_id  String    @id @default(auto()) @map(\"_id\") @db.ObjectId\n  id          String    @unique\n...\n}\n```\n\n#### Error: The \"mongodb\" provider is not supported with this command\n\nMongoDB does not support the [prisma migrate](https://www.prisma.io/docs/orm/prisma-migrate/understanding-prisma-migrate/overview) command. Instead, you can use the [prisma db push](https://www.prisma.io/docs/orm/reference/prisma-cli-reference#db-push) command and update the `shopify.web.toml` file with the following commands. If you are using MongoDB please see the [Prisma documentation](https://www.prisma.io/docs/orm/overview/databases/mongodb) for more information.\n\n```toml\n[commands]\npredev = \"npx prisma generate && npx prisma db push\"\ndev = \"npm exec remix vite:dev\"\n```\n\n#### Prisma needs to perform transactions, which requires your mongodb server to be run as a replica set\n\nSee the [Prisma documentation](https://www.prisma.io/docs/getting-started/setup-prisma/start-from-scratch/mongodb/connect-your-database-node-mongodb) for connecting to a MongoDB database.\n\n### I want to use Polaris v13.0.0 or higher\n\nCurrently, this template is set up to work on node v18.20 or higher. However, `@shopify/polaris` is limited to v12 because v13 can only run on node v20+.\n\nYou don't have to make any changes to the code in order to be able to upgrade Polaris to v13, but you'll need to do the following:\n\n- Upgrade your node version to v20.10 or higher.\n- Update your `Dockerfile` to pull `FROM node:20-alpine` instead of `node:18-alpine`\n\n### \"nbf\" claim timestamp check failed\n\nThis error will occur of the `nbf` claim timestamp check failed. This is because the JWT token is expired.\nIf you  are consistently getting this error, it could be that the clock on your machine is not in sync with the server.\n\nTo fix this ensure you have enabled `Set time and date automatically` in the `Date and Time` settings on your computer.\n\n## Benefits\n\nShopify apps are built on a variety of Shopify tools to create a great merchant experience.\n\n<!-- TODO: Uncomment this after we've updated the docs -->\n<!-- The [create an app](https://shopify.dev/docs/apps/getting-started/create) tutorial in our developer documentation will guide you through creating a Shopify app using this template. -->\n\nThe Remix app template comes with the following out-of-the-box functionality:\n\n- [OAuth](https://github.com/Shopify/shopify-app-js/tree/main/packages/shopify-app-remix#authenticating-admin-requests): Installing the app and granting permissions\n- [GraphQL Admin API](https://github.com/Shopify/shopify-app-js/tree/main/packages/shopify-app-remix#using-the-shopify-admin-graphql-api): Querying or mutating Shopify admin data\n- [Webhooks](https://github.com/Shopify/shopify-app-js/tree/main/packages/shopify-app-remix#authenticating-webhook-requests): Callbacks sent by Shopify when certain events occur\n- [AppBridge](https://shopify.dev/docs/api/app-bridge): This template uses the next generation of the Shopify App Bridge library which works in unison with previous versions.\n- [Polaris](https://polaris.shopify.com/): Design system that enables apps to create Shopify-like experiences\n\n## Tech Stack\n\nThis template uses [Remix](https://remix.run). The following Shopify tools are also included to ease app development:\n\n- [Shopify App Remix](https://shopify.dev/docs/api/shopify-app-remix) provides authentication and methods for interacting with Shopify APIs.\n- [Shopify App Bridge](https://shopify.dev/docs/apps/tools/app-bridge) allows your app to seamlessly integrate your app within Shopify's Admin.\n- [Polaris React](https://polaris.shopify.com/) is a powerful design system and component library that helps developers build high quality, consistent experiences for Shopify merchants.\n- [Webhooks](https://github.com/Shopify/shopify-app-js/tree/main/packages/shopify-app-remix#authenticating-webhook-requests): Callbacks sent by Shopify when certain events occur\n- [Polaris](https://polaris.shopify.com/): Design system that enables apps to create Shopify-like experiences\n\n## Resources\n\n- [Remix Docs](https://remix.run/docs/en/v1)\n- [Shopify App Remix](https://shopify.dev/docs/api/shopify-app-remix)\n- [Introduction to Shopify apps](https://shopify.dev/docs/apps/getting-started)\n- [App authentication](https://shopify.dev/docs/apps/auth)\n- [Shopify CLI](https://shopify.dev/docs/apps/tools/cli)\n- [App extensions](https://shopify.dev/docs/apps/app-extensions/list)\n- [Shopify Functions](https://shopify.dev/docs/api/functions)\n- [Getting started with internationalizing your app](https://shopify.dev/docs/apps/best-practices/internationalization/getting-started)\n"
  },
  {
    "path": "app/db.server.ts",
    "content": "import { PrismaClient } from \"@prisma/client\";\n\ndeclare global {\n  var prismaGlobal: PrismaClient;\n}\n\nif (process.env.NODE_ENV !== \"production\") {\n  if (!global.prismaGlobal) {\n    global.prismaGlobal = new PrismaClient();\n  }\n}\n\nconst prisma = global.prismaGlobal ?? new PrismaClient();\n\nexport default prisma;\n"
  },
  {
    "path": "app/entry.server.tsx",
    "content": "import { PassThrough } from \"stream\";\nimport { renderToPipeableStream } from \"react-dom/server\";\nimport { RemixServer } from \"@remix-run/react\";\nimport {\n  createReadableStreamFromReadable,\n  type EntryContext,\n} from \"@remix-run/node\";\nimport { isbot } from \"isbot\";\nimport { addDocumentResponseHeaders } from \"./shopify.server\";\n\nexport const streamTimeout = 5000;\n\nexport default async function handleRequest(\n  request: Request,\n  responseStatusCode: number,\n  responseHeaders: Headers,\n  remixContext: EntryContext\n) {\n  addDocumentResponseHeaders(request, responseHeaders);\n  const userAgent = request.headers.get(\"user-agent\");\n  const callbackName = isbot(userAgent ?? '')\n    ? \"onAllReady\"\n    : \"onShellReady\";\n\n  return new Promise((resolve, reject) => {\n    const { pipe, abort } = renderToPipeableStream(\n      <RemixServer\n        context={remixContext}\n        url={request.url}\n      />,\n      {\n        [callbackName]: () => {\n          const body = new PassThrough();\n          const stream = createReadableStreamFromReadable(body);\n\n          responseHeaders.set(\"Content-Type\", \"text/html\");\n          resolve(\n            new Response(stream, {\n              headers: responseHeaders,\n              status: responseStatusCode,\n            })\n          );\n          pipe(body);\n        },\n        onShellError(error) {\n          reject(error);\n        },\n        onError(error) {\n          responseStatusCode = 500;\n          console.error(error);\n        },\n      }\n    );\n\n    // Automatically timeout the React renderer after 6 seconds, which ensures\n    // React has enough time to flush down the rejected boundary contents\n    setTimeout(abort, streamTimeout + 1000);\n  });\n}\n"
  },
  {
    "path": "app/globals.d.ts",
    "content": "declare module \"*.css\";\n"
  },
  {
    "path": "app/root.tsx",
    "content": "import {\n  Links,\n  Meta,\n  Outlet,\n  Scripts,\n  ScrollRestoration,\n} from \"@remix-run/react\";\n\nexport default function App() {\n  return (\n    <html>\n      <head>\n        <meta charSet=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n        <link rel=\"preconnect\" href=\"https://cdn.shopify.com/\" />\n        <link\n          rel=\"stylesheet\"\n          href=\"https://cdn.shopify.com/static/fonts/inter/v4/styles.css\"\n        />\n        <Meta />\n        <Links />\n      </head>\n      <body>\n        <Outlet />\n        <ScrollRestoration />\n        <Scripts />\n      </body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "app/routes/_index/route.tsx",
    "content": "import type { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { redirect } from \"@remix-run/node\";\nimport { Form, useLoaderData } from \"@remix-run/react\";\n\nimport { login } from \"../../shopify.server\";\n\nimport styles from \"./styles.module.css\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n  const url = new URL(request.url);\n\n  if (url.searchParams.get(\"shop\")) {\n    throw redirect(`/app?${url.searchParams.toString()}`);\n  }\n\n  return { showForm: Boolean(login) };\n};\n\nexport default function App() {\n  const { showForm } = useLoaderData<typeof loader>();\n\n  return (\n    <div className={styles.index}>\n      <div className={styles.content}>\n        <h1 className={styles.heading}>A short heading about [your app]</h1>\n        <p className={styles.text}>\n          A tagline about [your app] that describes your value proposition.\n        </p>\n        {showForm && (\n          <Form className={styles.form} method=\"post\" action=\"/auth/login\">\n            <label className={styles.label}>\n              <span>Shop domain</span>\n              <input className={styles.input} type=\"text\" name=\"shop\" />\n              <span>e.g: my-shop-domain.myshopify.com</span>\n            </label>\n            <button className={styles.button} type=\"submit\">\n              Log in\n            </button>\n          </Form>\n        )}\n        <ul className={styles.list}>\n          <li>\n            <strong>Product feature</strong>. Some detail about your feature and\n            its benefit to your customer.\n          </li>\n          <li>\n            <strong>Product feature</strong>. Some detail about your feature and\n            its benefit to your customer.\n          </li>\n          <li>\n            <strong>Product feature</strong>. Some detail about your feature and\n            its benefit to your customer.\n          </li>\n        </ul>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/routes/_index/styles.module.css",
    "content": ".index {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n  height: 100%;\n  width: 100%;\n  text-align: center;\n  padding: 1rem;\n}\n\n.heading,\n.text {\n  padding: 0;\n  margin: 0;\n}\n\n.text {\n  font-size: 1.2rem;\n  padding-bottom: 2rem;\n}\n\n.content {\n  display: grid;\n  gap: 2rem;\n}\n\n.form {\n  display: flex;\n  align-items: center;\n  justify-content: flex-start;\n  margin: 0 auto;\n  gap: 1rem;\n}\n\n.label {\n  display: grid;\n  gap: 0.2rem;\n  max-width: 20rem;\n  text-align: left;\n  font-size: 1rem;\n}\n\n.input {\n  padding: 0.4rem;\n}\n\n.button {\n  padding: 0.4rem;\n}\n\n.list {\n  list-style: none;\n  padding: 0;\n  padding-top: 3rem;\n  margin: 0;\n  display: flex;\n  gap: 2rem;\n}\n\n.list > li {\n  max-width: 20rem;\n  text-align: left;\n}\n\n@media only screen and (max-width: 50rem) {\n  .list {\n    display: block;\n  }\n\n  .list > li {\n    padding-bottom: 1rem;\n  }\n}\n"
  },
  {
    "path": "app/routes/app._index.tsx",
    "content": "import { useEffect } from \"react\";\nimport type { ActionFunctionArgs, LoaderFunctionArgs } from \"@remix-run/node\";\nimport { useFetcher } from \"@remix-run/react\";\nimport {\n  Page,\n  Layout,\n  Text,\n  Card,\n  Button,\n  BlockStack,\n  Box,\n  List,\n  Link,\n  InlineStack,\n} from \"@shopify/polaris\";\nimport { TitleBar, useAppBridge } from \"@shopify/app-bridge-react\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n  await authenticate.admin(request);\n\n  return null;\n};\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n  const { admin } = await authenticate.admin(request);\n  const color = [\"Red\", \"Orange\", \"Yellow\", \"Green\"][\n    Math.floor(Math.random() * 4)\n  ];\n  const response = await admin.graphql(\n    `#graphql\n      mutation populateProduct($product: ProductCreateInput!) {\n        productCreate(product: $product) {\n          product {\n            id\n            title\n            handle\n            status\n            variants(first: 10) {\n              edges {\n                node {\n                  id\n                  price\n                  barcode\n                  createdAt\n                }\n              }\n            }\n          }\n        }\n      }`,\n    {\n      variables: {\n        product: {\n          title: `${color} Snowboard`,\n        },\n      },\n    },\n  );\n  const responseJson = await response.json();\n\n  const product = responseJson.data!.productCreate!.product!;\n  const variantId = product.variants.edges[0]!.node!.id!;\n\n  const variantResponse = await admin.graphql(\n    `#graphql\n    mutation shopifyRemixTemplateUpdateVariant($productId: ID!, $variants: [ProductVariantsBulkInput!]!) {\n      productVariantsBulkUpdate(productId: $productId, variants: $variants) {\n        productVariants {\n          id\n          price\n          barcode\n          createdAt\n        }\n      }\n    }`,\n    {\n      variables: {\n        productId: product.id,\n        variants: [{ id: variantId, price: \"100.00\" }],\n      },\n    },\n  );\n\n  const variantResponseJson = await variantResponse.json();\n\n  return {\n    product: responseJson!.data!.productCreate!.product,\n    variant:\n      variantResponseJson!.data!.productVariantsBulkUpdate!.productVariants,\n  };\n};\n\nexport default function Index() {\n  const fetcher = useFetcher<typeof action>();\n\n  const shopify = useAppBridge();\n  const isLoading =\n    [\"loading\", \"submitting\"].includes(fetcher.state) &&\n    fetcher.formMethod === \"POST\";\n  const productId = fetcher.data?.product?.id.replace(\n    \"gid://shopify/Product/\",\n    \"\",\n  );\n\n  useEffect(() => {\n    if (productId) {\n      shopify.toast.show(\"Product created\");\n    }\n  }, [productId, shopify]);\n  const generateProduct = () => fetcher.submit({}, { method: \"POST\" });\n\n  return (\n    <Page>\n      <TitleBar title=\"Remix app template\">\n        <button variant=\"primary\" onClick={generateProduct}>\n          Generate a product\n        </button>\n      </TitleBar>\n      <BlockStack gap=\"500\">\n        <Layout>\n          <Layout.Section>\n            <Card>\n              <BlockStack gap=\"500\">\n                <BlockStack gap=\"200\">\n                  <Text as=\"h2\" variant=\"headingMd\">\n                    Congrats on creating a new Shopify app 🎉\n                  </Text>\n                  <Text variant=\"bodyMd\" as=\"p\">\n                    This embedded app template uses{\" \"}\n                    <Link\n                      url=\"https://shopify.dev/docs/apps/tools/app-bridge\"\n                      target=\"_blank\"\n                      removeUnderline\n                    >\n                      App Bridge\n                    </Link>{\" \"}\n                    interface examples like an{\" \"}\n                    <Link url=\"/app/additional\" removeUnderline>\n                      additional page in the app nav\n                    </Link>\n                    , as well as an{\" \"}\n                    <Link\n                      url=\"https://shopify.dev/docs/api/admin-graphql\"\n                      target=\"_blank\"\n                      removeUnderline\n                    >\n                      Admin GraphQL\n                    </Link>{\" \"}\n                    mutation demo, to provide a starting point for app\n                    development.\n                  </Text>\n                </BlockStack>\n                <BlockStack gap=\"200\">\n                  <Text as=\"h3\" variant=\"headingMd\">\n                    Get started with products\n                  </Text>\n                  <Text as=\"p\" variant=\"bodyMd\">\n                    Generate a product with GraphQL and get the JSON output for\n                    that product. Learn more about the{\" \"}\n                    <Link\n                      url=\"https://shopify.dev/docs/api/admin-graphql/latest/mutations/productCreate\"\n                      target=\"_blank\"\n                      removeUnderline\n                    >\n                      productCreate\n                    </Link>{\" \"}\n                    mutation in our API references.\n                  </Text>\n                </BlockStack>\n                <InlineStack gap=\"300\">\n                  <Button loading={isLoading} onClick={generateProduct}>\n                    Generate a product\n                  </Button>\n                  {fetcher.data?.product && (\n                    <Button\n                      url={`shopify:admin/products/${productId}`}\n                      target=\"_blank\"\n                      variant=\"plain\"\n                    >\n                      View product\n                    </Button>\n                  )}\n                </InlineStack>\n                {fetcher.data?.product && (\n                  <>\n                    <Text as=\"h3\" variant=\"headingMd\">\n                      {\" \"}\n                      productCreate mutation\n                    </Text>\n                    <Box\n                      padding=\"400\"\n                      background=\"bg-surface-active\"\n                      borderWidth=\"025\"\n                      borderRadius=\"200\"\n                      borderColor=\"border\"\n                      overflowX=\"scroll\"\n                    >\n                      <pre style={{ margin: 0 }}>\n                        <code>\n                          {JSON.stringify(fetcher.data.product, null, 2)}\n                        </code>\n                      </pre>\n                    </Box>\n                    <Text as=\"h3\" variant=\"headingMd\">\n                      {\" \"}\n                      productVariantsBulkUpdate mutation\n                    </Text>\n                    <Box\n                      padding=\"400\"\n                      background=\"bg-surface-active\"\n                      borderWidth=\"025\"\n                      borderRadius=\"200\"\n                      borderColor=\"border\"\n                      overflowX=\"scroll\"\n                    >\n                      <pre style={{ margin: 0 }}>\n                        <code>\n                          {JSON.stringify(fetcher.data.variant, null, 2)}\n                        </code>\n                      </pre>\n                    </Box>\n                  </>\n                )}\n              </BlockStack>\n            </Card>\n          </Layout.Section>\n          <Layout.Section variant=\"oneThird\">\n            <BlockStack gap=\"500\">\n              <Card>\n                <BlockStack gap=\"200\">\n                  <Text as=\"h2\" variant=\"headingMd\">\n                    App template specs\n                  </Text>\n                  <BlockStack gap=\"200\">\n                    <InlineStack align=\"space-between\">\n                      <Text as=\"span\" variant=\"bodyMd\">\n                        Framework\n                      </Text>\n                      <Link\n                        url=\"https://remix.run\"\n                        target=\"_blank\"\n                        removeUnderline\n                      >\n                        Remix\n                      </Link>\n                    </InlineStack>\n                    <InlineStack align=\"space-between\">\n                      <Text as=\"span\" variant=\"bodyMd\">\n                        Database\n                      </Text>\n                      <Link\n                        url=\"https://www.prisma.io/\"\n                        target=\"_blank\"\n                        removeUnderline\n                      >\n                        Prisma\n                      </Link>\n                    </InlineStack>\n                    <InlineStack align=\"space-between\">\n                      <Text as=\"span\" variant=\"bodyMd\">\n                        Interface\n                      </Text>\n                      <span>\n                        <Link\n                          url=\"https://polaris.shopify.com\"\n                          target=\"_blank\"\n                          removeUnderline\n                        >\n                          Polaris\n                        </Link>\n                        {\", \"}\n                        <Link\n                          url=\"https://shopify.dev/docs/apps/tools/app-bridge\"\n                          target=\"_blank\"\n                          removeUnderline\n                        >\n                          App Bridge\n                        </Link>\n                      </span>\n                    </InlineStack>\n                    <InlineStack align=\"space-between\">\n                      <Text as=\"span\" variant=\"bodyMd\">\n                        API\n                      </Text>\n                      <Link\n                        url=\"https://shopify.dev/docs/api/admin-graphql\"\n                        target=\"_blank\"\n                        removeUnderline\n                      >\n                        GraphQL API\n                      </Link>\n                    </InlineStack>\n                  </BlockStack>\n                </BlockStack>\n              </Card>\n              <Card>\n                <BlockStack gap=\"200\">\n                  <Text as=\"h2\" variant=\"headingMd\">\n                    Next steps\n                  </Text>\n                  <List>\n                    <List.Item>\n                      Build an{\" \"}\n                      <Link\n                        url=\"https://shopify.dev/docs/apps/getting-started/build-app-example\"\n                        target=\"_blank\"\n                        removeUnderline\n                      >\n                        {\" \"}\n                        example app\n                      </Link>{\" \"}\n                      to get started\n                    </List.Item>\n                    <List.Item>\n                      Explore Shopify’s API with{\" \"}\n                      <Link\n                        url=\"https://shopify.dev/docs/apps/tools/graphiql-admin-api\"\n                        target=\"_blank\"\n                        removeUnderline\n                      >\n                        GraphiQL\n                      </Link>\n                    </List.Item>\n                  </List>\n                </BlockStack>\n              </Card>\n            </BlockStack>\n          </Layout.Section>\n        </Layout>\n      </BlockStack>\n    </Page>\n  );\n}\n"
  },
  {
    "path": "app/routes/app.additional.tsx",
    "content": "import {\n  Box,\n  Card,\n  Layout,\n  Link,\n  List,\n  Page,\n  Text,\n  BlockStack,\n} from \"@shopify/polaris\";\nimport { TitleBar } from \"@shopify/app-bridge-react\";\n\nexport default function AdditionalPage() {\n  return (\n    <Page>\n      <TitleBar title=\"Additional page\" />\n      <Layout>\n        <Layout.Section>\n          <Card>\n            <BlockStack gap=\"300\">\n              <Text as=\"p\" variant=\"bodyMd\">\n                The app template comes with an additional page which\n                demonstrates how to create multiple pages within app navigation\n                using{\" \"}\n                <Link\n                  url=\"https://shopify.dev/docs/apps/tools/app-bridge\"\n                  target=\"_blank\"\n                  removeUnderline\n                >\n                  App Bridge\n                </Link>\n                .\n              </Text>\n              <Text as=\"p\" variant=\"bodyMd\">\n                To create your own page and have it show up in the app\n                navigation, add a page inside <Code>app/routes</Code>, and a\n                link to it in the <Code>&lt;NavMenu&gt;</Code> component found\n                in <Code>app/routes/app.jsx</Code>.\n              </Text>\n            </BlockStack>\n          </Card>\n        </Layout.Section>\n        <Layout.Section variant=\"oneThird\">\n          <Card>\n            <BlockStack gap=\"200\">\n              <Text as=\"h2\" variant=\"headingMd\">\n                Resources\n              </Text>\n              <List>\n                <List.Item>\n                  <Link\n                    url=\"https://shopify.dev/docs/apps/design-guidelines/navigation#app-nav\"\n                    target=\"_blank\"\n                    removeUnderline\n                  >\n                    App nav best practices\n                  </Link>\n                </List.Item>\n              </List>\n            </BlockStack>\n          </Card>\n        </Layout.Section>\n      </Layout>\n    </Page>\n  );\n}\n\nfunction Code({ children }: { children: React.ReactNode }) {\n  return (\n    <Box\n      as=\"span\"\n      padding=\"025\"\n      paddingInlineStart=\"100\"\n      paddingInlineEnd=\"100\"\n      background=\"bg-surface-active\"\n      borderWidth=\"025\"\n      borderColor=\"border\"\n      borderRadius=\"100\"\n    >\n      <code>{children}</code>\n    </Box>\n  );\n}\n"
  },
  {
    "path": "app/routes/app.tsx",
    "content": "import type { HeadersFunction, LoaderFunctionArgs } from \"@remix-run/node\";\nimport { Link, Outlet, useLoaderData, useRouteError } from \"@remix-run/react\";\nimport { boundary } from \"@shopify/shopify-app-remix/server\";\nimport { AppProvider } from \"@shopify/shopify-app-remix/react\";\nimport { NavMenu } from \"@shopify/app-bridge-react\";\nimport polarisStyles from \"@shopify/polaris/build/esm/styles.css?url\";\n\nimport { authenticate } from \"../shopify.server\";\n\nexport const links = () => [{ rel: \"stylesheet\", href: polarisStyles }];\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n  await authenticate.admin(request);\n\n  return { apiKey: process.env.SHOPIFY_API_KEY || \"\" };\n};\n\nexport default function App() {\n  const { apiKey } = useLoaderData<typeof loader>();\n\n  return (\n    <AppProvider isEmbeddedApp apiKey={apiKey}>\n      <NavMenu>\n        <Link to=\"/app\" rel=\"home\">\n          Home\n        </Link>\n        <Link to=\"/app/additional\">Additional page</Link>\n      </NavMenu>\n      <Outlet />\n    </AppProvider>\n  );\n}\n\n// Shopify needs Remix to catch some thrown responses, so that their headers are included in the response.\nexport function ErrorBoundary() {\n  return boundary.error(useRouteError());\n}\n\nexport const headers: HeadersFunction = (headersArgs) => {\n  return boundary.headers(headersArgs);\n};\n"
  },
  {
    "path": "app/routes/auth.$.tsx",
    "content": "import type { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n  await authenticate.admin(request);\n\n  return null;\n};\n"
  },
  {
    "path": "app/routes/auth.login/error.server.tsx",
    "content": "import type { LoginError } from \"@shopify/shopify-app-remix/server\";\nimport { LoginErrorType } from \"@shopify/shopify-app-remix/server\";\n\ninterface LoginErrorMessage {\n  shop?: string;\n}\n\nexport function loginErrorMessage(loginErrors: LoginError): LoginErrorMessage {\n  if (loginErrors?.shop === LoginErrorType.MissingShop) {\n    return { shop: \"Please enter your shop domain to log in\" };\n  } else if (loginErrors?.shop === LoginErrorType.InvalidShop) {\n    return { shop: \"Please enter a valid shop domain to log in\" };\n  }\n\n  return {};\n}\n"
  },
  {
    "path": "app/routes/auth.login/route.tsx",
    "content": "import { useState } from \"react\";\nimport type { ActionFunctionArgs, LoaderFunctionArgs } from \"@remix-run/node\";\nimport { Form, useActionData, useLoaderData } from \"@remix-run/react\";\nimport {\n  AppProvider as PolarisAppProvider,\n  Button,\n  Card,\n  FormLayout,\n  Page,\n  Text,\n  TextField,\n} from \"@shopify/polaris\";\nimport polarisTranslations from \"@shopify/polaris/locales/en.json\";\nimport polarisStyles from \"@shopify/polaris/build/esm/styles.css?url\";\n\nimport { login } from \"../../shopify.server\";\n\nimport { loginErrorMessage } from \"./error.server\";\n\nexport const links = () => [{ rel: \"stylesheet\", href: polarisStyles }];\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n  const errors = loginErrorMessage(await login(request));\n\n  return { errors, polarisTranslations };\n};\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n  const errors = loginErrorMessage(await login(request));\n\n  return {\n    errors,\n  };\n};\n\nexport default function Auth() {\n  const loaderData = useLoaderData<typeof loader>();\n  const actionData = useActionData<typeof action>();\n  const [shop, setShop] = useState(\"\");\n  const { errors } = actionData || loaderData;\n\n  return (\n    <PolarisAppProvider i18n={loaderData.polarisTranslations}>\n      <Page>\n        <Card>\n          <Form method=\"post\">\n            <FormLayout>\n              <Text variant=\"headingMd\" as=\"h2\">\n                Log in\n              </Text>\n              <TextField\n                type=\"text\"\n                name=\"shop\"\n                label=\"Shop domain\"\n                helpText=\"example.myshopify.com\"\n                value={shop}\n                onChange={setShop}\n                autoComplete=\"on\"\n                error={errors.shop}\n              />\n              <Button submit>Log in</Button>\n            </FormLayout>\n          </Form>\n        </Card>\n      </Page>\n    </PolarisAppProvider>\n  );\n}\n"
  },
  {
    "path": "app/routes/webhooks.app.scopes_update.tsx",
    "content": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport db from \"../db.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n    const { payload, session, topic, shop } = await authenticate.webhook(request);\n    console.log(`Received ${topic} webhook for ${shop}`);\n\n    const current = payload.current as string[];\n    if (session) {\n        await db.session.update({   \n            where: {\n                id: session.id\n            },\n            data: {\n                scope: current.toString(),\n            },\n        });\n    }\n    return new Response();\n};\n"
  },
  {
    "path": "app/routes/webhooks.app.uninstalled.tsx",
    "content": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport db from \"../db.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n  const { shop, session, topic } = await authenticate.webhook(request);\n\n  console.log(`Received ${topic} webhook for ${shop}`);\n\n  // Webhook requests can trigger multiple times and after an app has already been uninstalled.\n  // If this webhook already ran, the session may have been deleted previously.\n  if (session) {\n    await db.session.deleteMany({ where: { shop } });\n  }\n\n  return new Response();\n};\n"
  },
  {
    "path": "app/routes.ts",
    "content": "import { flatRoutes } from \"@remix-run/fs-routes\";\n\nexport default flatRoutes();\n"
  },
  {
    "path": "app/shopify.server.ts",
    "content": "import \"@shopify/shopify-app-remix/adapters/node\";\nimport {\n  ApiVersion,\n  AppDistribution,\n  shopifyApp,\n} from \"@shopify/shopify-app-remix/server\";\nimport { PrismaSessionStorage } from \"@shopify/shopify-app-session-storage-prisma\";\nimport prisma from \"./db.server\";\n\nconst shopify = shopifyApp({\n  apiKey: process.env.SHOPIFY_API_KEY,\n  apiSecretKey: process.env.SHOPIFY_API_SECRET || \"\",\n  apiVersion: ApiVersion.January25,\n  scopes: process.env.SCOPES?.split(\",\"),\n  appUrl: process.env.SHOPIFY_APP_URL || \"\",\n  authPathPrefix: \"/auth\",\n  sessionStorage: new PrismaSessionStorage(prisma),\n  distribution: AppDistribution.AppStore,\n  future: {\n    unstable_newEmbeddedAuthStrategy: true,\n    expiringOfflineAccessTokens: true,\n  },\n  ...(process.env.SHOP_CUSTOM_DOMAIN\n    ? { customShopDomains: [process.env.SHOP_CUSTOM_DOMAIN] }\n    : {}),\n});\n\nexport default shopify;\nexport const apiVersion = ApiVersion.January25;\nexport const addDocumentResponseHeaders = shopify.addDocumentResponseHeaders;\nexport const authenticate = shopify.authenticate;\nexport const unauthenticated = shopify.unauthenticated;\nexport const login = shopify.login;\nexport const registerWebhooks = shopify.registerWebhooks;\nexport const sessionStorage = shopify.sessionStorage;\n"
  },
  {
    "path": "env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n/// <reference types=\"@remix-run/node\" />\n"
  },
  {
    "path": "extensions/.gitkeep",
    "content": ""
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"app\",\n  \"private\": true,\n  \"scripts\": {\n    \"build\": \"remix vite:build\",\n    \"dev\": \"shopify app dev\",\n    \"config:link\": \"shopify app config link\",\n    \"generate\": \"shopify app generate\",\n    \"deploy\": \"shopify app deploy\",\n    \"config:use\": \"shopify app config use\",\n    \"env\": \"shopify app env\",\n    \"start\": \"remix-serve ./build/server/index.js\",\n    \"docker-start\": \"npm run setup && npm run start\",\n    \"setup\": \"prisma generate && prisma migrate deploy\",\n    \"lint\": \"eslint --cache --cache-location ./node_modules/.cache/eslint .\",\n    \"shopify\": \"shopify\",\n    \"prisma\": \"prisma\",\n    \"graphql-codegen\": \"graphql-codegen\",\n    \"vite\": \"vite\"\n  },\n  \"type\": \"module\",\n  \"engines\": {\n    \"node\": \">=20.19 <22 || >=22.12\"\n  },\n  \"dependencies\": {\n    \"@prisma/client\": \"^6.2.1\",\n    \"@remix-run/dev\": \"^2.16.1\",\n    \"@remix-run/fs-routes\": \"^2.16.1\",\n    \"@remix-run/node\": \"^2.16.1\",\n    \"@remix-run/react\": \"^2.16.1\",\n    \"@remix-run/serve\": \"^2.16.1\",\n    \"@shopify/app-bridge-react\": \"^4.1.6\",\n    \"@shopify/polaris\": \"^12.0.0\",\n    \"@shopify/shopify-app-remix\": \"^4.1.0\",\n    \"@shopify/shopify-app-session-storage-prisma\": \"^8.0.0\",\n    \"isbot\": \"^5.1.0\",\n    \"prisma\": \"^6.2.1\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"vite-tsconfig-paths\": \"^5.0.1\"\n  },\n  \"devDependencies\": {\n    \"@remix-run/eslint-config\": \"^2.16.1\",\n    \"@remix-run/route-config\": \"^2.16.1\",\n    \"@shopify/api-codegen-preset\": \"^1.1.1\",\n    \"@types/eslint\": \"^9.6.1\",\n    \"@types/node\": \"^22.2.0\",\n    \"@types/react\": \"^18.2.31\",\n    \"@types/react-dom\": \"^18.2.14\",\n    \"eslint\": \"^8.42.0\",\n    \"eslint-config-prettier\": \"^10.0.1\",\n    \"prettier\": \"^3.2.4\",\n    \"typescript\": \"^5.2.2\",\n    \"vite\": \"^6.2.2\"\n  },\n  \"workspaces\": {\n    \"packages\": [\n      \"extensions/*\"\n    ]\n  },\n  \"trustedDependencies\": [\n    \"@shopify/plugin-cloudflare\"\n  ],\n  \"resolutions\": {\n    \"@graphql-tools/url-loader\": \"8.0.16\",\n    \"@graphql-codegen/client-preset\": \"4.7.0\",\n    \"@graphql-codegen/typescript-operations\": \"4.5.0\",\n    \"minimatch\": \"9.0.5\",\n    \"vite\": \"^6.2.2\"\n  },\n  \"overrides\": {\n    \"@graphql-tools/url-loader\": \"8.0.16\",\n    \"@graphql-codegen/client-preset\": \"4.7.0\",\n    \"@graphql-codegen/typescript-operations\": \"4.5.0\",\n    \"minimatch\": \"9.0.5\",\n    \"vite\": \"^6.2.2\"\n  }\n}\n"
  },
  {
    "path": "prisma/migrations/20240530213853_create_session_table/migration.sql",
    "content": "-- CreateTable\nCREATE TABLE \"Session\" (\n    \"id\" TEXT NOT NULL PRIMARY KEY,\n    \"shop\" TEXT NOT NULL,\n    \"state\" TEXT NOT NULL,\n    \"isOnline\" BOOLEAN NOT NULL DEFAULT false,\n    \"scope\" TEXT,\n    \"expires\" DATETIME,\n    \"accessToken\" TEXT NOT NULL,\n    \"userId\" BIGINT,\n    \"firstName\" TEXT,\n    \"lastName\" TEXT,\n    \"email\" TEXT,\n    \"accountOwner\" BOOLEAN NOT NULL DEFAULT false,\n    \"locale\" TEXT,\n    \"collaborator\" BOOLEAN DEFAULT false,\n    \"emailVerified\" BOOLEAN DEFAULT false,\n    \"refreshToken\" TEXT,\n    \"refreshTokenExpires\" DATETIME\n);\n"
  },
  {
    "path": "prisma/schema.prisma",
    "content": "// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\ngenerator client {\n  provider = \"prisma-client-js\"\n}\n\n// Note that some adapters may set a maximum length for the String type by default, please ensure your strings are long\n// enough when changing adapters.\n// See https://www.prisma.io/docs/orm/reference/prisma-schema-reference#string for more information\ndatasource db {\n  provider = \"sqlite\"\n  url      = \"file:dev.sqlite\"\n}\n\nmodel Session {\n  id                  String    @id\n  shop                String\n  state               String\n  isOnline            Boolean   @default(false)\n  scope               String?\n  expires             DateTime?\n  accessToken         String\n  userId              BigInt?\n  firstName           String?\n  lastName            String?\n  email               String?\n  accountOwner        Boolean   @default(false)\n  locale              String?\n  collaborator        Boolean?  @default(false)\n  emailVerified       Boolean?  @default(false)\n  refreshToken        String?\n  refreshTokenExpires DateTime?\n}\n"
  },
  {
    "path": "shopify.app.toml",
    "content": "# This file stores configurations for your Shopify app.\n\nclient_id = \"\"\n\n[access_scopes]\n# Learn more at https://shopify.dev/docs/apps/tools/cli/configuration#access_scopes\nscopes = \"write_products\"\n\n[webhooks]\napi_version = \"2024-10\"\n\n  # Handled by: /app/routes/webhooks.app.uninstalled.tsx\n  [[webhooks.subscriptions]]\n  uri = \"/webhooks/app/uninstalled\"\n  topics = [\"app/uninstalled\"]\n\n  # Handled by: /app/routes/webhooks.app.scopes_update.tsx\n  [[webhooks.subscriptions]]\n  topics = [ \"app/scopes_update\" ]\n  uri = \"/webhooks/app/scopes_update\"\n\n  # Webhooks can have filters\n  # Only receive webhooks for product updates with a product price >= 10.00\n  # See: https://shopify.dev/docs/apps/build/webhooks/customize/filters\n  # [[webhooks.subscriptions]]\n  # topics = [\"products/update\"]\n  # uri = \"/webhooks/products/update\"\n  # filter = \"variants.price:>=10.00\"\n\n  # Mandatory compliance topic for public apps only\n  # See: https://shopify.dev/docs/apps/build/privacy-law-compliance\n  # [[webhooks.subscriptions]]\n  # uri = \"/webhooks/customers/data_request\"\n  # compliance_topics = [\"customers/data_request\"]\n\n  # [[webhooks.subscriptions]]\n  # uri = \"/webhooks/customers/redact\"\n  # compliance_topics = [\"customers/redact\"]\n\n  # [[webhooks.subscriptions]]\n  # uri = \"/webhooks/shop/redact\"\n  # compliance_topics = [\"shop/redact\"]\n"
  },
  {
    "path": "shopify.web.toml.liquid",
    "content": "name = \"remix\"\nroles = [\"frontend\", \"backend\"]\nwebhooks_path = \"/webhooks/app/uninstalled\"\n\n{%- assign exec = dependency_manager | append: ' exec' -%}\n{%- if dependency_manager == 'yarn' -%}\n{%- assign exec = 'yarn' -%}\n{%- endif %}\n[commands]\npredev = \"{{ exec }} prisma generate\"\ndev = \"{{ exec }} prisma migrate deploy && {{ exec }} remix vite:dev\"\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"include\": [\"env.d.ts\", \"**/*.ts\", \"**/*.tsx\"],\n  \"compilerOptions\": {\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ES2022\"],\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"isolatedModules\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"removeComments\": false,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noEmit\": true,\n    \"allowJs\": true,\n    \"resolveJsonModule\": true,\n    \"jsx\": \"react-jsx\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Bundler\",\n    \"target\": \"ES2022\",\n    \"baseUrl\": \".\",\n    \"types\": [\"node\"]\n  }\n}\n"
  },
  {
    "path": "vite.config.ts",
    "content": "import { vitePlugin as remix } from \"@remix-run/dev\";\nimport { installGlobals } from \"@remix-run/node\";\nimport { defineConfig, type UserConfig } from \"vite\";\nimport tsconfigPaths from \"vite-tsconfig-paths\";\n\ninstallGlobals({ nativeFetch: true });\n\n// Related: https://github.com/remix-run/remix/issues/2835#issuecomment-1144102176\n// Replace the HOST env var with SHOPIFY_APP_URL so that it doesn't break the remix server. The CLI will eventually\n// stop passing in HOST, so we can remove this workaround after the next major release.\nif (\n  process.env.HOST &&\n  (!process.env.SHOPIFY_APP_URL ||\n    process.env.SHOPIFY_APP_URL === process.env.HOST)\n) {\n  process.env.SHOPIFY_APP_URL = process.env.HOST;\n  delete process.env.HOST;\n}\n\nconst host = new URL(process.env.SHOPIFY_APP_URL || \"http://localhost\")\n  .hostname;\n\nlet hmrConfig;\nif (host === \"localhost\") {\n  hmrConfig = {\n    protocol: \"ws\",\n    host: \"localhost\",\n    port: 64999,\n    clientPort: 64999,\n  };\n} else {\n  hmrConfig = {\n    protocol: \"wss\",\n    host: host,\n    port: parseInt(process.env.FRONTEND_PORT!) || 8002,\n    clientPort: 443,\n  };\n}\n\nexport default defineConfig({\n  server: {\n    allowedHosts: [host],\n    cors: {\n      preflightContinue: true,\n    },\n    port: Number(process.env.PORT || 3000),\n    hmr: hmrConfig,\n    fs: {\n      // See https://vitejs.dev/config/server-options.html#server-fs-allow for more information\n      allow: [\"app\", \"node_modules\"],\n    },\n  },\n  plugins: [\n    remix({\n      ignoredRouteFiles: [\"**/.*\"],\n      future: {\n        v3_fetcherPersist: true,\n        v3_relativeSplatPath: true,\n        v3_throwAbortReason: true,\n        v3_lazyRouteDiscovery: true,\n        v3_singleFetch: false,\n        v3_routeConfig: true,\n      },\n    }),\n    tsconfigPaths(),\n  ],\n  build: {\n    assetsInlineLimit: 0,\n  },\n  optimizeDeps: {\n    include: [\"@shopify/app-bridge-react\", \"@shopify/polaris\"],\n  },\n}) satisfies UserConfig;\n"
  }
]