- 
                Notifications
    You must be signed in to change notification settings 
- Fork 29
New package Flow #324
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
          
     Merged
      
      
            eleanorjboyd
  merged 22 commits into
  microsoft:create-project-branch
from
eleanorjboyd:new-package
  
      
      
   
  May 19, 2025 
      
    
  
     Merged
                    New package Flow #324
Changes from all commits
      Commits
    
    
            Show all changes
          
          
            22 commits
          
        
        Select commit
          Hold shift + click to select a range
      
      ffba068
              
                Adding menu items (#310)
              
              
                eleanorjboyd 6d6e25d
              
                new package support for new project
              
              
                eleanorjboyd f31d3cf
              
                update and add TODO notes
              
              
                eleanorjboyd 98341dc
              
                updates
              
              
                eleanorjboyd 470db8b
              
                create venv support
              
              
                eleanorjboyd 7238114
              
                minor
              
              
                eleanorjboyd c7cbd4d
              
                minor edits
              
              
                eleanorjboyd 8e4680f
              
                edits first pass
              
              
                eleanorjboyd 7449d5e
              
                updates based on feedback
              
              
                eleanorjboyd 432d7f5
              
                updates
              
              
                eleanorjboyd 98e80c2
              
                cleanup
              
              
                eleanorjboyd 4856c1d
              
                remove UV creator
              
              
                eleanorjboyd 060c440
              
                support quick create
              
              
                eleanorjboyd 77ff0f7
              
                add newProjectSelection
              
              
                eleanorjboyd 7b2f915
              
                handle callback correctly for back button
              
              
                eleanorjboyd 3f43c8a
              
                touchups
              
              
                eleanorjboyd 9f35db4
              
                Adding menu items (#310)
              
              
                eleanorjboyd f027893
              
                use l10n
              
              
                eleanorjboyd 1c04a7a
              
                add project manager add to create function
              
              
                eleanorjboyd bd4cde8
              
                Merge branch 'create-project-branch' into new-package
              
              
                eleanorjboyd 9080a9b
              
                fix errors from merging
              
              
                eleanorjboyd d0e7390
              
                updates to pyproject.toml
              
              
                eleanorjboyd File filter
Filter by extension
Conversations
          Failed to load comments.   
        
        
          
      Loading
        
  Jump to
        
          Jump to file
        
      
      
          Failed to load files.   
        
        
          
      Loading
        
  Diff view
Diff view
There are no files selected for viewing
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,199 @@ | ||
| import * as fs from 'fs-extra'; | ||
| import * as path from 'path'; | ||
| import { extensions, l10n, QuickInputButtons, Uri, window } from 'vscode'; | ||
| import { CreateEnvironmentOptions } from '../../api'; | ||
| import { traceError, traceVerbose } from '../../common/logging'; | ||
| import { showQuickPickWithButtons } from '../../common/window.apis'; | ||
| import { EnvironmentManagers, InternalEnvironmentManager } from '../../internal.api'; | ||
|  | ||
| /** | ||
| * Prompts the user to choose whether to create a new virtual environment (venv) for a project, with a clearer return and early exit. | ||
| * @returns {Promise<boolean | undefined>} Resolves to true if 'Yes' is selected, false if 'No', or undefined if cancelled. | ||
| */ | ||
| export async function promptForVenv(callback: () => void): Promise<boolean | undefined> { | ||
| try { | ||
| const venvChoice = await showQuickPickWithButtons([{ label: l10n.t('Yes') }, { label: l10n.t('No') }], { | ||
| placeHolder: l10n.t('Would you like to create a new virtual environment for this project?'), | ||
| ignoreFocusOut: true, | ||
| showBackButton: true, | ||
| }); | ||
| if (!venvChoice) { | ||
| return undefined; | ||
| } | ||
| if (Array.isArray(venvChoice)) { | ||
| // Should not happen for single selection, but handle just in case | ||
| return venvChoice.some((item) => item.label === 'Yes'); | ||
| } | ||
| return venvChoice.label === 'Yes'; | ||
| } catch (ex) { | ||
| if (ex === QuickInputButtons.Back) { | ||
| callback(); | ||
| } | ||
| } | ||
| } | ||
|  | ||
| /** | ||
| * Checks if the GitHub Copilot extension is installed in the current VS Code environment. | ||
| * @returns {boolean} True if Copilot is installed, false otherwise. | ||
| */ | ||
| export function isCopilotInstalled(): boolean { | ||
| return !!extensions.getExtension('GitHub.copilot'); | ||
| } | ||
|  | ||
| /** | ||
| * Prompts the user to choose whether to create a Copilot instructions file, only if Copilot is installed. | ||
| * @returns {Promise<boolean | undefined>} Resolves to true if 'Yes' is selected, false if 'No', or undefined if cancelled or Copilot is not installed. | ||
| */ | ||
| export async function promptForCopilotInstructions(): Promise<boolean | undefined> { | ||
| if (!isCopilotInstalled()) { | ||
| return undefined; | ||
| } | ||
| const copilotChoice = await showQuickPickWithButtons([{ label: 'Yes' }, { label: 'No' }], { | ||
| placeHolder: 'Would you like to create a Copilot instructions file?', | ||
|         
                  eleanorjboyd marked this conversation as resolved.
              Show resolved
            Hide resolved | ||
| ignoreFocusOut: true, | ||
| showBackButton: true, | ||
| }); | ||
| if (!copilotChoice) { | ||
| return undefined; | ||
| } | ||
| if (Array.isArray(copilotChoice)) { | ||
| // Should not happen for single selection, but handle just in case | ||
| return copilotChoice.some((item) => item.label === 'Yes'); | ||
| } | ||
| return copilotChoice.label === 'Yes'; | ||
| } | ||
|  | ||
| /** | ||
| * Quickly creates a new Python virtual environment (venv) in the specified destination folder using the available environment managers. | ||
| * Attempts to use the venv manager if available, otherwise falls back to any manager that supports environment creation. | ||
| * @param envManagers - The collection of available environment managers. | ||
| * @param destFolder - The absolute path to the destination folder where the environment should be created. | ||
| * @returns {Promise<void>} Resolves when the environment is created or an error is shown. | ||
| */ | ||
| export async function quickCreateNewVenv(envManagers: EnvironmentManagers, destFolder: string) { | ||
|         
                  eleanorjboyd marked this conversation as resolved.
              Show resolved
            Hide resolved | ||
| // get the environment manager for venv, should always exist | ||
| const envManager: InternalEnvironmentManager | undefined = envManagers.managers.find( | ||
| (m) => m.id === 'ms-python.python:venv', | ||
| ); | ||
| const destinationUri = Uri.parse(destFolder); | ||
| if (envManager?.supportsQuickCreate) { | ||
| // with quickCreate enabled, user will not be prompted when creating the environment | ||
| const options: CreateEnvironmentOptions = { quickCreate: false }; | ||
| if (envManager.supportsQuickCreate) { | ||
| options.quickCreate = true; | ||
| } | ||
| const pyEnv = await envManager.create(destinationUri, options); | ||
| // TODO: do I need to update to say this is the env for the file? Like set it? | ||
| if (!pyEnv) { | ||
| // comes back as undefined if this doesn't work | ||
| window.showErrorMessage(`Failed to create virtual environment, please create it manually.`); | ||
| } else { | ||
| traceVerbose(`Created venv at: ${pyEnv?.environmentPath}`); | ||
| } | ||
| } else { | ||
| window.showErrorMessage(`Failed to quick create virtual environment, please create it manually.`); | ||
| } | ||
| } | ||
|  | ||
| /** | ||
| * Recursively replaces all occurrences of a string in file and folder names, as well as file contents, within a directory tree. | ||
| * @param dir - The root directory to start the replacement from. | ||
| * @param searchValue - The string to search for in names and contents. | ||
| * @param replaceValue - The string to replace with. | ||
| * @returns {Promise<void>} Resolves when all replacements are complete. | ||
| */ | ||
| export async function replaceInFilesAndNames(dir: string, searchValue: string, replaceValue: string) { | ||
|         
                  eleanorjboyd marked this conversation as resolved.
              Show resolved
            Hide resolved | ||
| const entries = await fs.readdir(dir, { withFileTypes: true }); | ||
| for (const entry of entries) { | ||
| let entryName = entry.name; | ||
| let fullPath = path.join(dir, entryName); | ||
| let newFullPath = fullPath; | ||
| // If the file or folder name contains searchValue, rename it | ||
| if (entryName.includes(searchValue)) { | ||
| const newName = entryName.replace(new RegExp(searchValue, 'g'), replaceValue); | ||
| newFullPath = path.join(dir, newName); | ||
| await fs.rename(fullPath, newFullPath); | ||
| entryName = newName; | ||
| } | ||
| if (entry.isDirectory()) { | ||
| await replaceInFilesAndNames(newFullPath, searchValue, replaceValue); | ||
| } else { | ||
| let content = await fs.readFile(newFullPath, 'utf8'); | ||
| if (content.includes(searchValue)) { | ||
| content = content.replace(new RegExp(searchValue, 'g'), replaceValue); | ||
| await fs.writeFile(newFullPath, content, 'utf8'); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|  | ||
| /** | ||
| * Ensures the .github/copilot-instructions.md file exists at the given root, creating or appending as needed. | ||
| * @param destinationRootPath - The root directory where the .github folder should exist. | ||
| * @param instructionsText - The text to write or append to the copilot-instructions.md file. | ||
| */ | ||
| export async function manageCopilotInstructionsFile( | ||
| destinationRootPath: string, | ||
| packageName: string, | ||
| instructionsFilePath: string, | ||
| ) { | ||
| const instructionsText = `\n \n` + (await fs.readFile(instructionsFilePath, 'utf-8')); | ||
| const githubFolderPath = path.join(destinationRootPath, '.github'); | ||
| const customInstructionsPath = path.join(githubFolderPath, 'copilot-instructions.md'); | ||
| if (!(await fs.pathExists(githubFolderPath))) { | ||
| // make the .github folder if it doesn't exist | ||
| await fs.mkdir(githubFolderPath); | ||
| } | ||
| const customInstructions = await fs.pathExists(customInstructionsPath); | ||
| if (customInstructions) { | ||
| // Append to the existing file | ||
| await fs.appendFile(customInstructionsPath, instructionsText.replace(/<package_name>/g, packageName)); | ||
| } else { | ||
| // Create the file if it doesn't exist | ||
| await fs.writeFile(customInstructionsPath, instructionsText.replace(/<package_name>/g, packageName)); | ||
| } | ||
| } | ||
|  | ||
| /** | ||
| * Appends a configuration object to the configurations array in a launch.json file. | ||
| * @param launchJsonPath - The absolute path to the launch.json file. | ||
| * @param projectLaunchConfig - The stringified JSON config to append. | ||
| */ | ||
| async function appendToJsonConfigs(launchJsonPath: string, projectLaunchConfig: string) { | ||
| let content = await fs.readFile(launchJsonPath, 'utf8'); | ||
| const json = JSON.parse(content); | ||
| // If it's a VS Code launch config, append to configurations array | ||
| if (json && Array.isArray(json.configurations)) { | ||
| const configObj = JSON.parse(projectLaunchConfig); | ||
| json.configurations.push(configObj); | ||
| await fs.writeFile(launchJsonPath, JSON.stringify(json, null, 4), 'utf8'); | ||
| } else { | ||
| traceError('Failed to add Project Launch Config to launch.json.'); | ||
| return; | ||
| } | ||
| } | ||
|  | ||
| /** | ||
| * Updates the launch.json file in the .vscode folder to include the provided project launch configuration. | ||
| * @param destinationRootPath - The root directory where the .vscode folder should exist. | ||
| * @param projectLaunchConfig - The stringified JSON config to append. | ||
| */ | ||
| export async function manageLaunchJsonFile(destinationRootPath: string, projectLaunchConfig: string) { | ||
| const vscodeFolderPath = path.join(destinationRootPath, '.vscode'); | ||
| const launchJsonPath = path.join(vscodeFolderPath, 'launch.json'); | ||
| if (!(await fs.pathExists(vscodeFolderPath))) { | ||
| await fs.mkdir(vscodeFolderPath); | ||
| } | ||
| const launchJsonExists = await fs.pathExists(launchJsonPath); | ||
| if (launchJsonExists) { | ||
| // Try to parse and append to existing launch.json | ||
| await appendToJsonConfigs(launchJsonPath, projectLaunchConfig); | ||
| } else { | ||
| // Create a new launch.json with the provided config | ||
| const launchJson = { | ||
| version: '0.2.0', | ||
| configurations: [JSON.parse(projectLaunchConfig)], | ||
| }; | ||
| await fs.writeFile(launchJsonPath, JSON.stringify(launchJson, null, 4), 'utf8'); | ||
| } | ||
| } | ||
      
      Oops, something went wrong.
        
    
  
  Add this suggestion to a batch that can be applied as a single commit.
  This suggestion is invalid because no changes were made to the code.
  Suggestions cannot be applied while the pull request is closed.
  Suggestions cannot be applied while viewing a subset of changes.
  Only one suggestion per line can be applied in a batch.
  Add this suggestion to a batch that can be applied as a single commit.
  Applying suggestions on deleted lines is not supported.
  You must change the existing code in this line in order to create a valid suggestion.
  Outdated suggestions cannot be applied.
  This suggestion has been applied or marked resolved.
  Suggestions cannot be applied from pending reviews.
  Suggestions cannot be applied on multi-line comments.
  Suggestions cannot be applied while the pull request is queued to merge.
  Suggestion cannot be applied right now. Please check back later.
  
    
  
    
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have localized versions of this in
localize.ts. You can address this later.