From 99814ddca82f9064bd71b0be9003b6019c8e7464 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 26 Feb 2024 16:24:13 +0100 Subject: [PATCH 01/20] autofixed settings.json --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 4d84ab76..eb7186a6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "editor.codeActionsOnSave": { - "source.organizeImports": false + "source.organizeImports": "never" }, "editor.formatOnType": true, "editor.formatOnSave": true, From d2d6c3742bc2c6b90827afaf7b01f5112d9601a5 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 26 Feb 2024 16:25:30 +0100 Subject: [PATCH 02/20] powershell publishing --- README.md | 35 +++++++++++- src/targets/powershell.ts | 113 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 src/targets/powershell.ts diff --git a/README.md b/README.md index 56e777d2..00d6842c 100644 --- a/README.md +++ b/README.md @@ -707,8 +707,9 @@ The `dotnet` tool must be available on the system. | Name | Description | | ------------------ | ---------------------------------------------------------------- | -| `NUGET_API_TOKEN` | NuGet personal API token (https://www.nuget.org/account/apikeys) | +| `NUGET_API_TOKEN` | NuGet personal API token () | | `NUGET_DOTNET_BIN` | **optional**. Path to .NET Core. Defaults to `dotnet` | +| `POWERSHELL_BIN` | **optional**. Path to .NET Core. Defaults to `dotnet` | **Configuration** @@ -1227,6 +1228,38 @@ targets: createTag: true ``` +### PowerShellGet (`powershell`) + +Uploads a module to [PowerShell Gallery](https://www.powershellgallery.com/) or another repository +supported by [PowerShellGet](https://learn.microsoft.com/en-us/powershell/module/powershellget)'s `Publish-Module`. + +The action looks for an artifact named `.zip` and extracts it to a temporary directory. +The extracted directory is then published as a module. + +#### Environment + +The `pwsh` executable [must be installed](https://github.com/powershell/powershell#get-powershell) on the system. + +| Name | Description | Default | +| -------------------- | ---------------------------------------------------- | --------- | +| `POWERSHELL_API_KEY` | **required** PowerShell Gallery API key | | +| `POWERSHELL_BIN` | **optional** Path to PowerShell binary | `pwsh` | + +#### Configuration + +| Option | Description | Default | +| -------------------- | ---------------------------------------------------- | --------- | +| `module` | **required** Module name. | | +| `repository` | **optional** Repository to publish the package to. | PSGallery | + +#### Example + +```yaml +targets: + - name: powershell + module: Sentry +``` + ## Integrating Your Project with `craft` Here is how you can integrate your GitHub project with `craft`: diff --git a/src/targets/powershell.ts b/src/targets/powershell.ts new file mode 100644 index 00000000..369e31dc --- /dev/null +++ b/src/targets/powershell.ts @@ -0,0 +1,113 @@ +import { basename, join } from 'path'; +import { BaseArtifactProvider } from '../artifact_providers/base'; +import { TargetConfig } from '../schemas/project_config'; +import { ConfigurationError, reportError } from '../utils/errors'; +import { withTempDir } from '../utils/files'; +import { checkExecutableIsPresent, extractZipArchive, spawnProcess } from '../utils/system'; +import { BaseTarget } from './base'; + +/** Command to launch PowerShell */ +export const POWERSHELL_BIN = process.env.POWERSHELL_BIN || 'pwsh'; + +/** Default repository */ +export const DEFAULT_POWERSHELL_REPOSITORY = 'PSGallery'; + +/** PowerShell target configuration options */ +export interface PowerShellTargetOptions { + /** API token */ + apiKey: string; + /** PowerShell repository name */ + repository: string; + /** Module name */ + module: string; +} + +/** + * Target responsible for publishing modules to a PowerShell repository + */ +export class PowerShellTarget extends BaseTarget { + /** Target name */ + public readonly name: string = 'powershell'; + /** Target options */ + public readonly psConfig: PowerShellTargetOptions; + + public constructor( + config: TargetConfig, + artifactProvider: BaseArtifactProvider + ) { + super(config, artifactProvider); + this.psConfig = this.getPowerShellConfig(); + checkExecutableIsPresent(POWERSHELL_BIN); + } + + /** + * Extracts target options from the raw configuration + */ + protected getPowerShellConfig(): PowerShellTargetOptions { + if (!process.env.POWERSHELL_API_KEY) { + throw new ConfigurationError( + `Cannot perform PowerShell release: missing credentials. + Please use POWERSHELL_API_KEY environment variable.` + ); + } + return { + apiKey: process.env.POWERSHELL_API_KEY, + repository: this.config.repository || DEFAULT_POWERSHELL_REPOSITORY, + module: this.config.module, + }; + } + /** + * Executes a PowerShell command. + */ + private async spawnPwsh(command: string): Promise { + return spawnProcess(POWERSHELL_BIN, ['-Command', command]); + } + + /** + * Publishes a package tarball to the PowerShell repository + * + * @param version New version to be released + * @param revision Git commit SHA to be published + */ + public async publish(_version: string, revision: string): Promise { + // Emit the PowerShell executable for informational purposes. + this.logger.info(`PowerShell (${POWERSHELL_BIN}) info:`); + await spawnProcess(POWERSHELL_BIN, ['--version']); + + // Also check the command and its its module version in case there are issues: + this.logger.info('Publish-Module command info:'); + await this.spawnPwsh('Get-Command -Name Publish-Module'); + + // Escape the given module artifact name to avoid regex issues. + const moduleArtifactRegex = `${this.psConfig.module}.zip`.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&'); + + this.logger.debug(`Looking for artifact matching ${moduleArtifactRegex}`); + const packageFiles = await this.getArtifactsForRevision(revision, { + includeNames: moduleArtifactRegex, + }); + if (!packageFiles.length) { + reportError( + `Cannot release the module to ${this.psConfig.repository}: there are no matching artifacts!` + ); + } else if (packageFiles.length > 1) { + reportError( + `Cannot release the module to ${this.psConfig.repository}: found multiple matching artifacts!` + ); + } + const artifact = packageFiles[0]; + const zipPath = await this.artifactProvider.downloadArtifact(artifact); + + this.logger.info(`Extracting artifact "${artifact.filename}"`) + await withTempDir(async dir => { + await extractZipArchive(zipPath, dir); + // All artifacts downloaded from GitHub are ZIP files. + const pkgName = basename(artifact.filename, '.zip'); + const distDir = join(dir, pkgName); + + await this.spawnPwsh(`Publish-Module -Name '${this.psConfig.module}' -Path '${distDir}' -Repository ${this.psConfig.repository} -NuGetApiKey ${this.psConfig.apiKey}`) + }); + + // await this.uploadAsset(path); + this.logger.info(`PowerShell module upload complete: $`); + } +} From 21ca7282bc5507382f1a69395f4d42c8aa46300f Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 26 Feb 2024 19:47:06 +0100 Subject: [PATCH 03/20] fixup readme --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 00d6842c..795ddab7 100644 --- a/README.md +++ b/README.md @@ -705,11 +705,10 @@ By default, `craft` publishes all packages with `.nupkg` extension. The `dotnet` tool must be available on the system. -| Name | Description | -| ------------------ | ---------------------------------------------------------------- | -| `NUGET_API_TOKEN` | NuGet personal API token () | -| `NUGET_DOTNET_BIN` | **optional**. Path to .NET Core. Defaults to `dotnet` | -| `POWERSHELL_BIN` | **optional**. Path to .NET Core. Defaults to `dotnet` | +| Name | Description | +| ------------------ | ----------------------------------------------------------------- | +| `NUGET_API_TOKEN` | NuGet personal [API token](https://www.nuget.org/account/apikeys) | +| `NUGET_DOTNET_BIN` | **optional**. Path to .NET Core. Defaults to `dotnet` | **Configuration** From 03adc756656573eadde10905ac254532439472aa Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 26 Feb 2024 19:56:54 +0100 Subject: [PATCH 04/20] add dry-run support --- src/targets/powershell.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/targets/powershell.ts b/src/targets/powershell.ts index 369e31dc..4f70ca91 100644 --- a/src/targets/powershell.ts +++ b/src/targets/powershell.ts @@ -5,6 +5,7 @@ import { ConfigurationError, reportError } from '../utils/errors'; import { withTempDir } from '../utils/files'; import { checkExecutableIsPresent, extractZipArchive, spawnProcess } from '../utils/system'; import { BaseTarget } from './base'; +import { isDryRun } from 'src/utils/helpers'; /** Command to launch PowerShell */ export const POWERSHELL_BIN = process.env.POWERSHELL_BIN || 'pwsh'; @@ -104,7 +105,12 @@ export class PowerShellTarget extends BaseTarget { const pkgName = basename(artifact.filename, '.zip'); const distDir = join(dir, pkgName); - await this.spawnPwsh(`Publish-Module -Name '${this.psConfig.module}' -Path '${distDir}' -Repository ${this.psConfig.repository} -NuGetApiKey ${this.psConfig.apiKey}`) + await this.spawnPwsh('Publish-Module' + + ` -Name '${this.psConfig.module}'` + + ` -Path '${distDir}'` + + ` -Repository ${this.psConfig.repository}` + + ` -NuGetApiKey ${this.psConfig.apiKey}` + + (isDryRun() ? ' -WhatIf' : '')) }); // await this.uploadAsset(path); From 2004e3269eef0baae649eccb758f594a4a276458 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Mon, 26 Feb 2024 20:04:59 +0100 Subject: [PATCH 05/20] cleanup --- src/targets/powershell.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/targets/powershell.ts b/src/targets/powershell.ts index 4f70ca91..30f85f1d 100644 --- a/src/targets/powershell.ts +++ b/src/targets/powershell.ts @@ -113,7 +113,6 @@ export class PowerShellTarget extends BaseTarget { (isDryRun() ? ' -WhatIf' : '')) }); - // await this.uploadAsset(path); this.logger.info(`PowerShell module upload complete: $`); } } From 873eb624304b50fdf50590b75765e0bd3d05edfd Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 27 Feb 2024 10:03:15 +0100 Subject: [PATCH 06/20] add powershell to docker --- Dockerfile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Dockerfile b/Dockerfile index 6d08cc4e..d2a78d78 100644 --- a/Dockerfile +++ b/Dockerfile @@ -78,6 +78,13 @@ RUN curl -fsSL https://storage.googleapis.com/flutter_infra_release/releases/sta && tar xf /opt/flutter.tar.xz -C /opt \ && rm /opt/flutter.tar.xz +# https://learn.microsoft.com/en-us/powershell/scripting/install/install-debian +RUN curl -fsSL https://github.com/PowerShell/PowerShell/releases/download/v7.4.1/powershell_7.4.1-1.deb_amd64.deb -o /opt/powershell.deb \ + && dpkg -i /opt/powershell.deb \ + && apt-get install -f \ + && apt-get clean \ + && rm /opt/powershell.deb + # craft does `git` things against mounted directories as root RUN git config --global --add safe.directory '*' From 2613b71a75c7d25ed7257190a1601322f480eac5 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 27 Feb 2024 10:11:48 +0100 Subject: [PATCH 07/20] fix: version test for equal pre-release versions --- src/utils/__tests__/version.test.ts | 19 +++++++++++++++++++ src/utils/version.ts | 7 ++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/utils/__tests__/version.test.ts b/src/utils/__tests__/version.test.ts index c3922d84..76020deb 100644 --- a/src/utils/__tests__/version.test.ts +++ b/src/utils/__tests__/version.test.ts @@ -195,6 +195,25 @@ describe('versionGreaterOrEqualThan', () => { expect(() => versionGreaterOrEqualThan(v1, v2)).toThrowError(); expect(() => versionGreaterOrEqualThan(v2, v1)).toThrowError(); }); + + test('can compare pre parts that are the same', () => { + let v1 = parseVersion('1.2.3-dev.0')!; + let v2 = parseVersion('1.2.3-dev.0')!; + expect(versionGreaterOrEqualThan(v1, v2)).toBe(true); + expect(versionGreaterOrEqualThan(v2, v1)).toBe(true); + + v1 = parseVersion('1.2.3-dev.0+A')!; + v2 = parseVersion('1.2.3-dev.0+A')!; + expect(versionGreaterOrEqualThan(v1, v2)).toBe(true); + expect(versionGreaterOrEqualThan(v2, v1)).toBe(true); + }); + + test('can compare pre parts that are the same but have different builds', () => { + const v1 = parseVersion('1.2.3-dev.0+buildA')!; + const v2 = parseVersion('1.2.3-dev.0+buildB')!; + expect(versionGreaterOrEqualThan(v1, v2)).toBe(false); + expect(versionGreaterOrEqualThan(v2, v1)).toBe(false); + }); }); describe('getPackage', () => { diff --git a/src/utils/version.ts b/src/utils/version.ts index 5ab3581a..853e5d8b 100644 --- a/src/utils/version.ts +++ b/src/utils/version.ts @@ -89,6 +89,8 @@ export function versionGreaterOrEqualThan(v1: SemVer, v2: SemVer): boolean { return true; } else if (v1.pre && !v2.pre) { return false; + } else if (v1.pre && v2.pre && v1.pre === v2.pre) { + return v1.build === v2.build; } else if (v1.pre && v2.pre && v1.pre !== v2.pre && /^\d+$/.test(v1.pre) && /^\d+$/.test(v2.pre)) { return v1.pre > v2.pre; } else if (v1.build || v2.build || v1.pre || v2.pre) { @@ -157,7 +159,6 @@ export function getPackageVersion(): string { * Returns the stringified version of the passed SemVer object. */ export function semVerToString(s: SemVer) { - return `${s.major}.${s.minor}.${s.patch}${s.pre ? `-${s.pre}` : ''}${ - s.build ? `+${s.build}` : '' - }`; + return `${s.major}.${s.minor}.${s.patch}${s.pre ? `-${s.pre}` : ''}${s.build ? `+${s.build}` : '' + }`; } From 66ddde55093bd765e7f37b5b7d77772b6ce5356a Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 27 Feb 2024 10:15:02 +0100 Subject: [PATCH 08/20] add missing gem cleanup --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index d2a78d78..d0a2e2ef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -65,6 +65,7 @@ RUN curl -fsSL https://packages.microsoft.com/config/debian/10/packages-microsof && cargo --version \ && cargo install cargo-hack \ && gem install -g --no-document \ + && gem cleanup all \ # Install https://github.com/getsentry/symbol-collector && symbol_collector_url=$(curl -s https://api.github.com/repos/getsentry/symbol-collector/releases/tags/1.12.0 | \ jq -r '.assets[].browser_download_url | select(endswith("symbolcollector-console-linux-x64.zip"))') \ From da20804197bf720d7999a2d169e27097f9890afd Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 27 Feb 2024 11:34:17 +0100 Subject: [PATCH 09/20] fix artifact download --- src/targets/powershell.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/targets/powershell.ts b/src/targets/powershell.ts index 30f85f1d..b212897c 100644 --- a/src/targets/powershell.ts +++ b/src/targets/powershell.ts @@ -80,7 +80,8 @@ export class PowerShellTarget extends BaseTarget { await this.spawnPwsh('Get-Command -Name Publish-Module'); // Escape the given module artifact name to avoid regex issues. - const moduleArtifactRegex = `${this.psConfig.module}.zip`.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&'); + let moduleArtifactRegex = `${this.psConfig.module}`.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&'); + moduleArtifactRegex = `^${moduleArtifactRegex}$` this.logger.debug(`Looking for artifact matching ${moduleArtifactRegex}`); const packageFiles = await this.getArtifactsForRevision(revision, { From 26c7da06fa3bff51adc46bf1e28bb7d26c5b1fd4 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 27 Feb 2024 13:24:32 +0100 Subject: [PATCH 10/20] fixup artifact regex --- src/targets/powershell.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/targets/powershell.ts b/src/targets/powershell.ts index b212897c..6ec43b70 100644 --- a/src/targets/powershell.ts +++ b/src/targets/powershell.ts @@ -81,7 +81,7 @@ export class PowerShellTarget extends BaseTarget { // Escape the given module artifact name to avoid regex issues. let moduleArtifactRegex = `${this.psConfig.module}`.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&'); - moduleArtifactRegex = `^${moduleArtifactRegex}$` + moduleArtifactRegex = `^${moduleArtifactRegex}\\.zip$` this.logger.debug(`Looking for artifact matching ${moduleArtifactRegex}`); const packageFiles = await this.getArtifactsForRevision(revision, { From c5cecaabccf7e323a128a7ddc0f3b952bb724566 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 27 Feb 2024 13:30:19 +0100 Subject: [PATCH 11/20] fixup target registration --- src/targets/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/targets/index.ts b/src/targets/index.ts index 86e6461a..6ca249d1 100644 --- a/src/targets/index.ts +++ b/src/targets/index.ts @@ -19,6 +19,7 @@ import { SymbolCollector } from './symbolCollector'; import { PubDevTarget } from './pubDev'; import { HexTarget } from './hex'; import { CommitOnGitRepositoryTarget } from './commitOnGitRepository'; +import { PowerShellTarget } from './powershell'; export const TARGET_MAP: { [key: string]: typeof BaseTarget } = { brew: BrewTarget, @@ -41,6 +42,7 @@ export const TARGET_MAP: { [key: string]: typeof BaseTarget } = { 'pub-dev': PubDevTarget, hex: HexTarget, 'commit-on-git-repository': CommitOnGitRepositoryTarget, + powershell: PowerShellTarget, }; /** Targets that are treated specially */ From 8e851c23fa416b50cc077b21ece282ac59be29bf Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Tue, 27 Feb 2024 19:53:29 +0100 Subject: [PATCH 12/20] powershell target fixes --- src/targets/powershell.ts | 46 ++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/src/targets/powershell.ts b/src/targets/powershell.ts index 6ec43b70..8ffc68d1 100644 --- a/src/targets/powershell.ts +++ b/src/targets/powershell.ts @@ -1,11 +1,11 @@ -import { basename, join } from 'path'; +import { join } from 'path'; import { BaseArtifactProvider } from '../artifact_providers/base'; import { TargetConfig } from '../schemas/project_config'; import { ConfigurationError, reportError } from '../utils/errors'; import { withTempDir } from '../utils/files'; -import { checkExecutableIsPresent, extractZipArchive, spawnProcess } from '../utils/system'; +import { isDryRun } from '../utils/helpers'; +import { SpawnProcessOptions, checkExecutableIsPresent, extractZipArchive, spawnProcess } from '../utils/system'; import { BaseTarget } from './base'; -import { isDryRun } from 'src/utils/helpers'; /** Command to launch PowerShell */ export const POWERSHELL_BIN = process.env.POWERSHELL_BIN || 'pwsh'; @@ -60,8 +60,13 @@ export class PowerShellTarget extends BaseTarget { /** * Executes a PowerShell command. */ - private async spawnPwsh(command: string): Promise { - return spawnProcess(POWERSHELL_BIN, ['-Command', command]); + private async spawnPwsh( + command: string, + spawnProcessOptions: SpawnProcessOptions = {} + ): Promise { + command = `$ErrorActionPreference = 'Stop'\n` + command; + this.logger.trace("Executing PowerShell command:", command); + return spawnProcess(POWERSHELL_BIN, ['-Command', command], {}, spawnProcessOptions); } /** @@ -71,17 +76,23 @@ export class PowerShellTarget extends BaseTarget { * @param revision Git commit SHA to be published */ public async publish(_version: string, revision: string): Promise { + const defaultSpawnOptions = { enableInDryRunMode: true, showStdout: true } // Emit the PowerShell executable for informational purposes. this.logger.info(`PowerShell (${POWERSHELL_BIN}) info:`); - await spawnProcess(POWERSHELL_BIN, ['--version']); + await spawnProcess(POWERSHELL_BIN, ['--version'], {}, defaultSpawnOptions); // Also check the command and its its module version in case there are issues: this.logger.info('Publish-Module command info:'); - await this.spawnPwsh('Get-Command -Name Publish-Module'); + await this.spawnPwsh(` + $info = Get-Command -Name Publish-Module + "Module name: $($info.ModuleName)" + "Module version: $($info.Module.Version)" + "Module path: $($info.Module.Path)" + `, defaultSpawnOptions); // Escape the given module artifact name to avoid regex issues. let moduleArtifactRegex = `${this.psConfig.module}`.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&'); - moduleArtifactRegex = `^${moduleArtifactRegex}\\.zip$` + moduleArtifactRegex = `/^${moduleArtifactRegex}\\.zip$/` this.logger.debug(`Looking for artifact matching ${moduleArtifactRegex}`); const packageFiles = await this.getArtifactsForRevision(revision, { @@ -101,17 +112,16 @@ export class PowerShellTarget extends BaseTarget { this.logger.info(`Extracting artifact "${artifact.filename}"`) await withTempDir(async dir => { - await extractZipArchive(zipPath, dir); - // All artifacts downloaded from GitHub are ZIP files. - const pkgName = basename(artifact.filename, '.zip'); - const distDir = join(dir, pkgName); + const moduleDir = join(dir, this.psConfig.module); + await extractZipArchive(zipPath, moduleDir); - await this.spawnPwsh('Publish-Module' + - ` -Name '${this.psConfig.module}'` + - ` -Path '${distDir}'` + - ` -Repository ${this.psConfig.repository}` + - ` -NuGetApiKey ${this.psConfig.apiKey}` + - (isDryRun() ? ' -WhatIf' : '')) + this.logger.info(`Publishing PowerShell module "${this.psConfig.module}" to ${this.psConfig.repository}`) + await this.spawnPwsh(` + Publish-Module -Path '${moduleDir}' \` + -Repository ${this.psConfig.repository} \` + -NuGetApiKey ${this.psConfig.apiKey} \` + -WhatIf:$${isDryRun()} + `, defaultSpawnOptions) }); this.logger.info(`PowerShell module upload complete: $`); From 55f9ab31c6ee801984e28d800ae567b1b6d2a1ec Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 28 Feb 2024 17:45:27 +0100 Subject: [PATCH 13/20] Revert "add missing gem cleanup" This reverts commit 66ddde55093bd765e7f37b5b7d77772b6ce5356a. --- Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d0a2e2ef..d2a78d78 100644 --- a/Dockerfile +++ b/Dockerfile @@ -65,7 +65,6 @@ RUN curl -fsSL https://packages.microsoft.com/config/debian/10/packages-microsof && cargo --version \ && cargo install cargo-hack \ && gem install -g --no-document \ - && gem cleanup all \ # Install https://github.com/getsentry/symbol-collector && symbol_collector_url=$(curl -s https://api.github.com/repos/getsentry/symbol-collector/releases/tags/1.12.0 | \ jq -r '.assets[].browser_download_url | select(endswith("symbolcollector-console-linux-x64.zip"))') \ From 09630654fd6564218631714a4e600bc7b8a12399 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 28 Feb 2024 17:45:37 +0100 Subject: [PATCH 14/20] Revert "fix: version test for equal pre-release versions" This reverts commit 2613b71a75c7d25ed7257190a1601322f480eac5. --- src/utils/__tests__/version.test.ts | 19 ------------------- src/utils/version.ts | 7 +++---- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/src/utils/__tests__/version.test.ts b/src/utils/__tests__/version.test.ts index 76020deb..c3922d84 100644 --- a/src/utils/__tests__/version.test.ts +++ b/src/utils/__tests__/version.test.ts @@ -195,25 +195,6 @@ describe('versionGreaterOrEqualThan', () => { expect(() => versionGreaterOrEqualThan(v1, v2)).toThrowError(); expect(() => versionGreaterOrEqualThan(v2, v1)).toThrowError(); }); - - test('can compare pre parts that are the same', () => { - let v1 = parseVersion('1.2.3-dev.0')!; - let v2 = parseVersion('1.2.3-dev.0')!; - expect(versionGreaterOrEqualThan(v1, v2)).toBe(true); - expect(versionGreaterOrEqualThan(v2, v1)).toBe(true); - - v1 = parseVersion('1.2.3-dev.0+A')!; - v2 = parseVersion('1.2.3-dev.0+A')!; - expect(versionGreaterOrEqualThan(v1, v2)).toBe(true); - expect(versionGreaterOrEqualThan(v2, v1)).toBe(true); - }); - - test('can compare pre parts that are the same but have different builds', () => { - const v1 = parseVersion('1.2.3-dev.0+buildA')!; - const v2 = parseVersion('1.2.3-dev.0+buildB')!; - expect(versionGreaterOrEqualThan(v1, v2)).toBe(false); - expect(versionGreaterOrEqualThan(v2, v1)).toBe(false); - }); }); describe('getPackage', () => { diff --git a/src/utils/version.ts b/src/utils/version.ts index 853e5d8b..5ab3581a 100644 --- a/src/utils/version.ts +++ b/src/utils/version.ts @@ -89,8 +89,6 @@ export function versionGreaterOrEqualThan(v1: SemVer, v2: SemVer): boolean { return true; } else if (v1.pre && !v2.pre) { return false; - } else if (v1.pre && v2.pre && v1.pre === v2.pre) { - return v1.build === v2.build; } else if (v1.pre && v2.pre && v1.pre !== v2.pre && /^\d+$/.test(v1.pre) && /^\d+$/.test(v2.pre)) { return v1.pre > v2.pre; } else if (v1.build || v2.build || v1.pre || v2.pre) { @@ -159,6 +157,7 @@ export function getPackageVersion(): string { * Returns the stringified version of the passed SemVer object. */ export function semVerToString(s: SemVer) { - return `${s.major}.${s.minor}.${s.patch}${s.pre ? `-${s.pre}` : ''}${s.build ? `+${s.build}` : '' - }`; + return `${s.major}.${s.minor}.${s.patch}${s.pre ? `-${s.pre}` : ''}${ + s.build ? `+${s.build}` : '' + }`; } From 1a16bdcac4ec94163fa31b94911bc0a29a0edc2d Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 28 Feb 2024 17:46:46 +0100 Subject: [PATCH 15/20] Revert "autofixed settings.json" This reverts commit 99814ddca82f9064bd71b0be9003b6019c8e7464. --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index eb7186a6..4d84ab76 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "editor.codeActionsOnSave": { - "source.organizeImports": "never" + "source.organizeImports": false }, "editor.formatOnType": true, "editor.formatOnSave": true, From f562d55298a83dfe4557a33a2538bf7000d1e949 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Wed, 28 Feb 2024 20:59:54 +0100 Subject: [PATCH 16/20] some tests for the pwsh target --- src/targets/__tests__/powershell.test.ts | 127 +++++++++++++++++++++++ src/targets/powershell.ts | 54 ++++++---- 2 files changed, 160 insertions(+), 21 deletions(-) create mode 100644 src/targets/__tests__/powershell.test.ts diff --git a/src/targets/__tests__/powershell.test.ts b/src/targets/__tests__/powershell.test.ts new file mode 100644 index 00000000..89c6c768 --- /dev/null +++ b/src/targets/__tests__/powershell.test.ts @@ -0,0 +1,127 @@ +import { NoneArtifactProvider } from '../../artifact_providers/none'; +import { ConfigurationError } from '../../utils/errors'; +import { PowerShellTarget } from '../powershell'; + +jest.mock('fs'); + +/** Returns a new PowerShellTarget test instance. */ +function getPwshTarget(): PowerShellTarget { + return new PowerShellTarget( + { + name: 'powershell', + module: 'moduleName', + repository: 'repositoryName', + }, + new NoneArtifactProvider() + ); +} + +function setPwshEnvironmentVariables() { + process.env.POWERSHELL_API_KEY = 'test access key'; +} + +describe('pwsh environment variables', () => { + const oldEnvVariables = process.env; + + beforeEach(() => { + jest.resetModules(); // Clear the cache. + process.env = { ...oldEnvVariables }; // Restore environment + }); + + afterAll(() => { + process.env = { ...oldEnvVariables }; // Restore environment + }); + + function deleteTargetOptionsFromEnvironment() { + if ('POWERSHELL_API_KEY' in process.env) { + delete process.env.POWERSHELL_API_KEY; + } + } + + test('errors on missing environment variables', () => { + deleteTargetOptionsFromEnvironment(); + try { + getPwshTarget(); + } catch (e) { + expect(e instanceof ConfigurationError).toBe(true); + } + }); + + test('success on environment variables', () => { + deleteTargetOptionsFromEnvironment(); + setPwshEnvironmentVariables(); + // AwsLambdaTarget needs the environment variables to initialize. + getPwshTarget(); + }); +}); + +describe('config', () => { + function clearConfig(target: PowerShellTarget): void { + target.psConfig.apiKey = ''; + target.psConfig.repository = ''; + target.psConfig.module = ''; + } + + test('fails with missing config parameters', async () => { + const target = getPwshTarget(); + clearConfig(target); + try { + await target.publish('', ''); + } catch (error) { + expect(error).toBeInstanceOf(ConfigurationError); + expect(error.message).toBe( + 'Missing project configuration parameter(s): apiKey,repository,module'); + } + }); +}); + +describe('publish', () => { + beforeAll(() => { + setPwshEnvironmentVariables(); + }); + + const noArtifactsForRevision = jest.fn().mockImplementation(function () { + return []; + }); + + test('error on missing artifact', async () => { + const target = getPwshTarget(); + target.getArtifactsForRevision = noArtifactsForRevision.bind( + PowerShellTarget + ); + // `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 noPackageFound = await target.publish('version', 'revision'); + expect(noPackageFound).toBe(undefined); + } catch (error) { + expect(error).toBeInstanceOf(Error); + expect(error.message).toMatch(/there are no matching artifacts/); + } + }); + + const twoArtifactsForRevision = jest.fn().mockImplementation(function () { + return ['file1', 'file2']; + }); + + test('error on having too many artifacts', async () => { + const target = getPwshTarget(); + target.getArtifactsForRevision = twoArtifactsForRevision.bind( + PowerShellTarget + ); + // `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 target.publish( + 'version', + 'revision' + ); + expect(multiplePackagesFound).toBe(undefined); + } catch (error) { + expect(error).toBeInstanceOf(Error); + expect(error.message).toMatch(/found multiple matching artifacts/); + } + }); +}); diff --git a/src/targets/powershell.ts b/src/targets/powershell.ts index 8ffc68d1..8e89c34e 100644 --- a/src/targets/powershell.ts +++ b/src/targets/powershell.ts @@ -37,26 +37,14 @@ export class PowerShellTarget extends BaseTarget { artifactProvider: BaseArtifactProvider ) { super(config, artifactProvider); - this.psConfig = this.getPowerShellConfig(); - checkExecutableIsPresent(POWERSHELL_BIN); - } - - /** - * Extracts target options from the raw configuration - */ - protected getPowerShellConfig(): PowerShellTargetOptions { - if (!process.env.POWERSHELL_API_KEY) { - throw new ConfigurationError( - `Cannot perform PowerShell release: missing credentials. - Please use POWERSHELL_API_KEY environment variable.` - ); - } - return { - apiKey: process.env.POWERSHELL_API_KEY, + this.psConfig = { + apiKey: process.env.POWERSHELL_API_KEY || '', repository: this.config.repository || DEFAULT_POWERSHELL_REPOSITORY, - module: this.config.module, + module: this.config.module || '', }; + checkExecutableIsPresent(POWERSHELL_BIN); } + /** * Executes a PowerShell command. */ @@ -70,12 +58,36 @@ export class PowerShellTarget extends BaseTarget { } /** - * Publishes a package tarball to the PowerShell repository - * - * @param version New version to be released - * @param revision Git commit SHA to be published + * Checks if the required project configuration parameters are available. + * The required parameters are `layerName` and `compatibleRuntimes`. + * There is also an optional parameter `includeNames`. + */ + private checkProjectConfig(): void { + const missingConfigOptions = []; + if (this.psConfig.apiKey.length === 0) { + missingConfigOptions.push('apiKey'); + } + if (this.psConfig.repository.length === 0) { + missingConfigOptions.push('repository'); + } + if (this.psConfig.module.length === 0) { + missingConfigOptions.push('module'); + } + if (missingConfigOptions.length > 0) { + throw new ConfigurationError( + 'Missing project configuration parameter(s): ' + missingConfigOptions + ); + } + } + + /** + * Publishes a module to a PowerShell repository. + * @param _version ignored; the version must be set in the module manifest. + * @param revision Git commit SHA to be published. */ public async publish(_version: string, revision: string): Promise { + this.checkProjectConfig(); + const defaultSpawnOptions = { enableInDryRunMode: true, showStdout: true } // Emit the PowerShell executable for informational purposes. this.logger.info(`PowerShell (${POWERSHELL_BIN}) info:`); From 8951fe7e5372fd7d30fee962e480360c31cf60d7 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Thu, 29 Feb 2024 08:11:52 +0100 Subject: [PATCH 17/20] fixup tests --- src/targets/__tests__/powershell.test.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/targets/__tests__/powershell.test.ts b/src/targets/__tests__/powershell.test.ts index 89c6c768..a03cb3cf 100644 --- a/src/targets/__tests__/powershell.test.ts +++ b/src/targets/__tests__/powershell.test.ts @@ -99,17 +99,13 @@ describe('publish', () => { expect(error).toBeInstanceOf(Error); expect(error.message).toMatch(/there are no matching artifacts/); } - }); - - const twoArtifactsForRevision = jest.fn().mockImplementation(function () { - return ['file1', 'file2']; - }); + }, 10_000); test('error on having too many artifacts', async () => { const target = getPwshTarget(); - target.getArtifactsForRevision = twoArtifactsForRevision.bind( - PowerShellTarget - ); + target.getArtifactsForRevision = jest.fn() + .mockImplementation(() => ['file1', 'file2']).bind(PowerShellTarget); + // `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. @@ -123,5 +119,5 @@ describe('publish', () => { expect(error).toBeInstanceOf(Error); expect(error.message).toMatch(/found multiple matching artifacts/); } - }); + }, 10_000); }); From 5be6c7eb0e585fb82cf9d7350337e73f7e28d961 Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Thu, 29 Feb 2024 09:18:53 +0100 Subject: [PATCH 18/20] more pwsh tests --- src/targets/__tests__/powershell.test.ts | 52 ++++++++++++++++++++++-- src/targets/powershell.ts | 27 ++++++------ 2 files changed, 64 insertions(+), 15 deletions(-) diff --git a/src/targets/__tests__/powershell.test.ts b/src/targets/__tests__/powershell.test.ts index a03cb3cf..1ee916cd 100644 --- a/src/targets/__tests__/powershell.test.ts +++ b/src/targets/__tests__/powershell.test.ts @@ -1,8 +1,10 @@ +import { spawnProcess } from '../../utils/system'; import { NoneArtifactProvider } from '../../artifact_providers/none'; import { ConfigurationError } from '../../utils/errors'; import { PowerShellTarget } from '../powershell'; jest.mock('fs'); +jest.mock('../../utils/system'); /** Returns a new PowerShellTarget test instance. */ function getPwshTarget(): PowerShellTarget { @@ -76,8 +78,14 @@ describe('config', () => { }); describe('publish', () => { - beforeAll(() => { + const mockedSpawnProcess = spawnProcess as jest.Mock; + const spawnOptions = { enableInDryRunMode: true, showStdout: true } + // const getArtifactsForRevision = jest.fn() + // .mockImplementation(() => ['moduleName.zip']).bind(PowerShellTarget) + + beforeEach(() => { setPwshEnvironmentVariables(); + jest.clearAllMocks(); }); const noArtifactsForRevision = jest.fn().mockImplementation(function () { @@ -99,7 +107,7 @@ describe('publish', () => { expect(error).toBeInstanceOf(Error); expect(error.message).toMatch(/there are no matching artifacts/); } - }, 10_000); + }); test('error on having too many artifacts', async () => { const target = getPwshTarget(); @@ -119,5 +127,43 @@ describe('publish', () => { expect(error).toBeInstanceOf(Error); expect(error.message).toMatch(/found multiple matching artifacts/); } - }, 10_000); + }); + + test('prints pwsh info', async () => { + const target = getPwshTarget(); + try { + await target.publish('1.0', 'sha'); + } catch (error) { + expect(error).toBeInstanceOf(Error); + expect(error.message).toMatch(/there are no matching artifact/); + } + expect(mockedSpawnProcess).toBeCalledWith('pwsh', ['--version'], {}, spawnOptions); + expect(mockedSpawnProcess).toBeCalledWith('pwsh', + [ + '-Command', + `$ErrorActionPreference = 'Stop' + + $info = Get-Command -Name Publish-Module + \"Module name: $($info.ModuleName)\" + \"Module version: $($info.Module.Version)\" + \"Module path: $($info.Module.Path)\" + ` + ], {}, spawnOptions); + }); + + test('publish-module runs with expected args', async () => { + const target = getPwshTarget(); + await target.publishModule('/path/to/module'); + expect(mockedSpawnProcess).toBeCalledWith('pwsh', + [ + '-Command', + `$ErrorActionPreference = 'Stop' + + Publish-Module -Path '/path/to/module' \` + -Repository 'repositoryName' \` + -NuGetApiKey 'test access key' \` + -WhatIf:$false + ` + ], {}, spawnOptions); + }); }); diff --git a/src/targets/powershell.ts b/src/targets/powershell.ts index 8e89c34e..c7606bf8 100644 --- a/src/targets/powershell.ts +++ b/src/targets/powershell.ts @@ -31,6 +31,7 @@ export class PowerShellTarget extends BaseTarget { public readonly name: string = 'powershell'; /** Target options */ public readonly psConfig: PowerShellTargetOptions; + private readonly defaultSpawnOptions = { enableInDryRunMode: true, showStdout: true } public constructor( config: TargetConfig, @@ -50,7 +51,7 @@ export class PowerShellTarget extends BaseTarget { */ private async spawnPwsh( command: string, - spawnProcessOptions: SpawnProcessOptions = {} + spawnProcessOptions: SpawnProcessOptions = this.defaultSpawnOptions ): Promise { command = `$ErrorActionPreference = 'Stop'\n` + command; this.logger.trace("Executing PowerShell command:", command); @@ -88,10 +89,9 @@ export class PowerShellTarget extends BaseTarget { public async publish(_version: string, revision: string): Promise { this.checkProjectConfig(); - const defaultSpawnOptions = { enableInDryRunMode: true, showStdout: true } // Emit the PowerShell executable for informational purposes. this.logger.info(`PowerShell (${POWERSHELL_BIN}) info:`); - await spawnProcess(POWERSHELL_BIN, ['--version'], {}, defaultSpawnOptions); + await spawnProcess(POWERSHELL_BIN, ['--version'], {}, this.defaultSpawnOptions); // Also check the command and its its module version in case there are issues: this.logger.info('Publish-Module command info:'); @@ -100,7 +100,7 @@ export class PowerShellTarget extends BaseTarget { "Module name: $($info.ModuleName)" "Module version: $($info.Module.Version)" "Module path: $($info.Module.Path)" - `, defaultSpawnOptions); + `); // Escape the given module artifact name to avoid regex issues. let moduleArtifactRegex = `${this.psConfig.module}`.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&'); @@ -126,16 +126,19 @@ export class PowerShellTarget extends BaseTarget { await withTempDir(async dir => { const moduleDir = join(dir, this.psConfig.module); await extractZipArchive(zipPath, moduleDir); + await this.publishModule(moduleDir); + }); + + this.logger.info(`PowerShell module upload complete`); + } - this.logger.info(`Publishing PowerShell module "${this.psConfig.module}" to ${this.psConfig.repository}`) - await this.spawnPwsh(` + public async publishModule(moduleDir: string): Promise { + this.logger.info(`Publishing PowerShell module "${this.psConfig.module}" to ${this.psConfig.repository}`) + await this.spawnPwsh(` Publish-Module -Path '${moduleDir}' \` - -Repository ${this.psConfig.repository} \` - -NuGetApiKey ${this.psConfig.apiKey} \` + -Repository '${this.psConfig.repository}' \` + -NuGetApiKey '${this.psConfig.apiKey}' \` -WhatIf:$${isDryRun()} - `, defaultSpawnOptions) - }); - - this.logger.info(`PowerShell module upload complete: $`); + `); } } From c907de343e714641243ef5835e3c9146bb1b266b Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Thu, 29 Feb 2024 09:25:00 +0100 Subject: [PATCH 19/20] linter issues --- src/targets/__tests__/powershell.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/targets/__tests__/powershell.test.ts b/src/targets/__tests__/powershell.test.ts index 1ee916cd..6d70f35c 100644 --- a/src/targets/__tests__/powershell.test.ts +++ b/src/targets/__tests__/powershell.test.ts @@ -144,9 +144,9 @@ describe('publish', () => { `$ErrorActionPreference = 'Stop' $info = Get-Command -Name Publish-Module - \"Module name: $($info.ModuleName)\" - \"Module version: $($info.Module.Version)\" - \"Module path: $($info.Module.Path)\" + "Module name: $($info.ModuleName)" + "Module version: $($info.Module.Version)" + "Module path: $($info.Module.Path)" ` ], {}, spawnOptions); }); From 39cb20980a527667ba970c3bd0f72a613be8b4ae Mon Sep 17 00:00:00 2001 From: Ivan Dlugos Date: Thu, 29 Feb 2024 19:42:59 +0100 Subject: [PATCH 20/20] cleanup --- src/targets/__tests__/powershell.test.ts | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/targets/__tests__/powershell.test.ts b/src/targets/__tests__/powershell.test.ts index 6d70f35c..4ffa4676 100644 --- a/src/targets/__tests__/powershell.test.ts +++ b/src/targets/__tests__/powershell.test.ts @@ -52,7 +52,6 @@ describe('pwsh environment variables', () => { test('success on environment variables', () => { deleteTargetOptionsFromEnvironment(); setPwshEnvironmentVariables(); - // AwsLambdaTarget needs the environment variables to initialize. getPwshTarget(); }); }); @@ -80,23 +79,18 @@ describe('config', () => { describe('publish', () => { const mockedSpawnProcess = spawnProcess as jest.Mock; const spawnOptions = { enableInDryRunMode: true, showStdout: true } - // const getArtifactsForRevision = jest.fn() - // .mockImplementation(() => ['moduleName.zip']).bind(PowerShellTarget) beforeEach(() => { setPwshEnvironmentVariables(); jest.clearAllMocks(); }); - const noArtifactsForRevision = jest.fn().mockImplementation(function () { - return []; - }); test('error on missing artifact', async () => { const target = getPwshTarget(); - target.getArtifactsForRevision = noArtifactsForRevision.bind( - PowerShellTarget - ); + target.getArtifactsForRevision = jest.fn() + .mockImplementation(() => []).bind(PowerShellTarget); + // `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. @@ -118,11 +112,7 @@ describe('publish', () => { // 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 target.publish( - 'version', - 'revision' - ); - expect(multiplePackagesFound).toBe(undefined); + await target.publish('1.0', 'sha'); } catch (error) { expect(error).toBeInstanceOf(Error); expect(error.message).toMatch(/found multiple matching artifacts/);