public async makeConfig()

in packages/core/src/shared/sam/debugger/awsSamDebugger.ts [393:685]


    public async makeConfig(
        folder: vscode.WorkspaceFolder | undefined,
        config: AwsSamDebuggerConfiguration,
        token?: vscode.CancellationToken
    ): Promise<SamLaunchRequestArgs> {
        if (token?.isCancellationRequested) {
            throw new ToolkitError('Cancellation requested', { cancelled: true })
        }

        folder =
            folder ?? (vscode.workspace.workspaceFolders?.length ? vscode.workspace.workspaceFolders[0] : undefined)
        if (!folder) {
            const message = localize(
                'AWS.sam.debugger.noWorkspace',
                'Choose a workspace, then try again',
                getIdeProperties().company
            )

            throw new SamLaunchRequestError(message, { code: 'NoWorkspaceFolder', extraButtons: [] })
        }

        // If "request" field is missing this means launch.json does not exist.
        // User/vscode expects us to dynamically decide defaults if possible.
        const hasLaunchJson = !!config.request
        const configValidator: AwsSamDebugConfigurationValidator = new DefaultAwsSamDebugConfigurationValidator(folder)

        if (!hasLaunchJson) {
            const message = localize(
                'AWS.sam.debugger.noLaunchJson',
                'To debug a Lambda locally, create a launch.json from the Run panel, then select a configuration.'
            )

            throw new SamLaunchRequestError(message, {
                code: 'NoLaunchConfig',
                extraButtons: [
                    {
                        label: localize('AWS.gotoRunPanel', 'Run panel'),
                        onClick: () => vscode.commands.executeCommand('workbench.view.debug'),
                    },
                ],
            })
        } else {
            const registry = await globals.templateRegistry
            const rv = await configValidator.validate(config, registry)
            if (!rv.isValid) {
                throw new ToolkitError(`Invalid launch configuration: ${rv.message}`, { code: 'BadLaunchConfig' })
            } else if (rv.message) {
                void vscode.window.showInformationMessage(rv.message)
            }
            getLogger().verbose(`SAM debug: config %s:`, config.name)
        }

        const editor = vscode.window.activeTextEditor
        const templateInvoke = config.invokeTarget as TemplateTargetProperties
        const template = await getTemplate(folder, config)
        const templateResource = await getTemplateResource(folder, config)
        const codeRoot = await getCodeRoot(folder, config)
        const architecture = getArchitecture(template, templateResource, config.invokeTarget)
        // Handler is the only field that we need to parse refs for.
        // This is necessary for Python debugging since we have to create the temporary entry file
        // Other refs can fail; SAM will handle them.
        const handlerName = await getHandlerName(folder, config)

        config.baseBuildDir = resolve(folder.uri.fsPath, config.sam?.buildDir ?? (await makeTemporaryToolkitFolder()))
        await fs.mkdir(config.baseBuildDir)

        if (templateInvoke?.templatePath) {
            // Normalize to absolute path.
            // TODO: If path is relative, it is relative to launch.json (i.e. .vscode directory).
            templateInvoke.templatePath = pathutil.normalize(tryGetAbsolutePath(folder, templateInvoke.templatePath))
        } else if (config.invokeTarget.target === 'code') {
            const codeConfig = config as SamLaunchRequestArgs & { invokeTarget: { target: 'code' } }
            // 'projectRoot' may be a relative path
            // Older code left this property as relative, but there's no benefit in doing that since it's relative to the workspace
            codeConfig.invokeTarget.projectRoot = pathutil.normalize(
                resolve(folder.uri.fsPath, config.invokeTarget.projectRoot)
            )
            templateInvoke.templatePath = getInputTemplatePath(codeConfig)
        }

        const isZip = CloudFormation.isZipLambdaResource(templateResource?.Properties)
        const runtime: string | undefined =
            config.lambda?.runtime ??
            (template && isZip
                ? CloudFormation.getStringForProperty(templateResource?.Properties, 'Runtime', template)
                : undefined) ??
            getDefaultRuntime(getRuntimeFamily(editor?.document?.languageId ?? 'unknown'))

        const lambdaMemory =
            (template
                ? CloudFormation.getNumberForProperty(templateResource?.Properties, 'MemorySize', template)
                : undefined) ?? config.lambda?.memoryMb
        const lambdaTimeout =
            (template
                ? CloudFormation.getNumberForProperty(templateResource?.Properties, 'Timeout', template)
                : undefined) ?? config.lambda?.timeoutSec

        // TODO: Remove this when min sam version is > 1.13.0
        if (!isZip) {
            const samCliVersion = await getSamCliVersion(this.ctx.samCliContext())
            if (semver.lt(samCliVersion, minSamCliVersionForImageSupport)) {
                const message = localize(
                    'AWS.output.sam.no.image.support',
                    'Support for Image-based Lambdas requires a minimum SAM CLI version of 1.13.0.'
                )

                throw new SamLaunchRequestError(message, { code: 'UnsupportedSamVersion', details: { samCliVersion } })
            }
        }

        if (!runtime) {
            const message = localize(
                'AWS.sam.debugger.failedLaunch.missingRuntime',
                'Toolkit could not infer a runtime for config: {0}. Add a "lambda.runtime" field to your launch configuration.',
                config.name
            )

            throw new SamLaunchRequestError(message, { code: 'MissingRuntime' })
        }

        // SAM CLI versions before 1.18.1 do not work correctly for Go debugging.
        // TODO: remove this when min sam version is >= 1.18.1
        if (goRuntimes.includes(runtime) && !config.noDebug) {
            const samCliVersion = await getSamCliVersion(this.ctx.samCliContext())
            if (semver.lt(samCliVersion, minSamCliVersionForGoSupport)) {
                void vscode.window.showWarningMessage(
                    localize(
                        'AWS.output.sam.local.no.go.support',
                        'Debugging go1.x lambdas requires a minimum SAM CLI version of {0}. Function will run locally without debug.',
                        minSamCliVersionForGoSupport
                    )
                )
                config.noDebug = true
            }
        }

        const runtimeFamily = getFamily(runtime)
        // use region in debug config first, if not found, fall back to toolkit default region.
        const region = config.aws?.region ?? this.ctx.awsContext.getCredentialDefaultRegion()
        const documentUri =
            vscode.window.activeTextEditor?.document.uri ??
            // XXX: don't know what URI to choose...
            vscode.Uri.parse(templateInvoke.templatePath!)

        let awsCredentials = await this.ctx.awsContext.getCredentials()
        if (!awsCredentials && !config.aws?.credentials) {
            getLogger().warn('SAM debug: missing AWS credentials (Toolkit is not connected)')
        } else if (config.aws?.credentials) {
            // "aws.credentials" defined in the launch-config takes precedence
            // over Toolkit's current active credentials.
            let fromStore: Credentials | undefined
            try {
                const credentialsId = fromString(config.aws.credentials)
                fromStore = await getCredentialsFromStore(credentialsId, this.ctx.credentialsStore)
            } catch {
                getLogger().error(`SAM debug: fromString('${config.aws.credentials}') failed`)
            }
            if (fromStore) {
                awsCredentials = fromStore
            } else {
                const credentialsId = config.aws.credentials
                const getHelp = localize('AWS.generic.message.getHelp', 'Get Help...')

                throw new SamLaunchRequestError(`Invalid credentials found in launch configuration: ${credentialsId}`, {
                    code: 'InvalidCredentials',
                    extraButtons: [
                        {
                            label: getHelp,
                            onClick: () => openUrl(vscode.Uri.parse(credentialHelpUrl)),
                        },
                    ],
                })
            }
        }

        if (config.api) {
            config.api.headers = {
                'content-type': 'application/json',
                ...(config.api.headers ? config.api.headers : {}),
            }
        }

        let parameterOverrideArr: string[] | undefined
        const params = config.sam?.template?.parameters
        if (params) {
            parameterOverrideArr = []
            for (const key of Object.keys(params)) {
                parameterOverrideArr.push(`${key}=${params[key].toString()}`)
            }
        }

        // TODO: Let the OS (or SAM CLI) assign the port, then we need to
        // scrape SAM CLI to find the port that was actually used?
        const apiPort = config.invokeTarget.target === 'api' ? await getStartPort() : undefined
        const debugPort = config.noDebug ? undefined : await getStartPort(apiPort ? apiPort + 1 : undefined)
        let launchConfig: SamLaunchRequestArgs = {
            ...config,
            request: 'attach',
            codeRoot: codeRoot ?? '',
            workspaceFolder: folder,
            runtime: runtime as Runtime,
            runtimeFamily: runtimeFamily,
            handlerName: handlerName,
            documentUri: documentUri,
            templatePath: pathutil.normalize(templateInvoke?.templatePath),
            eventPayloadFile: '', // Populated by makeConfig().
            envFile: '', // Populated by makeConfig().
            apiPort: apiPort,
            debugPort: debugPort,
            invokeTarget: {
                ...config.invokeTarget,
            },
            lambda: {
                ...config.lambda,
                memoryMb: lambdaMemory,
                timeoutSec: lambdaTimeout,
                environmentVariables: { ...config.lambda?.environmentVariables },
            },
            region: region,
            awsCredentials: awsCredentials,
            parameterOverrides: parameterOverrideArr,
            architecture: architecture,
        }

        //
        // Configure and launch.
        //
        // 1. prepare a bunch of arguments
        // 2. do `sam build`
        // 3. do `sam local invoke`
        //
        switch (launchConfig.runtimeFamily) {
            case RuntimeFamily.NodeJS: {
                // Make a NodeJS launch-config from the generic config.
                launchConfig = await tsDebug.makeTypescriptConfig(launchConfig)
                break
            }
            case RuntimeFamily.Python: {
                // Make a Python launch-config from the generic config.
                launchConfig = await pythonDebug.makePythonDebugConfig(launchConfig)
                break
            }
            case RuntimeFamily.DotNet: {
                // Make a DotNet launch-config from the generic config.
                launchConfig = await csharpDebug.makeCsharpConfig(launchConfig)
                break
            }
            case RuntimeFamily.Go: {
                launchConfig = await goDebug.makeGoConfig(launchConfig)
                break
            }
            case RuntimeFamily.Java: {
                // Make a Java launch-config from the generic config.
                launchConfig = await javaDebug.makeJavaConfig(launchConfig)
                break
            }
            default: {
                const message = localize(
                    'AWS.sam.debugger.invalidRuntime',
                    'Unknown or unsupported runtime: {0}',
                    runtime
                )

                throw new ToolkitError(message, { code: 'UnsupportedRuntime' })
            }
        }

        // generate template for target=code
        if (launchConfig.invokeTarget.target === 'code') {
            const codeConfig = launchConfig as SamLaunchRequestArgs & { invokeTarget: { target: 'code' } }
            await makeInputTemplate(codeConfig)
        }

        await makeJsonFiles(launchConfig)

        // Set the type, then vscode will pass the config to SamDebugSession.attachRequest().
        // (Registered in sam/activation.ts which calls registerDebugAdapterDescriptorFactory()).
        // By this point launchConfig.request is now set to "attach" (not "direct-invoke").
        launchConfig.type = AWS_SAM_DEBUG_TYPE

        if (launchConfig.request !== 'attach' && launchConfig.request !== 'launch') {
            // The "request" field must be updated so that it routes to the
            // DebugAdapter (SamDebugSession.attachRequest()), else this will
            // just cycle back (and it indicates a bug in the config logic).
            throw Error(
                `resolveDebugConfiguration: launchConfig was not correctly resolved before return: ${JSON.stringify(
                    launchConfig
                )}`
            )
        }

        return launchConfig
    }