Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7687f410c4 | ||
|
|
7e34a73835 | ||
|
|
47c1db9979 | ||
|
|
22c842d5e3 | ||
|
|
1a15d57033 |
@@ -1,4 +1,5 @@
|
||||
dist/
|
||||
dist-windows/
|
||||
release/
|
||||
node_modules/
|
||||
# compiled backend binaries (built from source)
|
||||
|
||||
@@ -28,6 +28,22 @@ cd ../verstak-desktop
|
||||
./scripts/install-dev-plugins.sh
|
||||
```
|
||||
|
||||
## Portable plugin archives
|
||||
|
||||
Build target-specific archives on Linux:
|
||||
|
||||
```bash
|
||||
sudo apt install gcc-mingw-w64-x86-64
|
||||
./scripts/package-portable.sh v0.1.0-alpha.1
|
||||
```
|
||||
|
||||
The command creates `release/verstak-official-plugins-linux-amd64-<version>.tar.gz`
|
||||
and `release/verstak-official-plugins-windows-amd64-<version>.zip`, plus
|
||||
`SHA256SUMS`. Each archive expands directly into the desktop application's
|
||||
`plugins/` directory. Frontends and manifests are shared; native Go sidecars
|
||||
are built for the target OS. This is a local package operation and does not
|
||||
create a GitHub Release.
|
||||
|
||||
## Alpha behaviour
|
||||
|
||||
- Browser Inbox archives processed captures and can restore them; permanent
|
||||
@@ -38,14 +54,19 @@ cd ../verstak-desktop
|
||||
explicit unassigned scope. Journal creation is always a user action.
|
||||
- Todo is optional: Overview uses it only when the Todo capability is present.
|
||||
|
||||
## Release package
|
||||
## Publish a GitHub Release
|
||||
|
||||
```bash
|
||||
./scripts/release.sh v0.1.0-alpha.1
|
||||
./scripts/publish-github-release.sh v0.1.0-alpha.1
|
||||
```
|
||||
|
||||
The command rebuilds all packages and writes
|
||||
`release/verstak-official-plugins-<version>.tar.gz` plus `SHA256SUMS`.
|
||||
The publisher requires an authenticated [`gh`](https://cli.github.com/) CLI
|
||||
and a clean, up-to-date `main`. It runs the local release command, creates and
|
||||
pushes an annotated tag if necessary, then creates or updates the GitHub
|
||||
Release with the Linux archive, Windows archive and `SHA256SUMS`. Alpha, beta
|
||||
and release-candidate tags are published as prereleases; stable tags are
|
||||
marked latest. Re-running it for a tag that points at the current commit
|
||||
replaces the release assets.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -340,18 +340,45 @@
|
||||
});
|
||||
}
|
||||
|
||||
function notificationRequests() {
|
||||
return sortTodos(todos).filter(function (todo) {
|
||||
return todo.status === 'open' && todo.reminderAt;
|
||||
}).map(function (todo) {
|
||||
var dueAt = new Date(todo.reminderAt);
|
||||
if (isNaN(dueAt.getTime())) return null;
|
||||
var title = todo.title || tr('ui.untitled', null, 'Untitled todo');
|
||||
return {
|
||||
id: todo.id,
|
||||
dueAt: dueAt.toISOString(),
|
||||
title: tr('ui.notificationTitle', null, 'Todo reminder'),
|
||||
body: tr('ui.notificationBody', { title: title }, title)
|
||||
};
|
||||
}).filter(function (item) { return item !== null; });
|
||||
}
|
||||
|
||||
function syncNotifications() {
|
||||
if (!api || !api.notifications || typeof api.notifications.replace !== 'function') return Promise.resolve();
|
||||
return api.notifications.replace(notificationRequests()).catch(function (err) {
|
||||
statusText = tr('ui.notificationError', { error: err && err.message ? err.message : String(err) }, 'Could not schedule reminders: ' + (err && err.message ? err.message : String(err)));
|
||||
statusClass = 'error';
|
||||
});
|
||||
}
|
||||
|
||||
function persist() {
|
||||
if (!api || !api.settings || typeof api.settings.write !== 'function') return Promise.resolve();
|
||||
return api.settings.write(GLOBAL_KEY, storageTodos(sortTodos(todos))).catch(function (err) {
|
||||
if (!api || !api.settings || typeof api.settings.write !== 'function') return syncNotifications();
|
||||
return api.settings.write(GLOBAL_KEY, storageTodos(sortTodos(todos))).then(function () {
|
||||
return syncNotifications();
|
||||
}).catch(function (err) {
|
||||
statusText = tr('ui.saveError', { error: err && err.message ? err.message : String(err) }, 'Could not save todos: ' + (err && err.message ? err.message : String(err)));
|
||||
statusClass = 'error';
|
||||
});
|
||||
}
|
||||
|
||||
function loadStored() {
|
||||
if (!api || !api.settings || typeof api.settings.read !== 'function') return Promise.resolve();
|
||||
if (!api || !api.settings || typeof api.settings.read !== 'function') return syncNotifications();
|
||||
return api.settings.read().then(function (settings) {
|
||||
todos = sortTodos(normalizeTodos((settings || {})[GLOBAL_KEY]));
|
||||
return syncNotifications();
|
||||
}).catch(function (err) {
|
||||
statusText = tr('ui.loadError', { error: err && err.message ? err.message : String(err) }, 'Could not load todos: ' + (err && err.message ? err.message : String(err)));
|
||||
statusClass = 'error';
|
||||
@@ -589,6 +616,7 @@
|
||||
searchInput.setAttribute('placeholder', tr('ui.search', null, 'Search todos'));
|
||||
addBtn.textContent = tr('ui.add', null, 'Add Todo');
|
||||
render();
|
||||
syncNotifications().then(render);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
"ui.unassigned": "Unassigned",
|
||||
"ui.saveError": "Could not save todos: {error}",
|
||||
"ui.loadError": "Could not load todos: {error}",
|
||||
"ui.notificationError": "Could not schedule reminders: {error}",
|
||||
"ui.notificationTitle": "Todo reminder",
|
||||
"ui.notificationBody": "{title}",
|
||||
"ui.titlePlaceholder": "Todo title",
|
||||
"ui.descriptionPlaceholder": "Optional description",
|
||||
"ui.priority.low": "Low",
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
"ui.unassigned": "Не назначено",
|
||||
"ui.saveError": "Не удалось сохранить задачи: {error}",
|
||||
"ui.loadError": "Не удалось загрузить задачи: {error}",
|
||||
"ui.notificationError": "Не удалось запланировать напоминания: {error}",
|
||||
"ui.notificationTitle": "Напоминание о задаче",
|
||||
"ui.notificationBody": "{title}",
|
||||
"ui.titlePlaceholder": "Название задачи",
|
||||
"ui.descriptionPlaceholder": "Необязательное описание",
|
||||
"ui.priority.low": "Низкий",
|
||||
|
||||
@@ -12,10 +12,14 @@
|
||||
"todo.list",
|
||||
"todo.workspace"
|
||||
],
|
||||
"requires": [
|
||||
"verstak/core/notifications/v1"
|
||||
],
|
||||
"permissions": [
|
||||
"files.read",
|
||||
"storage.namespace",
|
||||
"ui.register"
|
||||
"ui.register",
|
||||
"notifications.schedule"
|
||||
],
|
||||
"frontend": {
|
||||
"entry": "frontend/src/index.js"
|
||||
|
||||
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
WINDOWS_DIST="${VERSTAK_WINDOWS_PLUGIN_DIST:-$ROOT/dist-windows}"
|
||||
WINDOWS_CC="${VERSTAK_WINDOWS_CC:-x86_64-w64-mingw32-gcc}"
|
||||
GO_BIN="${GO_BIN:-go}"
|
||||
|
||||
if ! command -v "$WINDOWS_CC" >/dev/null; then
|
||||
echo "Windows cross-compiler not found: $WINDOWS_CC" >&2
|
||||
echo "Install x86_64-w64-mingw32-gcc (for example: sudo apt install gcc-mingw-w64-x86-64)." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v "$GO_BIN" >/dev/null; then
|
||||
echo "go is required to build Windows plugin backends" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$ROOT"
|
||||
echo "=== verstak official plugins Windows amd64 build ==="
|
||||
|
||||
# Reuse the canonical frontend and manifest packager, then replace every Go
|
||||
# sidecar with its Windows executable in a separate dist-windows tree.
|
||||
./scripts/build.sh
|
||||
rm -rf "$WINDOWS_DIST"
|
||||
mkdir -p "$WINDOWS_DIST"
|
||||
cp -R "$ROOT/dist/." "$WINDOWS_DIST/"
|
||||
|
||||
backend_count=0
|
||||
for plugin_dir in "$ROOT"/plugins/*/; do
|
||||
[[ -d "$plugin_dir" ]] || continue
|
||||
plugin_name="$(basename "$plugin_dir")"
|
||||
backend_dir="$plugin_dir/backend"
|
||||
[[ -d "$backend_dir" ]] || continue
|
||||
if [[ ! -f "$backend_dir/go.mod" && ! -f "$backend_dir/main.go" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
package_backend="$WINDOWS_DIST/$plugin_name/backend"
|
||||
rm -rf "$package_backend"
|
||||
mkdir -p "$package_backend"
|
||||
echo " → Windows backend: $plugin_name.exe"
|
||||
(
|
||||
cd "$backend_dir"
|
||||
GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC="$WINDOWS_CC" \
|
||||
"$GO_BIN" build -o "$package_backend/$plugin_name.exe" .
|
||||
)
|
||||
backend_count=$((backend_count + 1))
|
||||
done
|
||||
|
||||
echo "Windows plugin packages: $WINDOWS_DIST"
|
||||
echo "Windows backends built: $backend_count"
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
VERSION="${1:-}"
|
||||
|
||||
if [[ -z "$VERSION" || ! "$VERSION" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]; then
|
||||
echo "usage: $0 <version>" >&2
|
||||
echo "example: $0 v0.1.0-alpha.1" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if ! command -v zip >/dev/null; then
|
||||
echo "zip is required to create the Windows plugin archive" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== verstak official portable plugin packages $VERSION ==="
|
||||
"$ROOT/scripts/build.sh"
|
||||
"$ROOT/scripts/build-windows.sh"
|
||||
|
||||
RELEASE_ROOT="$ROOT/release"
|
||||
LINUX_ARCHIVE="$RELEASE_ROOT/verstak-official-plugins-linux-amd64-$VERSION.tar.gz"
|
||||
WINDOWS_ARCHIVE="$RELEASE_ROOT/verstak-official-plugins-windows-amd64-$VERSION.zip"
|
||||
rm -rf "$RELEASE_ROOT"
|
||||
mkdir -p "$RELEASE_ROOT"
|
||||
|
||||
# Each archive expands directly into the desktop application's plugins/
|
||||
# directory. Frontends and manifests are shared; platform-test's native
|
||||
# sidecar is built for the target operating system.
|
||||
tar -C "$ROOT/dist" -czf "$LINUX_ARCHIVE" .
|
||||
(cd "$ROOT/dist-windows" && zip -qr "$WINDOWS_ARCHIVE" .)
|
||||
(cd "$RELEASE_ROOT" && sha256sum "$(basename "$LINUX_ARCHIVE")" "$(basename "$WINDOWS_ARCHIVE")" > SHA256SUMS)
|
||||
|
||||
echo "Linux plugins: $LINUX_ARCHIVE"
|
||||
echo "Windows plugins: $WINDOWS_ARCHIVE"
|
||||
echo "checksums: $RELEASE_ROOT/SHA256SUMS"
|
||||
Executable
+80
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
VERSION="${1:-}"
|
||||
REPOSITORY="mirivlad/verstak-official-plugins"
|
||||
RELEASE_SCRIPT="${VERSTAK_RELEASE_SCRIPT:-$ROOT/scripts/release.sh}"
|
||||
RELEASE_DIR="${VERSTAK_RELEASE_DIR:-$ROOT/release}"
|
||||
GIT_BIN="${GIT_BIN:-git}"
|
||||
GH_BIN="${GH_BIN:-gh}"
|
||||
|
||||
cd "$ROOT"
|
||||
|
||||
if [[ -z "$VERSION" || ! "$VERSION" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]; then
|
||||
echo "usage: $0 <version>" >&2
|
||||
echo "example: $0 v0.1.0-alpha.1" >&2
|
||||
exit 2
|
||||
fi
|
||||
if ! command -v "$GH_BIN" >/dev/null; then
|
||||
echo "gh CLI is required to publish a GitHub Release" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$("$GIT_BIN" branch --show-current)" != "main" ]]; then
|
||||
echo "GitHub releases must be published from main" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "$("$GIT_BIN" status --porcelain)" ]]; then
|
||||
echo "working tree must be clean before publishing a release" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"$GH_BIN" auth status
|
||||
"$GIT_BIN" fetch origin main --tags
|
||||
HEAD="$("$GIT_BIN" rev-parse HEAD)"
|
||||
if [[ "$HEAD" != "$("$GIT_BIN" rev-parse origin/main)" ]]; then
|
||||
echo "local main must match origin/main before publishing a release" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"$RELEASE_SCRIPT" "$VERSION"
|
||||
|
||||
ASSETS=(
|
||||
"$RELEASE_DIR/verstak-official-plugins-linux-amd64-$VERSION.tar.gz"
|
||||
"$RELEASE_DIR/verstak-official-plugins-windows-amd64-$VERSION.zip"
|
||||
"$RELEASE_DIR/SHA256SUMS"
|
||||
)
|
||||
for asset in "${ASSETS[@]}"; do
|
||||
if [[ ! -s "$asset" ]]; then
|
||||
echo "required release asset is missing or empty: $asset" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if "$GIT_BIN" rev-parse -q --verify "refs/tags/$VERSION" >/dev/null; then
|
||||
if [[ "$("$GIT_BIN" rev-parse "${VERSION}^{commit}")" != "$HEAD" ]]; then
|
||||
echo "existing tag $VERSION does not point at HEAD" >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
"$GIT_BIN" tag -a "$VERSION" -m "Release $VERSION"
|
||||
"$GIT_BIN" push origin "refs/tags/$VERSION"
|
||||
fi
|
||||
|
||||
if "$GH_BIN" release view "$VERSION" --repo "$REPOSITORY" >/dev/null 2>&1; then
|
||||
"$GH_BIN" release upload "$VERSION" "${ASSETS[@]}" --repo "$REPOSITORY" --clobber
|
||||
else
|
||||
RELEASE_OPTIONS=(--generate-notes --verify-tag)
|
||||
if [[ "$VERSION" == *-* ]]; then
|
||||
RELEASE_OPTIONS+=(--prerelease)
|
||||
else
|
||||
RELEASE_OPTIONS+=(--latest)
|
||||
fi
|
||||
"$GH_BIN" release create "$VERSION" "${ASSETS[@]}" \
|
||||
--repo "$REPOSITORY" \
|
||||
--title "Verstak Official Plugins $VERSION" \
|
||||
"${RELEASE_OPTIONS[@]}"
|
||||
fi
|
||||
|
||||
echo "GitHub release:"
|
||||
"$GH_BIN" release view "$VERSION" --repo "$REPOSITORY" --json url --jq .url
|
||||
+1
-15
@@ -12,18 +12,4 @@ fi
|
||||
|
||||
echo "=== verstak official plugins release $VERSION ==="
|
||||
"$ROOT/scripts/check.sh"
|
||||
"$ROOT/scripts/build.sh"
|
||||
|
||||
RELEASE_ROOT="$ROOT/release"
|
||||
STAGING="$RELEASE_ROOT/verstak-official-plugins-$VERSION"
|
||||
ARCHIVE="$RELEASE_ROOT/verstak-official-plugins-$VERSION.tar.gz"
|
||||
rm -rf "$STAGING" "$ARCHIVE"
|
||||
mkdir -p "$STAGING"
|
||||
|
||||
cp "$ROOT/README.md" "$ROOT/LICENSE" "$STAGING/"
|
||||
cp -R "$ROOT/dist" "$STAGING/dist"
|
||||
tar -C "$RELEASE_ROOT" -czf "$ARCHIVE" "$(basename "$STAGING")"
|
||||
(cd "$RELEASE_ROOT" && sha256sum "$(basename "$ARCHIVE")" > SHA256SUMS)
|
||||
|
||||
echo "release archive: $ARCHIVE"
|
||||
echo "checksums: $RELEASE_ROOT/SHA256SUMS"
|
||||
"$ROOT/scripts/package-portable.sh" "$VERSION"
|
||||
|
||||
@@ -146,10 +146,12 @@ function loadComponent(document, emittedEvents) {
|
||||
|
||||
function makeApi(initialSettings = {}, initialLocale = 'en') {
|
||||
const settings = { ...initialSettings };
|
||||
const notificationCalls = [];
|
||||
let locale = initialLocale;
|
||||
const localeListeners = [];
|
||||
return {
|
||||
settings,
|
||||
notificationCalls,
|
||||
settingsApi: {
|
||||
read: async (key) => (key ? settings[key] : { ...settings }),
|
||||
write: async (key, value) => {
|
||||
@@ -163,6 +165,11 @@ function makeApi(initialSettings = {}, initialLocale = 'en') {
|
||||
{ name: 'Project', relativePath: 'Project', type: 'folder' },
|
||||
],
|
||||
},
|
||||
notifications: {
|
||||
replace: async (items) => {
|
||||
notificationCalls.push((Array.isArray(items) ? items : []).map((item) => ({ ...item })));
|
||||
},
|
||||
},
|
||||
setLocale(nextLocale) {
|
||||
locale = nextLocale;
|
||||
localeListeners.slice().forEach((listener) => listener(locale));
|
||||
@@ -171,6 +178,7 @@ function makeApi(initialSettings = {}, initialLocale = 'en') {
|
||||
return {
|
||||
settings: this.settingsApi,
|
||||
files: this.files,
|
||||
notifications: this.notifications,
|
||||
i18n: {
|
||||
getLocale: () => locale,
|
||||
t: (key, params, fallback) => {
|
||||
@@ -208,6 +216,8 @@ async function mountWithApi(apiState, props, emittedEvents = [], document = make
|
||||
if (manifest.id !== 'verstak.todo') throw new Error('todo manifest id mismatch');
|
||||
if (!manifest.permissions.includes('storage.namespace')) throw new Error('todo manifest must request storage.namespace');
|
||||
if (!manifest.permissions.includes('files.read')) throw new Error('todo manifest must request files.read');
|
||||
if (!manifest.permissions.includes('notifications.schedule')) throw new Error('todo manifest must request notifications.schedule');
|
||||
if (!manifest.requires.includes('verstak/core/notifications/v1')) throw new Error('todo manifest must require native notifications');
|
||||
if (!(manifest.contributes.views || []).length) throw new Error('todo manifest must contribute a global view');
|
||||
if (!(manifest.contributes.workspaceItems || []).length) throw new Error('todo manifest must contribute a workspace item');
|
||||
|
||||
@@ -233,6 +243,14 @@ async function mountWithApi(apiState, props, emittedEvents = [], document = make
|
||||
if (createdTodo.status !== 'open' || createdTodo.priority !== 'high') throw new Error('Todo status or priority was not stored');
|
||||
if (createdTodo.dueAt !== '2000-01-01' || createdTodo.reminderAt !== '2000-01-01T09:00') throw new Error('Todo due/reminder metadata was not stored');
|
||||
if (!container.textContent.includes('Overdue') || !container.textContent.includes('Reminder due')) throw new Error('due/reminder indicators were not rendered');
|
||||
const scheduledAfterCreate = apiState.notificationCalls.at(-1) || [];
|
||||
if (scheduledAfterCreate.length !== 1
|
||||
|| scheduledAfterCreate[0].id !== createdTodo.id
|
||||
|| scheduledAfterCreate[0].dueAt !== new Date(createdTodo.reminderAt).toISOString()
|
||||
|| scheduledAfterCreate[0].title !== 'Todo reminder'
|
||||
|| scheduledAfterCreate[0].body !== 'Prepare project review') {
|
||||
throw new Error('open Todo reminder was not scheduled as a native notification');
|
||||
}
|
||||
|
||||
apiState.setLocale('ru');
|
||||
if (!container.textContent.includes('Задачи · Project') || !container.textContent.includes('Просрочено')) {
|
||||
@@ -252,6 +270,9 @@ async function mountWithApi(apiState, props, emittedEvents = [], document = make
|
||||
if (apiState.settings['todos:global'][0].status !== 'done' || !apiState.settings['todos:global'][0].completedAt) {
|
||||
throw new Error('mark done did not persist completed state');
|
||||
}
|
||||
if ((apiState.notificationCalls.at(-1) || []).length !== 0) {
|
||||
throw new Error('completed Todo reminder was not removed from native notification schedules');
|
||||
}
|
||||
if (!byData(container, 'data-todo-action', 'reopen')) throw new Error('done Todo did not expose reopen action');
|
||||
if (!byData(container, 'data-todo-action', 'create-journal-entry')) throw new Error('done workspace Todo did not expose Journal conversion');
|
||||
|
||||
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
BUILDER="$ROOT/scripts/build-windows.sh"
|
||||
|
||||
if [[ ! -x "$BUILDER" ]]; then
|
||||
echo "Windows plugin builder is missing or not executable: $BUILDER" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bash -n "$BUILDER"
|
||||
grep -Fq 'GOOS=windows' "$BUILDER"
|
||||
grep -Fq 'GOARCH=amd64' "$BUILDER"
|
||||
grep -Fq 'dist-windows' "$BUILDER"
|
||||
grep -Fq '.exe' "$BUILDER"
|
||||
grep -Fq 'x86_64-w64-mingw32-gcc' "$BUILDER"
|
||||
|
||||
echo "Windows plugin build script contract passed"
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
PACKAGER="$ROOT/scripts/package-portable.sh"
|
||||
|
||||
if [[ ! -x "$PACKAGER" ]]; then
|
||||
echo "portable plugin packager is missing or not executable: $PACKAGER" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bash -n "$PACKAGER"
|
||||
grep -Fq 'scripts/build.sh' "$PACKAGER"
|
||||
grep -Fq 'scripts/build-windows.sh' "$PACKAGER"
|
||||
grep -Fq 'verstak-official-plugins-linux-amd64-' "$PACKAGER"
|
||||
grep -Fq 'verstak-official-plugins-windows-amd64-' "$PACKAGER"
|
||||
grep -Fq 'tar -C' "$PACKAGER"
|
||||
grep -Fq 'zip -qr' "$PACKAGER"
|
||||
grep -Fq 'SHA256SUMS' "$PACKAGER"
|
||||
grep -Fq 'package-portable.sh' "$ROOT/scripts/release.sh"
|
||||
|
||||
echo "portable plugin package script contract passed"
|
||||
Executable
+98
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
PUBLISHER="$ROOT/scripts/publish-github-release.sh"
|
||||
VERSION="v0.0.0-test"
|
||||
REPOSITORY="mirivlad/verstak-official-plugins"
|
||||
ASSET_NAMES=(
|
||||
"verstak-official-plugins-linux-amd64-v0.0.0-test.tar.gz"
|
||||
"verstak-official-plugins-windows-amd64-v0.0.0-test.zip"
|
||||
)
|
||||
WORK="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
|
||||
if [[ ! -x "$PUBLISHER" ]]; then
|
||||
echo "publisher is missing or not executable: $PUBLISHER" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$WORK/bin" "$WORK/release" "$WORK/state"
|
||||
LOG="$WORK/log"
|
||||
export LOG
|
||||
|
||||
cat > "$WORK/release.sh" <<'SCRIPT'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
printf 'release:%s\n' "$1" >> "$LOG"
|
||||
printf 'linux\n' > "$VERSTAK_RELEASE_DIR/verstak-official-plugins-linux-amd64-$1.tar.gz"
|
||||
printf 'windows\n' > "$VERSTAK_RELEASE_DIR/verstak-official-plugins-windows-amd64-$1.zip"
|
||||
printf 'checksum\n' > "$VERSTAK_RELEASE_DIR/SHA256SUMS"
|
||||
SCRIPT
|
||||
chmod +x "$WORK/release.sh"
|
||||
|
||||
cat > "$WORK/bin/git" <<'SCRIPT'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
if [[ "$PWD" != "${EXPECTED_ROOT:?}" ]]; then
|
||||
echo "publisher did not enter repository root: $PWD" >&2
|
||||
exit 1
|
||||
fi
|
||||
case "${1:-}" in
|
||||
status) exit 0 ;;
|
||||
branch) echo main ;;
|
||||
fetch) printf 'fetch\n' >> "$LOG" ;;
|
||||
rev-parse)
|
||||
if [[ "${2:-}" == "-q" ]]; then
|
||||
if [[ -f "$TEST_STATE/tag" ]]; then echo test-commit; exit 0; fi
|
||||
exit 1
|
||||
fi
|
||||
echo test-commit
|
||||
;;
|
||||
tag) touch "$TEST_STATE/tag"; printf 'tag:%s\n' "${3:-}" >> "$LOG" ;;
|
||||
push) printf 'push:%s:%s\n' "${2:-}" "${3:-}" >> "$LOG" ;;
|
||||
*) echo "unexpected git invocation: $*" >&2; exit 1 ;;
|
||||
esac
|
||||
SCRIPT
|
||||
chmod +x "$WORK/bin/git"
|
||||
|
||||
cat > "$WORK/bin/gh" <<'SCRIPT'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
case "${1:-}:${2:-}" in
|
||||
auth:status) printf 'auth\n' >> "$LOG" ;;
|
||||
release:view)
|
||||
if [[ -f "$TEST_STATE/release" ]]; then echo https://github.example/release; else exit 1; fi
|
||||
;;
|
||||
release:create) touch "$TEST_STATE/release"; printf 'create:%s\n' "$*" >> "$LOG" ;;
|
||||
release:upload) printf 'upload:%s\n' "$*" >> "$LOG" ;;
|
||||
*) echo "unexpected gh invocation: $*" >&2; exit 1 ;;
|
||||
esac
|
||||
SCRIPT
|
||||
chmod +x "$WORK/bin/gh"
|
||||
|
||||
run_publisher() {
|
||||
VERSTAK_RELEASE_SCRIPT="$WORK/release.sh" \
|
||||
VERSTAK_RELEASE_DIR="$WORK/release" \
|
||||
GIT_BIN="$WORK/bin/git" \
|
||||
GH_BIN="$WORK/bin/gh" \
|
||||
EXPECTED_ROOT="$ROOT" \
|
||||
TEST_STATE="$WORK/state" \
|
||||
"$PUBLISHER" "$VERSION"
|
||||
}
|
||||
|
||||
run_publisher
|
||||
grep -Fqx "release:$VERSION" "$LOG"
|
||||
grep -Fqx "tag:$VERSION" "$LOG"
|
||||
grep -Fqx "push:origin:refs/tags/$VERSION" "$LOG"
|
||||
grep -F "release create $VERSION" "$LOG" >/dev/null
|
||||
for asset in "${ASSET_NAMES[@]}"; do
|
||||
grep -F "$asset" "$LOG" >/dev/null
|
||||
done
|
||||
grep -F "SHA256SUMS" "$LOG" >/dev/null
|
||||
grep -F -- "--prerelease" "$LOG" >/dev/null
|
||||
|
||||
run_publisher
|
||||
grep -F "release upload $VERSION" "$LOG" >/dev/null
|
||||
|
||||
echo "GitHub release publisher test passed"
|
||||
Reference in New Issue
Block a user