Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
32 changes: 30 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,36 @@ jobs:
- name: Build ovm.exe
run: oscript -make src/cmd/ovm.os ovm.exe

- name: Upload ovm.exe to release assets
- name: Extract version from packagedef
id: get_version
run: |
VERSION=$(grep -oP '\.Версия\("\K[^"]+' packagedef || true)
if [ -z "$VERSION" ]; then
echo "Error: failed to extract version from packagedef."
exit 1
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Extracted version: $VERSION"

- name: Create dist directory
run: |
mkdir -p dist
chmod 777 dist

- name: Build Windows installer with Inno Setup
run: |
if [ ! -f "ovm.exe" ]; then
echo "Error: ovm.exe not found in workspace root. Ensure the 'Build ovm.exe' step completed successfully."
exit 1
fi
docker run --rm -i -v "$GITHUB_WORKSPACE:/work" -w /work amake/innosetup \
/DMyAppVersion="${{ steps.get_version.outputs.version }}" \
install/ovm.iss

- name: Upload release assets
uses: AButler/upload-release-assets@v2.0.2
with:
files: ovm.exe
files: |
ovm.exe
dist/ovm-setup.exe
repo-token: ${{ secrets.GITHUB_TOKEN }}
Comment on lines +56 to 62
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

AButler upload-release-assets GitHub Action latest version releases

💡 Result:

The latest release is v3.0 (released Nov 20, 2023). [1][2]

Sources:
[1] AButler/upload-release-assets GitHub repository (Releases).
[2] Upload Release Assets — GitHub Marketplace.


🌐 Web query:

AButler upload-release-assets v3.0 breaking changes migration guide from v2.0.2

💡 Result:

Here’s a concise migration checklist from v2.0.2 → v3.0 for AButler/upload-release-assets, with sources.

  1. Update workflow reference
  • Change uses: AButler/upload-release-assets@v2.0.2 → AButler/upload-release-assets@v3.0.
    Source: README usage. [1]
  1. Node runtime / self-hosted runners
  • v3.0 upgrades to Node 20 (action built for node20). If you run on self-hosted runners, ensure Node 20 is available or use GitHub-hosted runners.
    Source: v3.0 release notes. [2]
  1. New / changed inputs
  • v3.0 explicitly documents/permits release-id to be specified as an input (optional). If you previously relied only on the action-derived release, you can now pass release-id to target a specific release. Confirm any workflows that computed/expected upload_url behavior still work.
    Source: README inputs & v3.0 notes. [1][2]
  1. Dependency / internals changes (non-functional unless you vendor or extend)
  • Dependencies bumped (fast-glob, semver, @actions/core). Expect no functional change, but if you fork/patch the action, update accordingly.
    Source: v3.0 changelog. [2]
  1. Verify file-glob formatting remains the same
  • The action still expects semicolon-separated globs and forward slashes; no change documented but verify uploads in a CI run. [1]
  1. Recommended steps to migrate safely
  • Pin the new major tag in a test branch (uses: AButler/upload-release-assets@v3.0).
  • Run a CI test that uploads to a draft/test release (or specify release-id) to confirm behavior.
  • If using self-hosted runner, install/verify Node 20 before switching.
  • If anything relies on older dependency behavior, test and open an issue on the repo. [1][2]

References

  • README / inputs and usage. [1]
  • v3.0 Release notes (what changed: Node upgrade, dependency bumps, allow release-id). [2]

If you’d like, I can produce a one-line replacement snippet for your workflow and a small test workflow that uploads to a draft release.


Обновите версию GitHub Action на v3.0.

Версия AButler/upload-release-assets@v2.0.2 устарела. Доступна более новая версия v3.0 (выпущена 20 ноября 2023). Обновление совместимо с текущей конфигурацией: формат файлов и синтаксис параметров остаются неизменными. v3.0 требует Node 20 (уже доступен на GitHub-hosted runners). Замените uses: AButler/upload-release-assets@v2.0.2 на uses: AButler/upload-release-assets@v3.0.

🤖 Prompt for AI Agents
In @.github/workflows/release.yml around lines 46 - 52, Update the GitHub Action
reference for the release asset upload step: replace the uses: declaration that
currently reads AButler/upload-release-assets@v2.0.2 with
AButler/upload-release-assets@v3.0 in the "Upload release assets" job; no other
parameter changes are required since v3.0 is compatible with the existing files
and repo-token syntax and GitHub-hosted runners now support the Node 20
requirement.

234 changes: 234 additions & 0 deletions install/ovm.iss
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
; Inno Setup script for OVM (OneScript Version Manager)
; Installs ovm.exe to %LOCALAPPDATA%\ovm and adds it to user PATH

#define MyAppName "OneScript Version Manager"
; MyAppVersion is passed from GitHub Actions via /D flag, extracting from packagedef
; Default value is used for local builds
#ifndef MyAppVersion
#define MyAppVersion "1.0.0"
#endif
#define MyAppPublisher "oscript-library"
#define MyAppURL "https://github.com/oscript-library/ovm"
#define MyAppExeName "ovm.exe"

[Setup]
; NOTE: The value of AppId uniquely identifies this application.
AppId={{8E5F4A2B-9C3D-4F1E-A6B8-2D7C9E4F1A3B}
Comment on lines +14 to +16
Copy link

@coderabbitai coderabbitai bot Jan 9, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Критично: AppId выглядит незакрытым (экранирование {}), инсталлятор может не собраться/получить некорректный AppId.
Сейчас AppId={{...} заканчивается одиночной }, из-за чего легко получить литерал без закрывающей скобки. Лучше явно закрыть GUID как {{GUID}}.

Возможный фикс
 AppId={{8E5F4A2B-9C3D-4F1E-A6B8-2D7C9E4F1A3B}
+; ожидание: литерал "{GUID}" => экранирование "{" и "}" удвоением
+; AppId={{8E5F4A2B-9C3D-4F1E-A6B8-2D7C9E4F1A3B}}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @install/ovm.iss around lines 14 - 16, AppId line contains a
mismatched/unterminated brace ("AppId={{8E5F4A2B-9C3D-4F1E-A6B8-2D7C9E4F1A3B}")
which will produce an incorrect AppId; update the AppId value to properly close
the GUID with double braces (i.e., ensure the token is formatted as {{GUID}}) so
the opening "{{" has a matching "}}", and verify the GUID string inside remains
unchanged and correctly formatted.

Copy link
Member Author

Choose a reason for hiding this comment

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

Это частый паттерн использования appId и экранирования appId. Не ошибка

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🌐 Web query:

Inno Setup AppId GUID curly braces escaping syntax

💡 Result:

Short answer: In [Setup] AppId braces must be doubled in the script because single { } starts an Inno constant. Use {{GUID}} (and }} for the close) or generate the doubled-braces string via the preprocessor. Example:

  • Direct (literal doubled braces):
    AppId={{FDDFBC15-7B5C-4580-AD69-F54B8087A52E}}

  • Using preprocessor to keep a single-brace value for code:
    #define AppId "{FDDFBC15-7B5C-4580-AD69-F54B8087A52E}"
    AppId={#StringChange(AppId, '{', '{{')}

Notes: Pascal Script and registry code expect the single-brace form (e.g. "{GUID}"), so only the [Setup] directive needs doubled braces in the script. (stackoverflow.com)

Citations:


🌐 Web query:

Inno Setup AppId single closing brace versus double closing brace

💡 Result:

Short answer: In [Setup] directives (like AppId) Inno Setup treats "{" as the start of a constant, so you must double the opening brace to get a literal "{". The closing brace does not need doubling. Therefore to get an AppId value of {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} write it in the script as:

AppId={{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}

(That produces a single leading "{" and a single trailing "}" at runtime.) [1][2]

References:

  • Inno Setup AppId documentation. [1]
  • Explanation and example on Stack Overflow. [2]

@EvilBeaver, вы абсолютно правы, приношу извинения за ошибочный комментарий!

Согласно документации Inno Setup, в секции [Setup] только открывающая фигурная скобка требует экранирования удвоением (так как { начинает константу), а закрывающая скобка остаётся одиночной. Поэтому формат AppId={{GUID} корректен и даёт на выходе {GUID}.

Спасибо за разъяснение!


✏️ Learnings added
Learnt from: EvilBeaver
Repo: oscript-library/ovm PR: 51
File: install/ovm.iss:14-16
Timestamp: 2026-01-10T17:56:17.816Z
Learning: В Inno Setup при указании AppId в секции [Setup] только открывающая фигурная скобка должна быть удвоена: `AppId={{GUID}` — это правильный синтаксис, который даёт значение `{GUID}` в рантайме. Закрывающая скобка остаётся одиночной.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={localappdata}\ovm
DisableProgramGroupPage=yes
OutputDir=..\dist
OutputBaseFilename=ovm-setup
Compression=lzma
SolidCompression=yes
; Per-user installation (no admin rights required)
PrivilegesRequired=lowest
PrivilegesRequiredOverridesAllowed=commandline
; Notify Windows about environment changes
ChangesEnvironment=yes
ArchitecturesInstallIn64BitMode=x64
UninstallDisplayIcon={app}\{#MyAppExeName}

[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl"

[Files]
Source: "..\ovm.exe"; DestDir: "{app}"; Flags: ignoreversion
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

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

The source file path uses a relative path '..\ovm.exe' which assumes the script is being run from the install/ directory. The GitHub Actions workflow runs the Inno Setup compiler from the workspace root (/work), so this path should correctly resolve to ovm.exe in the parent directory. However, consider using an absolute path or verifying that this relative path works correctly with the Docker container's working directory.

Suggested change
Source: "..\ovm.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#SourcePath}\..\ovm.exe"; DestDir: "{app}"; Flags: ignoreversion

Copilot uses AI. Check for mistakes.

[Code]
const
EnvironmentKey = 'Environment';

// Удаляет завершающий обратный слеш из пути, если он есть
function NormalizePath(Path: string): string;
begin
Result := Path;
if (Length(Result) > 0) and (Result[Length(Result)] = '\') then
Result := Copy(Result, 1, Length(Result) - 1);
end;

// Проверяет, существует ли путь в переменной PATH
// Учитывает варианты с завершающим слешем и без него
function PathExistsInEnv(Path: string): Boolean;
var
EnvPath: string;
NormalizedPath: string;
SearchIn: string;
begin
Result := False;

// Нормализуем ВХОДНОЙ путь для единообразия
NormalizedPath := NormalizePath(Path);

// Получаем PATH из реестра (НЕ нормализуем — там могут быть пути с backslash)
if not RegQueryStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', EnvPath) then
exit;

// Добавляем разделители по краям для корректного поиска подстроки
SearchIn := ';' + Uppercase(EnvPath) + ';';

// Ищем путь в EnvPath в двух вариантах:
// 1. Без trailing backslash (C:\MyApp)
// 2. С trailing backslash (C:\MyApp\) — на случай, если так записано в реестре
if Pos(';' + Uppercase(NormalizedPath) + ';', SearchIn) > 0 then
Result := True
else if Pos(';' + Uppercase(NormalizedPath) + '\;', SearchIn) > 0 then
Result := True;
end;

// Добавляет путь в переменную окружения PATH
// Возвращает True при успешном добавлении
function AddToPath(Path: string): Boolean;
var
EnvPath: string;
NewPath: string;
NormalizedPath: string;
begin
Result := False;
NormalizedPath := NormalizePath(Path);

// Проверяем, есть ли путь уже в PATH
if PathExistsInEnv(NormalizedPath) then
begin
Log('Путь уже существует в PATH: ' + NormalizedPath);
Result := True;
exit;
end;

// Получаем текущее значение PATH
if RegQueryStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', EnvPath) then
begin
// Добавляем разделитель, если PATH не заканчивается на него
if Length(EnvPath) = 0 then
NewPath := NormalizedPath
else if EnvPath[Length(EnvPath)] = ';' then
NewPath := EnvPath + NormalizedPath
else
NewPath := EnvPath + ';' + NormalizedPath;
end
else
begin
// PATH не существует, создаём новый
NewPath := NormalizedPath;
end;

// Записываем обновлённый PATH в реестр
if RegWriteStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', NewPath) then
begin
Log('Добавлено в PATH: ' + NormalizedPath);
Result := True;
end
else
Log('Ошибка при добавлении в PATH: ' + NormalizedPath);
end;
Comment on lines +85 to +129
Copy link

@coderabbitai coderabbitai bot Jan 10, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Добавьте проверку лимита длины PATH и обработку ошибок для пользователя.

В функции AddToPath отсутствуют следующие важные проверки:

  1. Лимит длины PATH: Windows ограничивает размер переменных окружения (2047 символов в старых версиях, до 32767 в Windows 10+). Добавление пути без проверки может привести к молчаливому сбою или усечению.

  2. Условие гонки (TOCTOU): Между чтением PATH (строка 105) и записью (строка 122) другой процесс может изменить PATH, и эти изменения будут потеряны.

  3. Отсутствие обратной связи с пользователем: Если запись в реестр не удалась (строки 127-128), пользователь увидит только сообщение в логе, но не получит видимого предупреждения.

💡 Рекомендации по улучшению

Добавьте проверку длины PATH перед записью:

// В функции AddToPath, перед строкой 122:
const
  MAX_PATH_LENGTH = 2047; // Консервативный лимит для совместимости

// После строки 119 (перед записью):
if Length(NewPath) > MAX_PATH_LENGTH then
begin
  Log('Ошибка: превышен лимит длины PATH (' + IntToStr(Length(NewPath)) + ' символов)');
  MsgBox('Не удалось добавить путь в PATH: превышен лимит длины переменной окружения.' + #13#10 +
         'Пожалуйста, добавьте путь вручную: ' + NormalizedPath,
         mbError, MB_OK);
  exit;
end;

Добавьте видимое предупреждение при ошибке записи (замените строки 127-128):

else
begin
  Log('Ошибка при добавлении в PATH: ' + NormalizedPath);
  MsgBox('Не удалось автоматически добавить путь в PATH.' + #13#10 +
         'Пожалуйста, добавьте путь вручную: ' + NormalizedPath,
         mbError, MB_OK);
end;
🤖 Prompt for AI Agents
In @install/ovm.iss around lines 85 - 129, In AddToPath: enforce a conservative
PATH length limit and surface errors to the user, and mitigate the TOCTOU race
between RegQueryStringValue and RegWriteStringValue by re-reading and merging
PATH just before write; specifically, add a constant (e.g., MAX_PATH_LENGTH =
2047), after computing NewPath re-read the current PATH via RegQueryStringValue
(use EnvironmentKey and 'Path'), merge NormalizedPath into the freshly read
value only if not present, then check Length(NewPath) > MAX_PATH_LENGTH and if
so Log the length and show MsgBox explaining the failure and exit; after
RegWriteStringValue fails, replace the silent Log with a MsgBox informing the
user to add NormalizedPath manually (keep using PathExistsInEnv, NormalizePath,
RegQueryStringValue, and RegWriteStringValue to locate code).

Copy link
Member Author

Choose a reason for hiding this comment

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

Это приемлемый риск

Copy link

Choose a reason for hiding this comment

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

@EvilBeaver, понял, принял к сведению вашу оценку. Спасибо за ответ!


// Удаляет путь из переменной окружения PATH
// Использует TStringList для парсинга и ручную сборку строки для записи,
// чтобы избежать проблемы с кавычками вокруг путей с пробелами
function RemoveFromPath(Path: string): Boolean;
var
EnvPath: string;
NewPath: string;
NormalizedPath: string;
PathList: TStringList;
PathItem: string;
NormalizedItem: string;
i: Integer;
Removed: Boolean;
begin
Result := False;
Removed := False;
NormalizedPath := NormalizePath(Path);

// Получаем текущее значение PATH
if not RegQueryStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', EnvPath) then
begin
Log('PATH не найден в реестре');
exit;
end;

PathList := TStringList.Create;
try
// Разбираем PATH на элементы
PathList.Delimiter := ';';
PathList.StrictDelimiter := True;
PathList.DelimitedText := EnvPath;

// Удаляем наш путь и пустые элементы
for i := PathList.Count - 1 downto 0 do
begin
PathItem := PathList[i];

// Удаляем пустые элементы (защита от ";;" в PATH)
if Trim(PathItem) = '' then
begin
PathList.Delete(i);
Continue;
end;

// Нормализуем для сравнения (убираем завершающий слеш)
NormalizedItem := NormalizePath(PathItem);

// Сравниваем без учёта регистра
if Uppercase(NormalizedItem) = Uppercase(NormalizedPath) then
begin
Log('Удалено из PATH: ' + PathItem);
PathList.Delete(i);
Removed := True;
end;
end;

// Собираем строку вручную, чтобы избежать добавления кавычек
// (TStringList.DelimitedText добавляет кавычки к путям с пробелами)
NewPath := '';
for i := 0 to PathList.Count - 1 do
begin
if i > 0 then
NewPath := NewPath + ';';
NewPath := NewPath + PathList[i];
end;

// Записываем обновлённый PATH в реестр
if RegWriteStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', NewPath) then
begin
if Removed then
Log('PATH успешно обновлён')
else
Log('Путь не найден в PATH: ' + NormalizedPath);
Result := True;
end
else
Log('Ошибка при обновлении PATH');
finally
PathList.Free;
end;
end;
Comment on lines +131 to +211
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Добавьте обработку ошибок с обратной связью для пользователя.

Функция RemoveFromPath имеет те же проблемы, что и AddToPath:

  1. Условие гонки (TOCTOU): PATH может быть изменён между чтением (строка 150) и записью (строка 198).

  2. Отсутствие видимой обратной связи: При ошибке обновления PATH (строка 207) пользователь не получает предупреждения.

Хорошо реализовано: Ручная сборка строки (строки 187-195) корректно избегает проблемы с добавлением кавычек функцией TStringList.DelimitedText.

💡 Рекомендация по улучшению

Добавьте видимое предупреждение при ошибке (замените строку 207):

else
begin
  Log('Ошибка при обновлении PATH');
  MsgBox('Не удалось автоматически удалить путь из PATH.' + #13#10 +
         'При необходимости удалите путь вручную: ' + NormalizedPath,
         mbError, MB_OK);
end;


procedure CurStepChanged(CurStep: TSetupStep);
var
AppPath: string;
begin
if CurStep = ssPostInstall then
begin
AppPath := ExpandConstant('{app}');
AddToPath(AppPath);
end;
end;

procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
var
AppPath: string;
begin
if CurUninstallStep = usPostUninstall then
begin
AppPath := ExpandConstant('{app}');
RemoveFromPath(AppPath);
end;
end;

Loading