Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 75 additions & 15 deletions packages/core/src/awsService/appBuilder/serverlessLand/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@
import * as nls from 'vscode-nls'
const localize = nls.loadMessageBundle()
import * as path from 'path'
import * as vscode from 'vscode'
import { getTelemetryReason, getTelemetryResult } from '../../../shared/errors'
import { getLogger } from '../../../shared/logger/logger'
import globals from '../../../shared/extensionGlobals'
import { checklogs } from '../../../shared/localizedText'
import { Result, telemetry } from '../../../shared/telemetry/telemetry'
import { CreateServerlessLandWizard } from './wizard'
import { CreateServerlessLandWizardForm, CreateServerlessLandWizard } from './wizard'
import { ExtContext } from '../../../shared/extensions'
import { addFolderToWorkspace } from '../../../shared/utilities/workspaceUtils'
import { ToolkitError } from '../../../shared/errors'
import { fs } from '../../../shared/fs/fs'
import { getPattern } from '../../../shared/utilities/downloadPatterns'

export const readmeFile: string = 'README.md'
const serverlessLandOwner = 'aws-samples'
const serverlessLandRepo = 'serverless-patterns'

/**
* Creates a new Serverless Land project using the provided extension context
Expand All @@ -31,38 +36,30 @@ export const readmeFile: string = 'README.md'
* 6. Handles errors and emits telemetry
*/
export async function createNewServerlessLandProject(extContext: ExtContext): Promise<void> {
const awsContext = extContext.awsContext
let createResult: Result = 'Succeeded'
let reason: string | undefined

try {
const credentials = await awsContext.getCredentials()
const defaultRegion = awsContext.getCredentialDefaultRegion()

// Launch the project creation wizard
const config = await new CreateServerlessLandWizard({
credentials,
defaultRegion,
}).run()
const config = await launchProjectCreationWizard(extContext)
if (!config) {
createResult = 'Cancelled'
reason = 'userCancelled'
return
}

// Add the project folder to the workspace
await downloadPatternCode(config)
await openReadmeFile(config)
await addFolderToWorkspace(
{
uri: config.location,
name: path.basename(config.location.fsPath),
uri: vscode.Uri.joinPath(config.location, config.name),
name: path.basename(config.name),
},
true
)
} catch (err) {
createResult = getTelemetryResult(err)
reason = getTelemetryReason(err)

globals.outputChannel.show(true)
getLogger().error(
localize(
'AWS.serverlessland.initWizard.general.error',
Expand All @@ -80,3 +77,66 @@ export async function createNewServerlessLandProject(extContext: ExtContext): Pr
})
}
}

async function launchProjectCreationWizard(
extContext: ExtContext
): Promise<CreateServerlessLandWizardForm | undefined> {
const awsContext = extContext.awsContext
const credentials = await awsContext.getCredentials()
const defaultRegion = awsContext.getCredentialDefaultRegion()

return new CreateServerlessLandWizard({
credentials,
defaultRegion,
}).run()
}

async function downloadPatternCode(config: CreateServerlessLandWizardForm): Promise<void> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems you are providing a Official Serverless Land experience here, could you help to update the Download SL logic I have in walkthrough to use your logic here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is going to be used in other places as well, I suggest bringing these function outside of main.py and into another file in this folder for readability.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this file, I am using the same getPattern() function that is defined in the shared/utilities/downloadPattern. The only difference is that I am appending the .zip extension to the asset_name to ensure the pattern code is downloaded correctly. In the metadata file, I am saving the asset_name without the extension, while for the Getting Started project, the asset_name was saved with the extension. If we plan to use the metadata for the Getting Started patterns in the future, we can update the approach there as well.

const assetName = config.assetName + '.zip'
const location = vscode.Uri.joinPath(config.location, config.name)
try {
await getPattern(serverlessLandOwner, serverlessLandRepo, assetName, location, true)
} catch (error) {
if (error instanceof ToolkitError) {
throw error
}
throw new ToolkitError(`Failed to download pattern: ${error}`)
}
}

async function openReadmeFile(config: CreateServerlessLandWizardForm): Promise<void> {
try {
const readmeUri = await getProjectUri(config, readmeFile)
if (!readmeUri) {
getLogger().warn('README.md file not found in the project directory')
return
}

await vscode.commands.executeCommand('workbench.action.focusFirstEditorGroup')
await vscode.window.showTextDocument(readmeUri)
} catch (err) {
getLogger().error(`Error in openReadmeFile: ${err}`)
throw new ToolkitError('Error processing README file')
}
}

async function getProjectUri(
config: Pick<CreateServerlessLandWizardForm, 'location' | 'name'>,
file: string
): Promise<vscode.Uri | undefined> {
if (!file) {
throw Error('expected "file" parameter to have at least one item')
}
const cfnTemplatePath = path.resolve(config.location.fsPath, config.name, file)
if (await fs.exists(cfnTemplatePath)) {
return vscode.Uri.file(cfnTemplatePath)
}
void vscode.window.showWarningMessage(
localize(
'AWS.serverlessLand.initWizard.source.error.notFound',
'Project created successfully, but {0} file not found: {1}',
file!,
cfnTemplatePath!
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,52 @@
"s3-lambda-resizing-sam": {
"name": "Resizing image",
"description": "Lambda, S3 • Python, Javascript, Java, .NET • SAM",
"runtimes": [
"implementation": [
{
"id": "python",
"name": "Python"
"iac": "sam",
"runtime": "python",
"assetName": "s3-lambda-resizing-python"
},
{
"id": "javascript",
"name": "Javascript"
"iac": "sam",
"runtime": "javascript",
"assetName": "s3-lambda"
},
{
"id": "dotnet",
"name": "Dotnet"
"iac": "sam",
"runtime": "java",
"assetName": "s3-lambda-resizing-java"
},
{
"id": "java",
"name": "Java"
}
],
"iac": [
{
"id": "sam",
"name": "SAM"
"iac": "sam",
"runtime": "dotnet",
"assetName": "s3-lambda-dotnet"
}
]
},
"apigw-rest-api-lambda-sam": {
"name": "Rest API",
"description": "Lambda, API Gateway • Python, Javascript, Java, .NET • SAM",
"runtimes": [
"implementation": [
{
"id": "python",
"name": "Python"
"iac": "sam",
"runtime": "python",
"assetName": "apigw-rest-api-lambda-python"
},
{
"id": "javascript",
"name": "Javascript"
"iac": "sam",
"runtime": "javascript",
"assetName": "apigw-rest-api-lambda-node"
},
{
"id": "dotnet",
"name": "Dotnet"
"iac": "sam",
"runtime": "java",
"assetName": "apigw-rest-api-lambda-java"
},
{
"id": "java",
"name": "Java"
}
],
"iac": [
{
"id": "sam",
"name": "AWS SAM"
"iac": "sam",
"runtime": "dotnet",
"assetName": "apigw-rest-api-lambda-dotnet"
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,15 @@
import * as nodefs from 'fs' // eslint-disable-line no-restricted-imports
import { ToolkitError } from '../../../shared/errors'

interface IaC {
id: string
name: string
}
interface Runtime {
id: string
name: string
version: string
interface Implementation {
iac: string
runtime: string
assetName: string
}
interface PatternData {
name: string
description: string
runtimes: Runtime[]
iac: IaC[]
implementation: Implementation[]
}

export interface ProjectMetadata {
Expand Down Expand Up @@ -96,11 +91,12 @@ export class MetadataManager {
*/
public getRuntimes(pattern: string): { label: string }[] {
const patternData = this.metadata?.patterns?.[pattern]
if (!patternData || !patternData.runtimes) {
if (!patternData || !patternData.implementation) {
return []
}
return patternData.runtimes.map((runtime) => ({
label: runtime.name,
const uniqueRuntimes = new Set(patternData.implementation.map((item) => item.runtime))
return Array.from(uniqueRuntimes).map((runtime) => ({
label: runtime,
}))
}

Expand All @@ -111,11 +107,22 @@ export class MetadataManager {
*/
public getIacOptions(pattern: string): { label: string }[] {
const patternData = this.metadata?.patterns?.[pattern]
if (!patternData || !patternData.iac) {
if (!patternData || !patternData.implementation) {
return []
}
return patternData.iac.map((iac) => ({
label: iac.name,
const uniqueIaCs = new Set(patternData.implementation.map((item) => item.iac))
return Array.from(uniqueIaCs).map((iac) => ({
label: iac,
}))
}
public getAssetName(selectedPattern: string, selectedRuntime: string, selectedIaC: string): string {
const patternData = this.metadata?.patterns?.[selectedPattern]
if (!patternData || !patternData.implementation) {
return ''
}
const matchingImplementation = patternData.implementation.find(
(impl) => impl.runtime === selectedRuntime && impl.iac === selectedIaC
)
return matchingImplementation?.assetName ?? ''
}
}
Loading