Skip to content

Commit b2510b8

Browse files
committed
fix(cloudformation): Make CloudFormation LSP download strategy consistent with other plugins and more robust
1 parent 8354bf4 commit b2510b8

12 files changed

Lines changed: 494 additions & 315 deletions

File tree

packages/core/src/awsService/cloudformation/extension.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ import { openStackTemplateCommand } from './commands/openStackTemplate'
5050
import { selectRegionCommand } from './commands/regionCommands'
5151
import { AwsCredentialsService, encryptionKey } from './auth/credentials'
5252
import { ExtensionId, ExtensionName, CloudFormationTelemetrySettings } from './extensionConfig'
53-
import { VSCODE_EXTENSION_ID_CONSTANTS } from '../../shared/extensionIds'
5453
import { commandKey } from './utils'
5554
import { CloudFormationExplorer } from './explorer/explorer'
5655
import { handleTelemetryOptIn } from './telemetryOptIn'
@@ -150,7 +149,7 @@ async function startClient(context: ExtensionContext) {
150149
aws: {
151150
clientInfo: {
152151
extension: {
153-
name: VSCODE_EXTENSION_ID_CONSTANTS.awstoolkit,
152+
name: 'aws.toolkit.vscode',
154153
version: extensionVersion,
155154
},
156155
clientId: getClientId(globals.globalState, telemetryEnabled),

packages/core/src/awsService/cloudformation/lsp-server/githubManifestAdapter.ts

Lines changed: 30 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -3,199 +3,53 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { CfnLspName, CfnLspServerEnvType } from './lspServerConfig'
7-
import {
8-
addWindows,
9-
CfnManifest,
10-
CfnTarget,
11-
CfnLspVersion,
12-
dedupeAndGetLatestVersions,
13-
extractPlatformAndArch,
14-
useOldLinuxVersion,
15-
mapLegacyLinux,
16-
} from './utils'
6+
import { CfnLspServerEnvType } from './lspServerConfig'
7+
import { CfnManifest, CfnLspVersion, useOldLinuxVersion, mapLegacyLinux } from './utils'
178
import { getLogger } from '../../../shared/logger/logger'
18-
import { ToolkitError } from '../../../shared/errors'
9+
10+
const ManifestUrl =
11+
'https://raw.githubusercontent.com/aws-cloudformation/cloudformation-languageserver/refs/heads/main/assets/release-manifest.json'
1912

2013
export class GitHubManifestAdapter {
21-
constructor(
22-
private readonly repoOwner: string,
23-
private readonly repoName: string,
24-
readonly environment: CfnLspServerEnvType
25-
) {}
14+
private lastRawManifest?: string
2615

27-
async getManifest(): Promise<CfnManifest> {
28-
let manifest: CfnManifest
29-
try {
30-
manifest = await this.getManifestJson()
31-
} catch (err) {
32-
getLogger('awsCfnLsp').error(ToolkitError.chain(err, 'Failed to get CloudFormation manifest'))
33-
manifest = await this.getFromReleases()
34-
}
16+
constructor(readonly environment: CfnLspServerEnvType) {}
3517

36-
getLogger('awsCfnLsp').info(
37-
'Candidate versions: %s',
38-
manifest.versions
39-
.map(
40-
(v) =>
41-
`${v.serverVersion}[${v.targets
42-
.sort()
43-
.map((t) => `${t.platform}-${t.arch}-${t.nodejs}`)
44-
.join(',')}]`
45-
)
46-
.join(', ')
47-
)
48-
49-
if (process.platform !== 'linux') {
50-
return manifest
51-
}
18+
getLastRawManifest(): string | undefined {
19+
return this.lastRawManifest
20+
}
5221

53-
const useFallbackLinux = useOldLinuxVersion()
54-
if (!useFallbackLinux) {
55-
return manifest
22+
async getManifest(): Promise<CfnManifest> {
23+
const response = await fetch(ManifestUrl)
24+
if (!response.ok) {
25+
throw new Error(`Manifest fetch failed: ${response.status}`)
5626
}
5727

58-
getLogger('awsCfnLsp').info('In a legacy or sandbox Linux environment')
59-
manifest.versions = mapLegacyLinux(manifest.versions)
28+
const rawText = await response.text()
29+
this.lastRawManifest = rawText
30+
const json = JSON.parse(rawText) as Record<string, unknown>
6031

61-
getLogger('awsCfnLsp').info(
62-
'Remapped candidate versions: %s',
63-
manifest.versions
64-
.map(
65-
(v) =>
66-
`${v.serverVersion}[${v.targets
67-
.sort()
68-
.map((t) => `${t.platform}-${t.arch}-${t.nodejs}`)
69-
.join(',')}]`
70-
)
71-
.join(', ')
72-
)
73-
return manifest
74-
}
32+
const versions = json[this.environment] as CfnLspVersion[] | undefined
33+
const manifest: CfnManifest = {
34+
manifestSchemaVersion: json.manifestSchemaVersion as string,
35+
artifactId: json.artifactId as string,
36+
artifactDescription: json.artifactDescription as string,
37+
isManifestDeprecated: json.isManifestDeprecated as boolean,
38+
versions: versions ?? [],
39+
}
7540

76-
private async getFromReleases(): Promise<CfnManifest> {
77-
const releases = await this.fetchGitHubReleases()
78-
const envReleases = this.filterByEnvironment(releases)
79-
const sortedReleases = envReleases.sort((a, b) => {
80-
return b.tag_name.localeCompare(a.tag_name)
81-
})
82-
const versions = dedupeAndGetLatestVersions(sortedReleases.map((release) => this.convertRelease(release)))
8341
getLogger('awsCfnLsp').info(
8442
'Candidate versions: %s',
85-
versions
43+
manifest.versions
8644
.map((v) => `${v.serverVersion}[${v.targets.map((t) => `${t.platform}-${t.arch}`).join(',')}]`)
8745
.join(', ')
8846
)
89-
return {
90-
manifestSchemaVersion: '1.0',
91-
artifactId: CfnLspName,
92-
artifactDescription: 'GitHub CloudFormation Language Server',
93-
isManifestDeprecated: false,
94-
versions: versions,
95-
}
96-
}
97-
98-
private filterByEnvironment(releases: GitHubRelease[]): GitHubRelease[] {
99-
return releases.filter((release) => {
100-
const tag = release.tag_name
101-
if (this.environment === 'alpha') {
102-
return release.prerelease && tag.endsWith('-alpha')
103-
} else if (this.environment === 'beta') {
104-
return release.prerelease && tag.endsWith('-beta')
105-
} else {
106-
return !release.prerelease
107-
}
108-
})
109-
}
110-
111-
private async fetchGitHubReleases(): Promise<GitHubRelease[]> {
112-
const response = await fetch(`https://api.github.com/repos/${this.repoOwner}/${this.repoName}/releases`)
113-
if (!response.ok) {
114-
throw new Error(`GitHub API error: ${response.status}`)
115-
}
116-
return response.json()
117-
}
118-
119-
private convertRelease(release: GitHubRelease): CfnLspVersion {
120-
return {
121-
serverVersion: release.tag_name,
122-
isDelisted: false,
123-
targets: addWindows(this.extractTargets(release.assets)),
124-
}
125-
}
126-
127-
private extractTargets(assets: GitHubAsset[]): CfnTarget[] {
128-
return assets.map((asset) => {
129-
const { arch, platform, nodejs } = extractPlatformAndArch(asset.name)
130-
131-
return {
132-
platform,
133-
arch,
134-
nodejs,
135-
contents: [
136-
{
137-
filename: asset.name,
138-
url: asset.browser_download_url,
139-
hashes: [],
140-
bytes: asset.size,
141-
},
142-
],
143-
}
144-
})
145-
}
14647

147-
private async getManifestJson(): Promise<CfnManifest> {
148-
const response = await fetch(
149-
`https://raw.githubusercontent.com/${this.repoOwner}/${this.repoName}/refs/heads/main/assets/release-manifest.json`
150-
)
151-
if (!response.ok) {
152-
throw new Error(`GitHub API error: ${response.status}`)
48+
if (process.platform === 'linux' && useOldLinuxVersion()) {
49+
getLogger('awsCfnLsp').info('In a legacy or sandbox Linux environment')
50+
manifest.versions = mapLegacyLinux(manifest.versions)
15351
}
15452

155-
const json = (await response.json()) as Record<string, unknown>
156-
157-
return {
158-
manifestSchemaVersion: json.manifestSchemaVersion as string,
159-
artifactId: json.artifactId as string,
160-
artifactDescription: json.artifactDescription as string,
161-
isManifestDeprecated: json.isManifestDeprecated as boolean,
162-
versions: json[this.environment] as CfnLspVersion[],
163-
}
53+
return manifest
16454
}
16555
}
166-
167-
/* eslint-disable @typescript-eslint/naming-convention */
168-
interface GitHubAsset {
169-
url: string
170-
browser_download_url: string
171-
id: number
172-
node_id: string
173-
name: string
174-
label: string | null
175-
state: string
176-
content_type: string
177-
size: number
178-
download_count: number
179-
created_at: string
180-
updated_at: string
181-
}
182-
183-
interface GitHubRelease {
184-
url: string
185-
html_url: string
186-
assets_url: string
187-
upload_url: string
188-
tarball_url: string | null
189-
zipball_url: string | null
190-
id: number
191-
node_id: string
192-
tag_name: string
193-
target_commitish: string
194-
name: string | null
195-
body: string | null
196-
draft: boolean
197-
prerelease: boolean
198-
created_at: string // ISO 8601 date string
199-
published_at: string | null // ISO 8601 date string
200-
assets: GitHubAsset[]
201-
}

0 commit comments

Comments
 (0)