From 230450164babafce4626598a4e3db12c1b22824f Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Tue, 13 Jan 2026 14:22:58 +0000 Subject: [PATCH] fix(aws-lambda): Skip layer publication for pre-release versions AWS Lambda layers should not be published for pre-release versions by default, consistent with the existing isPushableToRegistry behavior that skips registry updates for prereleases. This adds an early check in the publish method to skip the entire layer publication process for pre-release versions unless linkPrereleases is explicitly set to true in the configuration. --- src/targets/__tests__/awsLambda.test.ts | 70 +++++++++++++++++----- src/targets/awsLambdaLayer.ts | 78 ++++++++++++++----------- 2 files changed, 99 insertions(+), 49 deletions(-) diff --git a/src/targets/__tests__/awsLambda.test.ts b/src/targets/__tests__/awsLambda.test.ts index 7c84948e..a1d6f1a6 100644 --- a/src/targets/__tests__/awsLambda.test.ts +++ b/src/targets/__tests__/awsLambda.test.ts @@ -1,4 +1,10 @@ -import { vi, type Mock, type MockInstance, type Mocked, type MockedFunction } from 'vitest'; +import { + vi, + type Mock, + type MockInstance, + type Mocked, + type MockedFunction, +} from 'vitest'; import { NoneArtifactProvider } from '../../artifact_providers/none'; import { ConfigurationError } from '../../utils/errors'; import { AwsLambdaLayerTarget } from '../awsLambdaLayer'; @@ -12,7 +18,7 @@ function getAwsLambdaTarget(): AwsLambdaLayerTarget { name: 'aws-lambda-layer', ['testKey']: 'testValue', }, - new NoneArtifactProvider() + new NoneArtifactProvider(), ); } @@ -91,7 +97,7 @@ describe('project config parameters', () => { } catch (error) { expect(error instanceof ConfigurationError).toBe(true); expect( - /Missing project configuration parameter/.test(error.message) + /Missing project configuration parameter/.test(error.message), ).toBe(true); } }); @@ -109,9 +115,8 @@ describe('project config parameters', () => { // throw an error and avoid the whole `publish` to be executed. So, if // the error in the mocked function is thrown, the project config test // was successful; on the other hand, if it's not thrown, the test fails. - awsTarget.getArtifactsForRevision = getArtifactsFailingMock.bind( - AwsLambdaLayerTarget - ); + awsTarget.getArtifactsForRevision = + getArtifactsFailingMock.bind(AwsLambdaLayerTarget); await awsTarget.publish('', ''); // Should break the mocked function. fail('Should not reach here'); } catch (error) { @@ -141,7 +146,8 @@ describe('layer name templating', () => { test('layer name with multiple version variables', () => { const awsTarget = getAwsLambdaTarget(); - awsTarget.config.layerName = 'SentrySDKv{{{major}}}-{{{minor}}}-{{{patch}}}'; + awsTarget.config.layerName = + 'SentrySDKv{{{major}}}-{{{minor}}}-{{{patch}}}'; const resolved = awsTarget.resolveLayerName('10.2.3'); expect(resolved).toBe('SentrySDKv10-2-3'); }); @@ -173,9 +179,8 @@ describe('publish', () => { test('error on missing artifact', async () => { const awsTarget = getAwsLambdaTarget(); setTestingProjectConfig(awsTarget); - awsTarget.getArtifactsForRevision = noArtifactsForRevision.bind( - AwsLambdaLayerTarget - ); + awsTarget.getArtifactsForRevision = + noArtifactsForRevision.bind(AwsLambdaLayerTarget); // `publish` should report an error. When it's not dry run, the error is // thrown; when it's on dry run, the error is logged and `undefined` is // returned. Thus, both alternatives have been considered. @@ -196,16 +201,15 @@ describe('publish', () => { test('error on having too many artifacts', async () => { const awsTarget = getAwsLambdaTarget(); setTestingProjectConfig(awsTarget); - awsTarget.getArtifactsForRevision = twoArtifactsForRevision.bind( - AwsLambdaLayerTarget - ); + awsTarget.getArtifactsForRevision = + twoArtifactsForRevision.bind(AwsLambdaLayerTarget); // `publish` should report an error. When it's not dry run, the error is // thrown; when it's on dry run, the error is logged and `undefined` is // returned. Thus, both alternatives have been considered. try { const multiplePackagesFound = await awsTarget.publish( 'version', - 'revision' + 'revision', ); expect(multiplePackagesFound).toBe(undefined); } catch (error) { @@ -214,4 +218,42 @@ describe('publish', () => { expect(multiplePackagesPattern.test(error.message)).toBe(true); } }); + + test('skips publishing for pre-release versions', async () => { + const awsTarget = getAwsLambdaTarget(); + setTestingProjectConfig(awsTarget); + + // Test various pre-release version formats + const preReleaseVersions = [ + '1.0.0-alpha.1', + '2.5.3-beta.2', + '3.0.0-rc.1', + '1.2.3-dev', + '4.0.0-preview.5', + ]; + + for (const version of preReleaseVersions) { + const result = await awsTarget.publish(version, 'revision'); + expect(result).toBe(undefined); + } + }); + + test('publishes for pre-release when linkPrereleases is true', async () => { + const awsTarget = getAwsLambdaTarget(); + setTestingProjectConfig(awsTarget); + awsTarget.awsLambdaConfig.linkPrereleases = true; + + const getArtifactsMock = vi.fn().mockImplementation(() => ['artifact.zip']); + awsTarget.getArtifactsForRevision = + getArtifactsMock.bind(AwsLambdaLayerTarget); + + // This should proceed to call getArtifactsForRevision for a pre-release + try { + await awsTarget.publish('1.0.0-alpha.1', 'revision'); + } catch (error) { + // Expected to fail at a later stage, but getArtifactsForRevision should be called + } + + expect(getArtifactsMock).toHaveBeenCalled(); + }); }); diff --git a/src/targets/awsLambdaLayer.ts b/src/targets/awsLambdaLayer.ts index b372ce44..8896d177 100644 --- a/src/targets/awsLambdaLayer.ts +++ b/src/targets/awsLambdaLayer.ts @@ -55,7 +55,7 @@ export class AwsLambdaLayerTarget extends BaseTarget { public constructor( config: TargetConfig, - artifactProvider: BaseArtifactProvider + artifactProvider: BaseArtifactProvider, ) { super(config, artifactProvider); this.github = getGitHubClient(); @@ -69,7 +69,7 @@ export class AwsLambdaLayerTarget extends BaseTarget { if (!process.env.AWS_ACCESS_KEY_ID || !process.env.AWS_SECRET_ACCESS_KEY) { throw new ConfigurationError( `Cannot publish AWS Lambda Layer: missing credentials. - Please use AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.` + Please use AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.`, ); } return { @@ -98,7 +98,7 @@ export class AwsLambdaLayerTarget extends BaseTarget { } if (missingConfigOptions.length > 0) { throw new ConfigurationError( - 'Missing project configuration parameter(s): ' + missingConfigOptions + 'Missing project configuration parameter(s): ' + missingConfigOptions, ); } } @@ -139,6 +139,14 @@ export class AwsLambdaLayerTarget extends BaseTarget { public async publish(version: string, revision: string): Promise { this.checkProjectConfig(); + // Skip publishing AWS Lambda layers for pre-releases unless explicitly configured + if (isPreviewRelease(version) && !this.awsLambdaConfig.linkPrereleases) { + this.logger.info( + `Skipping AWS Lambda layer publication for pre-release version ${version}`, + ); + return undefined; + } + this.logger.debug('Fetching artifact list...'); const packageFiles = await this.getArtifactsForRevision(revision, { includeNames: @@ -154,13 +162,13 @@ export class AwsLambdaLayerTarget extends BaseTarget { reportError( 'Cannot publish AWS Lambda Layer: ' + 'multiple packages with matching patterns were found. You may want ' + - 'to include or modify the includeNames parameter in the project config' + 'to include or modify the includeNames parameter in the project config', ); return undefined; } const artifactBuffer = fs.readFileSync( - await this.artifactProvider.downloadArtifact(packageFiles[0]) + await this.artifactProvider.downloadArtifact(packageFiles[0]), ); const awsRegions = await getRegionsFromAws(); @@ -172,31 +180,31 @@ export class AwsLambdaLayerTarget extends BaseTarget { await withTempDir( async directory => { this.logger.info( - `Cloning ${remote.getRemoteString()} to ${directory}...` + `Cloning ${remote.getRemoteString()} to ${directory}...`, ); - const git = await cloneRepo(remote.getRemoteStringWithAuth(), directory); - - await safeExec( - async () => { - await this.publishRuntimes( - version, - directory, - awsRegions, - artifactBuffer - ); - this.logger.debug('Finished publishing runtimes.'); - }, - 'publishRuntimes(...)' + const git = await cloneRepo( + remote.getRemoteStringWithAuth(), + directory, ); + await safeExec(async () => { + await this.publishRuntimes( + version, + directory, + awsRegions, + artifactBuffer, + ); + this.logger.debug('Finished publishing runtimes.'); + }, 'publishRuntimes(...)'); + await git.add(['.']); await git.checkout('master'); const runtimeNames = this.config.compatibleRuntimes.map( - (runtime: CompatibleRuntime) => runtime.name + (runtime: CompatibleRuntime) => runtime.name, ); await git.commit( 'craft(aws-lambda): AWS Lambda layers published\n\n' + - `v${version} for ${runtimeNames}` + `v${version} for ${runtimeNames}`, ); if (this.isPushableToRegistry(version)) { @@ -205,7 +213,7 @@ export class AwsLambdaLayerTarget extends BaseTarget { } }, true, - 'craft-release-awslambdalayer-' + 'craft-release-awslambdalayer-', ); } @@ -224,7 +232,7 @@ export class AwsLambdaLayerTarget extends BaseTarget { if (isPreviewRelease(version) && !this.awsLambdaConfig.linkPrereleases) { // preview release this.logger.info( - "Preview release detected, not updating the layer's data." + "Preview release detected, not updating the layer's data.", ); return false; } @@ -240,7 +248,7 @@ export class AwsLambdaLayerTarget extends BaseTarget { private createVersionSymlinks( directory: string, version: string, - versionFilepath: string + versionFilepath: string, ): void { this.logger.debug('Creating symlinks...'); const latestVersionPath = path.posix.join(directory, 'latest.json'); @@ -266,7 +274,7 @@ export class AwsLambdaLayerTarget extends BaseTarget { version: string, directory: string, awsRegions: string[], - artifactBuffer: Buffer + artifactBuffer: Buffer, ): Promise { const resolvedLayerName = this.resolveLayerName(version); this.logger.debug(`Resolved layer name: ${resolvedLayerName}`); @@ -280,7 +288,7 @@ export class AwsLambdaLayerTarget extends BaseTarget { this.config.license, artifactBuffer, awsRegions, - version + version, ); let publishedLayers = []; @@ -290,7 +298,7 @@ export class AwsLambdaLayerTarget extends BaseTarget { } catch (error) { this.logger.error( `Did not publish layers for ${runtime.name}.`, - error + error, ); return; } @@ -301,7 +309,7 @@ export class AwsLambdaLayerTarget extends BaseTarget { return; } else { this.logger.info( - `${runtime.name}: ${publishedLayers.length} layers published.` + `${runtime.name}: ${publishedLayers.length} layers published.`, ); } @@ -309,11 +317,11 @@ export class AwsLambdaLayerTarget extends BaseTarget { const runtimeBaseDir = path.posix.join( directory, this.AWS_REGISTRY_DIR, - runtime.name + runtime.name, ); if (!fs.existsSync(runtimeBaseDir)) { this.logger.warn( - `Directory structure for ${runtime.name} is missing, skipping file creation.` + `Directory structure for ${runtime.name} is missing, skipping file creation.`, ); return; } @@ -336,11 +344,11 @@ export class AwsLambdaLayerTarget extends BaseTarget { const baseFilepath = path.posix.join( runtimeBaseDir, - this.BASE_FILENAME + this.BASE_FILENAME, ); const newVersionFilepath = path.posix.join( runtimeBaseDir, - `${version}.json` + `${version}.json`, ); if (!fs.existsSync(baseFilepath)) { @@ -350,7 +358,7 @@ export class AwsLambdaLayerTarget extends BaseTarget { fs.writeFileSync(newVersionFilepath, manifestString); } else { const baseData = JSON.parse( - fs.readFileSync(baseFilepath, { encoding: 'utf-8' }).toString() + fs.readFileSync(baseFilepath, { encoding: 'utf-8' }).toString(), ); const manifestString = JSON.stringify({ ...baseData, ...runtimeData }, undefined, 2) + @@ -360,9 +368,9 @@ export class AwsLambdaLayerTarget extends BaseTarget { this.createVersionSymlinks(runtimeBaseDir, version, newVersionFilepath); this.logger.info( - `${runtime.name}: created files and updated symlinks.` + `${runtime.name}: created files and updated symlinks.`, ); - }) + }), ); } }