From ebc0ecfdaa1d4883388f6e6f577b5c88a0b7226e Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Fri, 23 Jan 2026 19:55:30 -0800 Subject: [PATCH 1/3] Add Windows Installer --- .gitattributes | 1 + .github/workflows/pyinstaller.yaml | 61 ++++++++++- .gitignore | 9 +- assets/post-install.rtf | Bin 0 -> 1459 bytes assets/pre-install.rtf | Bin 0 -> 1412 bytes installer.iss | 168 +++++++++++++++++++++++++++++ pyproject.toml | 8 +- 7 files changed, 241 insertions(+), 6 deletions(-) create mode 100644 assets/post-install.rtf create mode 100644 assets/pre-install.rtf create mode 100644 installer.iss diff --git a/.gitattributes b/.gitattributes index bf02a51..3623482 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ docs/** linguist-documentation +*.iss linguist-vendored diff --git a/.github/workflows/pyinstaller.yaml b/.github/workflows/pyinstaller.yaml index 4790567..2ff3708 100644 --- a/.github/workflows/pyinstaller.yaml +++ b/.github/workflows/pyinstaller.yaml @@ -114,11 +114,70 @@ jobs: run: | cat win-version.txt - - name: "Build" + # BEGIN - Windows Installer + + - name: "Windows Installer - Build" + if: ${{ matrix.os == 'windows-latest' }} shell: bash run: | uvx toml-run pyinstaller -- ${{ matrix.extra-args }} + - name: "Windows Installer - Download PathMgr" + if: ${{ matrix.os == 'windows-latest' }} + uses: robinraju/release-downloader@daf26c55d821e836577a15f77d86ddc078948b05 # v1.12 + with: + repository: "Bill-Stewart/PathMgr" + fileName: "PathMgr-2.0.0.zip" + out-file-path: "dist/PathMgr" + tag: "v2.0.0" + extract: true + + - name: "Windows Installer - Inno Setup" + env: + version: ${{ inputs.version }} + shell: pwsh + run: | + echo "Building Version: $Env:version" + iscc.exe "/DMyAppVersion=$Env:version" installer.iss + + - name: "Windows Installer - Debug Output" + if: ${{ matrix.os == 'windows-latest' }} + continue-on-error: true + shell: bash + run: | + echo "::group::dist" + ls -lAh dist/* + echo "::endgroup::" + echo "::group::out" + ls -lAhR out + echo "::endgroup::" + + - name: "Windows Installer - Upload Artifact" + if: ${{ matrix.os == 'windows-latest' && github.event_name != 'release' }} + uses: actions/upload-artifact@v5 + with: + name: windows-installer + path: out/*.exe + + - name: "Windows Installer - Upload Release" + if: ${{ matrix.os == 'windows-latest' && github.event_name == 'release' }} + uses: cssnr/upload-release-action@latest + with: + globs: out/*.exe + + - name: "Windows Installer - Cleanup" + if: ${{ matrix.os == 'windows-latest' }} + shell: bash + run: | + rm -rf dist out + + # END - Windows Installer + + - name: "Build" + shell: bash + run: | + uvx toml-run pyinstaller -- -F ${{ matrix.extra-args }} + - name: "List Artifacts" continue-on-error: true working-directory: dist diff --git a/.gitignore b/.gitignore index 4039fdc..f8e497f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,14 +2,15 @@ .idea/ *.iml .vscode/ +.*cache* +node_modules/ +build/ +dist/ +out/ .venv/ venv/ __pycache__/ *.egg-info/ -build/ -dist/ -.*cache/ -*.log *.pyc .coverage coverage.xml diff --git a/assets/post-install.rtf b/assets/post-install.rtf new file mode 100644 index 0000000000000000000000000000000000000000..d6ff4181ba543b7a67b41f4a536ddb2b059ea216 GIT binary patch literal 1459 zcmbtU+fLjt5bZM(|1jbaX|sv9EVm~Ft=JYJ1hfUoFOEG)EF3$v9WILY-#f`JXho|n zf|at0W-@cmoHKUEeJnCAY|z7>Y|4!7Fs`sDQf^(VrK=ke`+yO)%+krE=wRyB#Ek!zT+(zoFT!NXdsX&fWTX>NbsHjhBvL+tuutF`Y827e+6n`K+G(-_3X&rd`o+=pDUgY_!~oGj6n6-;11RGX zhukZ4*?N4Y8PMOOUuK`DGkTYiUjI;y*NqmfNMg2(|F^E&4Y2Jl88ug`b#XC|;M{^T_6ieD@4}uc#-{TkN}RI!Vj=` zt;Ra&6TC-5C&j}r%!7^}!Bsj)=T%ac%aZnwz7=VtsASgfq@#3OHh3$(*%bDVwv}^s3z3 zd+J1qkM2a>Vy&{u^XNQWYtboL=1}CLd{pe02L}uGpw;xJwsM#CqJZ@^HsM~fz{@;m z!IZmX$GwRK`z2VN_87{N8E)GHfC?L0c-fMl0tvE2;EkW`IrV2=)U(n^lD)bfAmaxHu7$rEbNh6d#hCLhmcG^r1gP z7LjpE>XN0&ib>8Y8&Y5(5!)3_kV5_{!=Bh_iVF*%}W{0k@qFFxAF@ zwjpxtW2fK-&yu=>lgo49R)U*E?JNdoz@-EoAv&13-ow@L?b&E4^}Hb$h|am7z`6t? zI4wxH?KGk_Q$9k}3it!phSx@+H+HU%5GG3KW~ObM2u+Wo@XU9pFgc>~V~uJ6Pgie^ z3IWF4_J8Fm%;+AIuEepXgkvf7av$JTK=nd}!=-kr#172yzG!9SoNckjNGizu?EdQW z_2v233)tvloP@(H6CqeXYEf-pkF?7I73=qoFgn{<(48~V2ZMLCH%#CK6itakxK?~w zi&H21v9ghqm<6_UOVjz*R{g(@l>&SbmXe+bc}>SRynCrUH>m{pTuAi>70;{jTIy5v zpp2U+58qWCbbL!*se^D{j@mYx-p%&M<=v<0{cFj;I6Oan0B7NJ$^B>7>50JC>)jv5 CEZ@)o literal 0 HcmV?d00001 diff --git a/installer.iss b/installer.iss new file mode 100644 index 0000000..bbe0b10 --- /dev/null +++ b/installer.iss @@ -0,0 +1,168 @@ +#define MyAppName "ShareX CLI" +#define MyAppPublisher "CSSNR" +#define MyAppURL "https://cssnr.github.io/sharex-cli/" +#define MyAppExeName "sharex.exe" +#ifndef MyAppVersion + #define MyAppVersion "0.0.1" +#endif + +[Setup] +AppId={{1FD9B94A-D2D2-40FA-81D5-A4B5BCD39A47} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +AppVerName={#MyAppName} {#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppPublisherURL={#MyAppURL} +AppSupportURL={#MyAppURL} +AppUpdatesURL={#MyAppURL} +;Compression=lzma +;SolidCompression=yes +DefaultDirName={autopf}\sharex-cli +DefaultGroupName={#MyAppName} +;DisableDirPage=yes +;DisableProgramGroupPage=yes +DisableFinishedPage=yes +InfoBeforeFile=assets\pre-install.rtf +InfoAfterFile=assets\post-install.rtf + +OutputBaseFilename=windows-installer +OutputDir=out +PrivilegesRequired=lowest +PrivilegesRequiredOverridesAllowed=dialog +SetupIconFile=docs\favicon.ico +UninstallDisplayIcon={uninstallexe} +WizardStyle=modern dynamic + +ChangesEnvironment=yes +;LicenseFile=LICENSE +;VersionInfoVersion={#MyAppVersion} + +; "ArchitecturesAllowed=x64compatible" specifies that Setup cannot run +; on anything but x64 and Windows 11 on Arm. +;ArchitecturesAllowed=x64compatible +; "ArchitecturesInstallIn64BitMode=x64compatible" requests that the +; install be done in "64-bit mode" on x64 or Windows 11 on Arm, +; meaning it should use the native 64-bit Program Files directory and +; the 64-bit view of the registry. +ArchitecturesInstallIn64BitMode=x64compatible + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Files] +Source: "dist\PathMgr\i386\PathMgr.dll"; DestDir: "{app}"; Flags: uninsneveruninstall +;Source: "dist\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion +Source: "dist\sharex\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs + +[Icons] +Name: "{group}\{#MyAppName} Folder"; Filename: "{app}" +Name: "{group}\{#MyAppName} Documentation"; Filename: "{#MyAppURL}" +Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}" + +[Tasks] +Name: modifypath; Description: "&Add to Path" + +[Code] +const + MODIFY_PATH_TASK_NAME = 'modifypath'; // Specify name of task + +var + PathIsModified: Boolean; // Cache task selection from previous installs + ApplicationUninstalled: Boolean; // Has application been uninstalled? + +// Import AddDirToPath() at setup time ('files:' prefix) +function DLLAddDirToPath(DirName: string; PathType, AddType: DWORD): DWORD; + external 'AddDirToPath@files:PathMgr.dll stdcall setuponly'; + +// Import RemoveDirFromPath() at uninstall time ('{app}\' prefix) +function DLLRemoveDirFromPath(DirName: string; PathType: DWORD): DWORD; + external 'RemoveDirFromPath@{app}\PathMgr.dll stdcall uninstallonly'; + +// Wrapper for AddDirToPath() DLL function +function AddDirToPath(const DirName: string): DWORD; +var + PathType, AddType: DWORD; +begin + // PathType = 0 - use system Path + // PathType = 1 - use user Path + // AddType = 0 - add to end of Path + // AddType = 1 - add to beginning of Path + if IsAdminInstallMode() then + PathType := 0 + else + PathType := 1; + AddType := 0; + result := DLLAddDirToPath(DirName, PathType, AddType); +end; + +// Wrapper for RemoveDirFromPath() DLL function +function RemoveDirFromPath(const DirName: string): DWORD; +var + PathType: DWORD; +begin + // PathType = 0 - use system Path + // PathType = 1 - use user Path + if IsAdminInstallMode() then + PathType := 0 + else + PathType := 1; + result := DLLRemoveDirFromPath(DirName, PathType); +end; + +procedure RegisterPreviousData(PreviousDataKey: Integer); +begin + // Store previous or current task selection as custom user setting + if PathIsModified or WizardIsTaskSelected(MODIFY_PATH_TASK_NAME) then + SetPreviousData(PreviousDataKey, MODIFY_PATH_TASK_NAME, 'true'); +end; + +function InitializeSetup(): Boolean; +begin + result := true; + // Was task selected during a previous install? + PathIsModified := GetPreviousData(MODIFY_PATH_TASK_NAME, '') = 'true'; +end; + +function InitializeUninstall(): Boolean; +begin + result := true; + // Was task selected during a previous install? + PathIsModified := GetPreviousData(MODIFY_PATH_TASK_NAME, '') = 'true'; + ApplicationUninstalled := false; +end; + +procedure CurStepChanged(CurStep: TSetupStep); +begin + if CurStep = ssPostInstall then + begin + // Add app directory to Path at post-install step if task selected + if PathIsModified or WizardIsTaskSelected(MODIFY_PATH_TASK_NAME) then + AddDirToPath(ExpandConstant('{app}')); + end; +end; + +procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); +begin + if CurUninstallStep = usUninstall then + begin + // Remove app directory from path during uninstall if task was selected; + // use variable because we can't use WizardIsTaskSelected() at uninstall + if PathIsModified then + RemoveDirFromPath(ExpandConstant('{app}')); + end + else if CurUninstallStep = usPostUninstall then + begin + ApplicationUninstalled := true; + end; +end; + +procedure DeinitializeUninstall(); +begin + if ApplicationUninstalled then + begin + // Unload and delete PathMgr.dll and remove app dir when uninstalling + UnloadDLL(ExpandConstant('{app}\PathMgr.dll')); + DeleteFile(ExpandConstant('{app}\PathMgr.dll')); + RemoveDir(ExpandConstant('{app}')); + end; +end; diff --git a/pyproject.toml b/pyproject.toml index 75679e8..07e159d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -123,7 +123,13 @@ select = ["E4", "E7", "E9", "F", "B", "Q"] [tool.scripts] win-version = "uv run pyivf-make_version --source-format yaml --metadata-source .github/files/win-version.yaml --outfile win-version.txt" -pyinstaller = "uv run pyinstaller -F -n sharex -i docs/favicon.ico src/app.py" +pyinstaller = "uv run pyinstaller -n sharex -i docs/favicon.ico src/app.py" +pathmgr = [ + "mkdir dist || echo dist dir exists", + "wget https://github.com/Bill-Stewart/PathMgr/releases/download/v2.0.0/PathMgr-2.0.0.zip -O dist/pathmgr.zip", + "unzip dist/pathmgr.zip -d dist/PathMgr", +] +inno = ["iscc.exe installer.iss"] test = ["uv run coverage run -m pytest", "uv run coverage report -m"] clean = "rm -rf .cache dist site" build = "uv run hatch build" From ea0a6d1e2290e94172548d72e81264535e0cdae0 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Fri, 23 Jan 2026 20:02:47 -0800 Subject: [PATCH 2/3] Add VirusTotal Scan --- .github/workflows/release.yaml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b08dfcc..1d8b674 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -162,3 +162,19 @@ jobs: with: webhook: ${{ secrets.DISCORD_WEBHOOK }} description: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + + virustotal: + name: "VirusTotal Scan" + runs-on: ubuntu-latest + needs: [pyinstaller, release] + timeout-minutes: 5 + if: ${{ github.event_name == 'release' }} + + steps: + - name: "VirusTotal" + uses: cssnr/virustotal-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + vt_api_key: ${{ secrets.VT_API_KEY }} + update_release: true + collapsed: true From 494e47d0d013d07d3de7bb833f9f34925a1d0d1f Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Fri, 23 Jan 2026 20:05:09 -0800 Subject: [PATCH 3/3] Add VirusTotal Scan --- .github/workflows/release.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1d8b674..64d5661 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -173,6 +173,7 @@ jobs: steps: - name: "VirusTotal" uses: cssnr/virustotal-action@master + continue-on-error: true with: github_token: ${{ secrets.GITHUB_TOKEN }} vt_api_key: ${{ secrets.VT_API_KEY }}