angularjs-to-react

Migrate an AngularJS codebase to a React codebase

Example

# Example Output React folder
# react-app/
# ├── public/                              # Contains static assets like HTML files, icons, etc.
# │   ├── favicon.ico                      # App icon
# │   ├── index.html                       # Main HTML file
# │   └── manifest.json                    # Manifest file for PWA configuration
# ├── src/
# │   ├── components/                      # Contains all React components.
# │   │   ├── Dashboard.tsx                # Example component
# │   │   ├── UserProfile.tsx              # Example component
# │   │   └── Header.tsx                   # Example component
# │   ├── services/                        # Services for handling API calls and other shared logic.
# │   │   ├── userService.ts               # Example service
# │   │   ├── apiService.ts                # Handles general API interactions
# │   │   └── authService.ts               # Authentication related services
# │   ├── hooks/                           # Custom React hooks.
# │   │   ├── useFetch.ts                  # Hook for API fetching
# │   │   ├── useAuth.ts                   # Authentication hook
# │   │   └── useResponsive.ts             # Responsive design hook
# │   ├── utils/                           # Utility functions.
# │   │   ├── format.ts                    # Formatting utilities
# │   │   ├── validate.ts                  # Validation utilities
# │   │   └── constants/                   # Constants used across the app
# │   │       ├── <constant-name>.ts
# │   │       ├── <constant-name>.ts
# │   │       └── <constant-name>.ts
# │   ├── store/                           # Redux store, reducers, and actions if using Redux.
# │   │   ├── index.ts                     # Combines reducers and configures the store
# │   │   ├── rootReducer.ts               # Root reducer combining all feature reducers
# │   │   ├── user/
# │   │   │   ├── userActions.ts           # Actions for user-related functionality
# │   │   │   ├── userReducer.ts           # Reducer for user-related data
# │   │   │   └── userTypes.ts             # TypeScript types and interfaces for user
# │   │   ├── product/
# │   │   │   ├── productActions.ts        # Actions for product-related functionality
# │   │   │   ├── productReducer.ts        # Reducer for product-related data
# │   │   │   └── productTypes.ts          # TypeScript types and interfaces for product
# │   │   └── settings/
# │   │       ├── settingsActions.ts       # Actions for settings functionality
# │   │       ├── settingsReducer.ts       # Reducer for settings data
# │   │       └── settingsTypes.ts         # TypeScript types and interfaces for settings
# │   ├── assets/                          # Static assets like images, stylesheets, etc.
# │   │   ├── images/                      # Folder for images
# │   │   ├── styles/                      # Folder for CSS or SCSS files
# │   │   └── fonts/                       # Folder for custom fonts
# │   ├── routes/
# │   │   ├── PrivateRoute.tsx             # Component for handling private routes
# │   │   └── Routes.tsx                   # All route definitions in one place
# │   ├── App.tsx                          # Root React component
# │   └── index.tsx                        # Entry point for React application, setup context providers, and Router
# ├── tsconfig.json                        # TypeScript configuration file
# ├── package.json                         # Project metadata and dependencies
# ├── .gitignore                           # Specifies intentionally untracked files to ignore
# └── README.md                            # Project overview and documentation

# High level overview of the process
# 1. **Create New React App**: Generate a new react app
# 2. **Convert Constants**: AngularJS constants to TypeScript constants
# 3. **Convert Filters**: AngularJS filters to TypeScript utilities
# 4. **Create Global Context**: Use a hook to replace rootScope assignments
# 5. **Convert Services**: AngularJS services to TypeScript services
# 6. **Convert Configs**: AngularJS configs to React router files/hooks
# 7. **Convert Directives**: AngularJS directives to React components
# 8. **Convert Controllers**: AngularJS controllers to React components
# 9. **Convert HTML Files**: Remaining HTML files to React components
# 10. **Convert Root App.tsx**: Integrate all converted parts

constants:
  react_directory: 'react'
  src_directory: '{{ react_directory }}/src'
  router_directory: '{{ src_directory }}/routes'
  components_directory: '{{ src_directory }}/components'
  services_directory: '{{ src_directory }}/services'
  utils_directory: '{{ src_directory }}/utils'
  hooks_directory: '{{ src_directory }}/hooks'
  global_hook_name: 'useRootScope'
  root_router_name: 'RootRouter'
  global_hook_file_path: '{{ hooks_directory }}/{{ global_hook_name }}.ts'
  root_router_file_path: '{{ router_directory }}/{{ root_router_name }}.tsx'
  limit: 5 # This is set to 5 to prevent running expensive jobs that may or may not work well
  context_depth: 3 # The depth of the context to retrieve for each file.

  entry_file_prompt: |
    Find the entry file for the provided angularjs repository. This should have something like angular.module('<'app' or the literal name of the app>', [...]) in it and is likely a javascript file.

  should_convert_to_react_prompt: |
    Should this file be converted to React? My preference is "No" if conversion isn't absolutely necessary.

    ## Criteria:
    - If the file contains `angular.module(...)`, `constants`, `factories`, `services`, or `directives`, or other code that would be converted to React, answer "Yes".
    - Otherwise, answer "No".

  transformation_notes: |
    ## Codebase Transformation Notes

    ### Generation Preferences
    When generating code, follow these guidelines:
    - Only include information from the portion you were told to convert.
    - Do not create new configs, components, or example usages.
    - Remove AngularJS-specific elements that cannot be converted to React.
    - Leave empty functions as they are.
    - If the current file path was provided above, you should ensure that the import paths are relative to this path.

    ### Angularjs to React Conversion Guidelines
    - Angular `ng-` directives should be converted to the react-equivilant logic. Do not leave these in the template.
    - Special built-in angularjs services and factories, typically defined as $<service-name>, should be converted to react-equivilant logic.
    - AngularJS injectors are no longer needed in React.

    ### New Files
    When defining new files based on unconverted files, assume the following naming strategies (useful for imports):
    - AngularJS constants -> TypeScript constants in `{{ utils_directory }}/constants/<constant-name>.ts`.
    - AngularJS services, factories, and providers -> React services in `{{ services_directory }}/<current-file-name>.ts`.
    - AngularJS directives and filters -> React components in `{{ components_directory }}/<current-file-name>.tsx`.
    - AngularJS controllers and template HTML files -> React components in `{{ components_directory }}/<current-file-name>.tsx`.

  global_dependency_rules:
    # Check for import paths
    - type: resolve_path
      source_patterns:
        - import .* from ["'](.*)["']
    # Find the rootScope assignments
    - type: string_match
      source_patterns:
        - "\\$rootScope\\.\\w+\\s*=\\s*(\\w+);"
        - ^(?!true$|false$|null).*
      target_patterns:
        - constant\(['"]{1}['"]
        - factory\(['"]{1}['"]
        - service\(['"]{1}['"]

  codebase_dependency_rules:
    # Check for import paths
    - type: resolve_path
      source_patterns:
        - import .* from ["'](.*)["']
    # Check for paths defined with templateUrl (usually in controllers)
    - type: resolve_path
      source_patterns:
        - templateUrl:\s*['\"]([^'\"]+)['\"]
    # Check for ng-controller in templates
    - type: string_match
      source_patterns:
        - ng-controller=['\"]([^'\"]+)['\"]
      target_patterns:
        - "controller\\(['\"]{1}['\"]"
    # Check for controller in templates, usually in directives
    - type: string_match
      source_patterns:
        - controller:\s*[\"']([^'\"]+)[\"']
      target_patterns:
        - "controller\\(['\"]{1}['\"]"
    # Check for constants, factories, etc. that are passed in to modules,
    # typically it will be module('module.name').factory/constant/service/...([...]
    - type: string_match
      source_patterns:
        # Grab all the values from the module
        - "\\[\\s*(['\"][^'\"]+['\"](?:,\\s*['\"][^'\"]+['\"])*),\\s*function"
        # Grab each of the values in quotes
        - '[''"]([^''"]+)[''"]'
      target_patterns:
        - constant\(['"]{1}['"]
        - factory\(['"]{1}['"]
        - service\(['"]{1}['"]
    # Check for directives... specifically tags that have a "-" in the middle
    - type: string_match
      source_patterns:
        - '<([a-zA-Z]+(?:-[a-zA-Z]+)+)[^>]*>'
      match_transformations:
        - camel_case
      target_patterns:
        - directive\(['"]{1}['"]
        - factory\(['"]{1}['"]
        - service\(['"]{1}['"]
    # Check for filters in the form {{ item | filter}} # TODO: This may need adjusting
    - type: string_match
      source_patterns:
        - \{\{([^\|}]+)\s*\|[^\|]+\}\}
        - ((\w+)\s*:\s*[^\|]+\|)*
      target_patterns:
        - filter\(['"]{1}['"]

# TODO: This flow duplicates some work, we need to figure out how to avoid that.
steps:
  ### Step 1. Create a new React app
  - tools:
      - name: create_react_app
        arguments:
          path_to_directory: '{{ react_directory }}'

  ### Step 2: Find and convert AngularJS constants to TypeScript constants
  - tools:
      - name: find_files_by_content_with_regex
        arguments:
          find_content_pattern: \.[\n]?constant\(['\"].*['\"]
          limit: '{{ limit }}'
          exclude_duplicates: true
        returns: constant_files
      - name: for_each
        items: '{{ constant_files }}'
        each_item:
          item_name: constant_file
          tools:
            - name: get_content_from_file
              arguments:
                path_to_file: '{{ constant_file }}'
                require_exists: true
              returns: constant_content

            - name: get_file_name
              arguments:
                path_to_file: '{{ constant_file }}'
                include_extension: false
              returns: constant_file_name

            # Get the names of each constant in this file (there may be many)
            - name: find_content_in_file_with_regex
              arguments:
                path_to_file: '{{ constant_file }}'
                find_content_pattern: \.[\n]?constant\(['\"]([^'\"]+)['\"]
                exclude_duplicates: true
              returns: constant_names

            - name: get_file_context_paths
              arguments:
                path_to_file: '{{ constant_file }}'
                depth: '{{ context_depth }}'
                rules: '{{ codebase_dependency_rules }}'
              returns: constant_file_context_paths

            # Convert global dependency files first
            - name: for_each
              items: '{{ constant_file_context_paths }}'
              each_item:
                item_name: dependency_file_path
                tools:
                  - name: get_content_from_file
                    arguments:
                      path_to_file: '{{ dependency_file_path }}'
                      require_exists: true
                    returns: dependency_file_content
                  - name: get_file_name
                    arguments:
                      path_to_file: '{{ dependency_file_path }}'
                      include_extension: false
                    returns: dependency_file_name_without_extension
                  - name: create_file_with_ai
                    arguments:
                      path_to_file: '{{ utils_directory }}/{{ dependency_file_name_without_extension }}.ts'
                      create_content_prompt: |
                        ## AngularJS File Contents:
                        ```
                        {{ dependency_file_content }}
                        ```

                        # Instructions
                        Create a TypeScript file that converts the above code into React-equivalent logic. The resulting file should contain utility functions or constants for import into other files.
                        The resulting file will be created at {{ utils_directory }}/{{ dependency_file_name_without_extension }}.ts.

                        {{ transformation_notes }}
                    returns: new_file_path
                  - name: mark_files_converted_in_context
                    arguments:
                      done_converting: true
                      old_file_path: '{{ dependency_file_path }}'
                      new_file_paths:
                        - '{{ new_file_path }}'
                      rules: '{{ codebase_dependency_rules }}'

            - name: get_file_context
              arguments:
                path_to_file: '{{ constant_file }}'
                depth: '{{ context_depth }}'
                rules: '{{ codebase_dependency_rules }}'
              returns: constant_file_context

            # Create a seperate file for each constant
            - name: for_each
              items: '{{ constant_names }}'
              returns_key: newly_created_constant_path
              each_item:
                item_name: constant_name
                tools:
                  - name: echo_one
                    arguments:
                      echo_arg: '{{ utils_directory }}/constants/{{ constant_name }}.ts'
                    returns: constant_file_path
                  - name: truncate_prompt
                    arguments:
                      contents:
                        - priority: 3
                          content: '{{ constant_file_context }}'
                        - priority: 2
                          content: |
                            # {{ constant_file }} Contents:
                            ```
                            {{ constant_content }}
                            ```
                        - priority: 1
                          content: |
                            # Instructions
                            Convert the above AngularJS constant into a TypeScript constant for React components.
                            Only convert the following constant: `{{ constant_name }}`. Ignore anything else.

                            The resulting file will be created at {{ constant_file_path }}.

                            {{ transformation_notes }}
                    returns: truncated_prompt

                  - name: create_file_with_ai
                    arguments:
                      path_to_file: '{{ constant_file_path }}'
                      create_content_prompt: '{{ truncated_prompt }}'
                    returns: newly_created_constant_path

              returns: newly_created_constant_paths

            - name: mark_files_converted_in_context
              arguments:
                done_converting: true
                old_file_path: '{{ constant_file }}'
                new_file_paths: '{{ newly_created_constant_paths }}'
                rules: '{{ codebase_dependency_rules }}'

  ### Step 3. Extract and Convert AngularJS Filters
  - tools:
      - name: find_files_by_content_with_regex
        arguments:
          find_content_pattern: \.[\n]?filter\(['\"].*['\"],
          limit: '{{ limit }}'
          exclude_duplicates: true
        returns: filter_files
      - name: for_each
        items: '{{ filter_files }}'
        each_item:
          item_name: filter_file
          tools:
            - name: get_content_from_file
              arguments:
                path_to_file: '{{ filter_file }}'
                require_exists: true
              returns: filter_content

            - name: get_file_name
              arguments:
                path_to_file: '{{ filter_file }}'
                include_extension: false
              returns: filter_file_name

            - name: find_content_in_file_with_regex
              arguments:
                path_to_file: '{{ filter_file }}'
                find_content_pattern: \.[\n]?filter\(['\"]([^'\"]+)['\"]
                exclude_duplicates: true
              returns: filter_names

            - name: get_file_context
              arguments:
                path_to_file: '{{ filter_file }}'
                depth: '{{ context_depth }}'
                rules: '{{ codebase_dependency_rules }}'
              returns: filter_file_context

            # Create a separate file for each filter
            - name: for_each
              items: '{{ filter_names }}'
              returns_key: newly_created_filter_path
              each_item:
                item_name: filter_name
                tools:
                  - name: echo_one
                    arguments:
                      echo_arg: '{{ utils_directory }}/{{ filter_name }}.ts'
                    returns: filter_file_path
                  - name: truncate_prompt
                    arguments:
                      contents:
                        - priority: 3
                          content: '{{ filter_file_context }}'
                        - priority: 2
                          content: |
                            # {{ filter_file }} Contents:
                            ```
                            {{ filter_content }}
                            ```
                        - priority: 1
                          content: |
                            # Instructions
                            Convert the above AngularJS filter into a TypeScript utility function for React components. Only convert the following filter: `{{ filter_name }}`. Ignore anything else.

                            The resulting file will be created at {{ filter_file_path }}.

                            {{ transformation_notes }}
                    returns: truncated_prompt

                  - name: create_file_with_ai
                    arguments:
                      path_to_file: '{{ filter_file_path }}'
                      create_content_prompt: '{{ truncated_prompt }}'
                    returns: newly_created_filter_path

              returns: newly_created_filter_paths

            - name: mark_files_converted_in_context
              arguments:
                done_converting: true
                old_file_path: '{{ filter_file }}'
                new_file_paths: '{{ newly_created_filter_paths }}'
                rules: '{{ codebase_dependency_rules }}'

  ### Step 4. Find and convert $rootScope to a hook.
  - tools:
      - name: find_file
        arguments:
          find_file_prompt: '{{ entry_file_prompt }}'
        returns: entry_file
      - name: get_file_context
        arguments:
          path_to_file: '{{ entry_file }}'
          depth: '{{ context_depth }}'
          rules: '{{ global_dependency_rules }}'
          are_global: true
        returns: root_file_context_for_globals
      - name: get_file_context_paths
        arguments:
          path_to_file: '{{ entry_file }}'
          depth: '{{ context_depth }}'
          rules: '{{ global_dependency_rules }}'
          are_global: true
        returns: root_file_context_paths_for_globals

      # Convert global dependency files first
      - name: for_each
        items: '{{ root_file_context_paths_for_globals }}'
        each_item:
          item_name: dependency_file_path
          tools:
            - name: get_content_from_file
              arguments:
                path_to_file: '{{ dependency_file_path }}'
                require_exists: true
              returns: dependency_file_content
            - name: get_file_name
              arguments:
                path_to_file: '{{ dependency_file_path }}'
                include_extension: false
              returns: dependency_file_name_without_extension
            - name: ask_question
              arguments:
                question_prompt: |
                  {{ should_convert_to_react_prompt }}


                  ## File Contents:
                  ```
                  {{ dependency_file_content }}
                  ```
              returns: should_edit_file
            - name: if_else
              condition: '{{ should_edit_file }}'
              returns_key: new_file_path
              if:
                tools:
                  # If this file needs to be converted, then convert it
                  - name: create_file_with_ai
                    arguments:
                      path_to_file: '{{ utils_directory }}/{{ dependency_file_name_without_extension }}.ts'
                      create_content_prompt: |
                        ## AngularJS File Contents:
                        ```
                        {{ dependency_file_content }}
                        ```

                        # Instructions
                        Create a TypeScript file that converts the above code into React-equivalent logic. The resulting file should contain utility functions or constants for import into other files.
                        The resulting file will be created at {{ utils_directory }}/{{ dependency_file_name_without_extension }}.ts.

                        {{ transformation_notes }}
                    returns: new_file_path
              else:
                tools:
                  # It doesn't need changing, just copy it to the new location
                  - name: copy_file
                    arguments:
                      source_path: '{{ dependency_file_path }}'
                      destination_path: '{{ utils_directory }}/{{ dependency_file_name_without_extension }}.ts'
                    returns: new_file_path
              returns: updated_file_path

            # Always mark the file as completed
            - name: mark_files_converted_in_context
              arguments:
                done_converting: true
                rules: '{{ global_dependency_rules }}'
                old_file_path: '{{ dependency_file_path }}'
                new_file_paths:
                  - '{{ updated_file_path }}'

      # Now, extract out the global dependencies from the root file and create a globals hook that defines the imports from the newly converted context files
      - name: get_file_context
        arguments:
          path_to_file: '{{ entry_file }}'
          depth: '{{ context_depth }}'
          rules: '{{ global_dependency_rules }}'
          are_global: true
        returns: utility_files_context
      - name: find_content_in_file_with_ai
        arguments:
          path_to_file: '{{ entry_file }}'
          find_context_prompt: |
            Find all places in the file where an attribute of $rootScope is set to a value in this file. Aka you should return all places where you see `$rootScope.someattribute = ...`.
        returns: root_scope_usages
      - name: truncate_prompt
        arguments:
          contents:
            - priority: 3
              content: '{{ utility_files_context }}'
            - priority: 2
              content: |
                # Root Scope Usages
                ```
                {{ root_scope_usages }}
                ```
            - priority: 1
              content: |
                # Instructions
                1. Create a TypeScript React hook called {{ global_hook_name }} to replace $rootScope usage.
                2. Refer to the "Root Scope Usages" header for all instances where $rootScope is set.
                3. Use and import the converted TypeScript utilities that match $rootScope attributes.
                4. Define functions, variables, and events from $rootScope in the new hook.

                {{ transformation_notes }}
        returns: truncated_prompt
      - name: create_file_with_ai
        arguments:
          path_to_file: '{{ global_hook_file_path }}'
          create_content_prompt: '{{ truncated_prompt }}'
        returns: newly_created_global_hook_path
      - name: mark_files_converted_in_context
        arguments:
          done_converting: True
          rules: '{{ global_dependency_rules }}'
          old_file_path: '{{ entry_file }}'
          new_file_paths:
            - '{{ newly_created_global_hook_path }}'

  ### Step 5: Find and convert AngularJS services to TypeScript services
  - tools:
      - name: find_files_by_content_with_regex
        arguments:
          find_content_pattern: \.[\n]?(factory|service|provider)\(['\"].*['\"]
          limit: '{{ limit }}'
          exclude_duplicates: true
        returns: service_files

      - name: for_each
        items: '{{ service_files }}'
        each_item:
          item_name: service_file
          tools:
            - name: get_content_from_file
              arguments:
                path_to_file: '{{ service_file }}'
              returns: service_content

            - name: get_file_name
              arguments:
                path_to_file: '{{ service_file }}'
                include_extension: false
              returns: service_file_name

            # Get the service name
            - name: find_content_in_file_with_regex
              arguments:
                path_to_file: '{{ service_file }}'
                find_content_pattern: \.[\n]?(?:factory|service|provider)\(['\"]([^'\"]+)['\"]
                exclude_duplicates: true
              returns: service_names

            - name: get_file_context
              arguments:
                path_to_file: '{{ service_file }}'
                depth: '{{ context_depth }}'
                rules: '{{ codebase_dependency_rules }}'
              returns: service_file_context

            # Create a separate file for each service
            - name: for_each
              items: '{{ service_names }}'
              returns_key: newly_created_service_path
              each_item:
                item_name: service_name
                tools:
                  - name: echo_one
                    arguments:
                      echo_arg: '{{ services_directory }}/{{ service_name }}.ts'
                    returns: service_file_path
                  - name: truncate_prompt
                    arguments:
                      contents:
                        - priority: 3
                          content: '{{ service_file_context }}'
                        - priority: 2
                          content: |
                            # {{ service_file }} Contents:
                            ```
                            {{ service_content }}
                            ```
                        - priority: 1
                          content: |
                            # Instructions
                            Convert the above AngularJS service into a TypeScript service for React components.
                            Only convert the following service: `{{ service_name }}`. Ignore anything else.
                            The resulting file will be created at {{ service_file_path }}. Ensure imports are relative to this path.

                            {{ transformation_notes }}
                    returns: truncated_prompt

                  - name: create_file_with_ai
                    arguments:
                      path_to_file: '{{ service_file_path }}'
                      create_content_prompt: '{{ truncated_prompt }}'
                    returns: newly_created_service_path

              returns: newly_created_service_paths

            - name: mark_files_converted_in_context
              arguments:
                done_converting: True
                rules: '{{ codebase_dependency_rules }}'
                old_file_path: '{{ service_file }}'
                new_file_paths: '{{ newly_created_service_paths }}'

  ### Step 6. Find all configs and convert them into either the router files or hooks.
  - tools:
      - name: find_files_by_content_with_regex
        arguments:
          find_content_pattern: \.[\n]?config\(\[
          limit: '{{ limit }}'
          exclude_duplicates: true
        returns: config_files
      - name: for_each
        items: '{{ config_files }}'
        returns_key: newly_created_router
        each_item:
          item_name: config_file
          tools:
            # Get file content and context
            - name: get_file_context
              arguments:
                path_to_file: '{{ config_file }}'
                depth: 1
                rules: '{{ global_dependency_rules }}'
                are_global: true
              returns: config_file_context
            - name: get_content_from_file
              arguments:
                path_to_file: '{{ config_file }}'
                require_exists: true
              returns: config_content

            # Find the name for each config
            - name: find_content_in_file_with_regex
              arguments:
                path_to_file: '{{ config_file }}'
                find_content_pattern: \.[\n]?config\(\[['\"]([^'\"]+)['\"]
              returns: config_names

            - name: get_file_name
              arguments:
                path_to_file: '{{ config_file }}'
                include_extension: false
              returns: config_file_name

            # Check if this config is for the router
            - name: find_content_in_file_with_regex
              arguments:
                path_to_file: '{{ config_file }}'
                find_content_pattern: \$(stateProvider|routeProvider)
                limit: '{{ limit }}'
                exclude_duplicates: true
              returns: state_provider_usages
            - name: if_else
              condition: '{{ state_provider_usages }}'
              returns_key: newly_created_path
              if:
                tools:
                  - name: truncate_prompt
                    arguments:
                      contents:
                        - priority: 3
                          content: '{{ config_file_context }}'
                        - priority: 2
                          content: |
                            # {{ config_file }} Contents:
                            ```
                            {{ config_content }}
                            ```
                        - priority: 1
                          content: |
                            # Instructions
                            Convert the above AngularJS config into a React router sub-component that will be imported and used in the root router file.
                            Only convert the configs in this file (typically in the form .config(...)). Ignore anything else.

                            {{ transformation_notes }}
                  # We are in the case where this needs to be converted into a router file
                  - name: create_file_with_ai
                    arguments:
                      path_to_file: '{{ router_directory }}/{{ config_file_name }}.ts'
                      create_content_prompt: '{{ truncated_prompt }}'
                    returns: newly_created_path
                  - name: mark_files_converted_in_context
                    arguments:
                      done_converting: True
                      old_file_path: '{{ config_file }}'
                      new_file_paths:
                        - '{{ newly_created_path }}'
              else:
                tools:
                  # Just return an empty string
                  - name: echo_one
                    arguments:
                      echo_arg: ''
                    returns: newly_created_path
              returns: newly_created_router
        returns: newly_created_router_paths

      # Read all of the content
      - name: for_each
        items: '{{ newly_created_router_paths }}'
        returns_key: newly_created_prompt
        each_item:
          item_name: newly_created_path
          tools:
            - name: get_content_from_file
              arguments:
                path_to_file: '{{ newly_created_path }}'
              returns: newly_created_content
            - name: build_prompt
              arguments:
                prompt_templates:
                  - check_exists: '{{ newly_created_content }}'
                    template: |
                      # {{ newly_created_path }} Contents:
                      ```
                      {content}
                      ```
                    content: '{{ newly_created_content }}'
              returns: newly_created_prompt
        returns: newly_created_prompts

      - name: truncate_prompt
        arguments:
          contents:
            - priority: 2
              content: '{{ newly_created_prompts }}'
            - priority: 1
              content: |
                # Instructions
                Above are all the routes for this application. Import and use them in this file, which will be the root router file imported into the main App.tsx file.
                The current path to the root router file is `{{ root_router_file_path }}`.

                {{ transformation_notes }}
        returns: truncated_prompt

      # Now, import all of these into the main router file
      - name: create_file_with_ai
        arguments:
          path_to_file: '{{ root_router_file_path }}'
          create_content_prompt: '{{ truncated_prompt }}'
        returns: root_router_file_path

      - name: mark_files_converted_in_context
        arguments:
          done_converting: True
          rules: '{{ global_dependency_rules }}'
          old_file_path: '{{ config_file }}'
          new_file_paths:
            - '{{ root_router_file_path }}'

  ### Step 7. Find and convert AngularJS Directives
  - tools:
      - name: find_files_by_content_with_regex
        arguments:
          find_content_pattern: \.[\n]?directive\(['"]
          limit: '{{ limit }}'
          exclude_duplicates: true
        returns: directive_files
      - name: for_each
        items: '{{ directive_files }}'
        each_item:
          item_name: directive_file
          tools:
            - name: get_content_from_file
              arguments:
                path_to_file: '{{ directive_file }}'
                require_exists: true
              returns: directive_content

            - name: get_file_name
              arguments:
                path_to_file: '{{ directive_file }}'
                include_extension: false
              returns: directive_file_name

            # Find the name of each directive in this file (there may be many)
            - name: find_content_in_file_with_regex
              arguments:
                path_to_file: '{{ directive_file }}'
                find_content_pattern: \.[\n]?directive\(['\"]([^'\"]+)['\"]
                exclude_duplicates: true
              returns: directive_names

            - name: get_file_context
              arguments:
                path_to_file: '{{ directive_file }}'
                depth: '{{ context_depth }}'
                rules: '{{ codebase_dependency_rules }}'
              returns: directive_file_context

            # Create a separate file for each directive
            - name: for_each
              items: '{{ directive_names }}'
              returns_key: newly_created_directive_path
              each_item:
                item_name: directive_name
                tools:
                  - name: echo_one
                    arguments:
                      echo_arg: '{{ components_directory }}/{{ directive_name }}.tsx'
                    returns: directive_file_path
                  - name: truncate_prompt
                    arguments:
                      contents:
                        - priority: 3
                          content: '{{ directive_file_context }}'
                        - priority: 2
                          content: |
                            # {{ directive_file }} Contents:
                            ```
                            {{ directive_content }}
                            ```
                        - priority: 1
                          content: |
                            # Instructions
                            Convert the above AngularJS directive into a React component in TypeScript. Follow best practices and use hooks where necessary. Only convert the following directive: `{{ directive_name }}`. Ignore everything else.
                            The resulting file will be created at {{ directive_file_path }}.

                            {{ transformation_notes }}
                    returns: truncated_prompt

                  - name: create_file_with_ai
                    arguments:
                      path_to_file: '{{ directive_file_path }}'
                      create_content_prompt: '{{ truncated_prompt }}'
                    returns: newly_created_directive_path

              returns: newly_created_directive_paths

            - name: mark_files_converted_in_context
              arguments:
                done_converting: True
                rules: '{{ codebase_dependency_rules }}'
                old_file_path: '{{ directive_file }}'
                new_file_paths: '{{ newly_created_directive_paths }}'

  ### Step 8: Find and convert AngularJS controllers to React components
  - tools:
      - name: find_files_by_content_with_regex
        arguments:
          find_content_pattern: \.[\n]?config\(\[
          limit: '{{ limit }}'
          exclude_duplicates: true
        returns: config_files
      - name: for_each
        items: '{{ config_files }}'
        each_item:
          item_name: config_file
          tools:
            # This has a mapping of controller to html template
            - name: get_content_from_file
              arguments:
                path_to_file: '{{ config_file }}'
                require_exists: true
              returns: config_content
            # Find the controllers
            - name: find_content_in_file_with_regex
              arguments:
                path_to_file: '{{ config_file }}'
                find_content_pattern: controller:\s*['\"]([^'\"]+)['\"]
                exclude_duplicates: true
              returns: controller_names
            - name: for_each
              items: '{{ controller_names }}'
              each_item:
                item_name: controller_name
                tools:
                  # Find the controller file path
                  - name: find_files_by_content_with_regex
                    arguments:
                      find_content_pattern: \.[\n]?controller\(['\"]{{ controller_name }}['\"]
                      limit: 1
                      exclude_duplicates: true
                    returns: controller_file_paths
                  - name: for_each
                    items: '{{ controller_file_paths }}'
                    each_item:
                      item_name: controller_file_path
                      tools:
                        # Get the context for the controller file
                        - name: get_file_context
                          arguments:
                            path_to_file: '{{ controller_file_path }}'
                            depth: '{{ context_depth }}'
                            rules: '{{ codebase_dependency_rules }}'
                          returns: controller_file_context

                        - name: get_content_from_file
                          arguments:
                            path_to_file: '{{ controller_file_path }}'
                            require_exists: true
                          returns: controller_content

                        - name: truncate_prompt
                          arguments:
                            contents:
                              - priority: 4
                                content: '{{ controller_file_context }}'
                              - priority: 3
                                content: |
                                  # Mapping of controller to html template:
                                  ```
                                  {{ config_content }}
                                  ```
                              - priority: 2
                                content: |
                                  # AngularJS {{ controller_name }} Controller:
                                  ```
                                  {{ controller_content }}
                                  ```
                              - priority: 1
                                content: |
                                  # Instructions
                                  1. Look at the code that maps the controller to the HTML template.
                                  2. Find the correct HTML template for this controller from the context.
                                  3. Convert the controller and its associated HTML template into a TypeScript React component.
                                  4. Extract logic from the controller into the React component as needed to make it fully functional.
                                  5. The resulting file will be created at {{ components_directory }}/{{ controller_name }}.tsx.

                                  {{ transformation_notes }}
                          returns: truncated_prompt

                        # Create the React component
                        - name: create_file_with_ai
                          arguments:
                            path_to_file: '{{ components_directory }}/{{ controller_name }}.tsx'
                            create_content_prompt: '{{ truncated_prompt }}'
                          returns: newly_created_controller_path

                        - name: mark_files_converted_in_context
                          arguments:
                            done_converting: True
                            rules: '{{ codebase_dependency_rules }}'
                            old_file_path: '{{ controller_file_path }}'
                            new_file_paths:
                              - '{{ newly_created_controller_path }}'
                              # TODO: Ideally we directly map html file to controller so we
                              # Can update the context directly

  ### Step 9: Find and convert all remaining html files to React components
  - tools:
      - name: find_files_by_name_with_regex
        arguments:
          find_file_name_pattern: "\\.html$"
          limit: '{{ limit }}'
        returns: html_files
      - name: for_each
        items: '{{ html_files }}'
        each_item:
          item_name: html_template_file
          tools:
            - name: get_content_from_file
              arguments:
                path_to_file: '{{ html_template_file }}'
                require_exists: true
              returns: html_template_content

            - name: get_file_name
              arguments:
                path_to_file: '{{ html_template_file }}'
                include_extension: false
              returns: html_template_file_name_without_extension

            - name: get_file_context
              arguments:
                path_to_file: '{{ html_template_file }}'
                depth: '{{ context_depth }}'
                rules: '{{ codebase_dependency_rules }}'
              returns: html_template_file_context

            - name: truncate_prompt
              arguments:
                contents:
                  - priority: 3
                    content: '{{ html_template_file_context }}'