Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7687f410c4 | ||
|
|
7e34a73835 | ||
|
|
47c1db9979 | ||
|
|
22c842d5e3 | ||
|
|
1a15d57033 |
@@ -1,4 +1,5 @@
|
|||||||
dist/
|
dist/
|
||||||
|
dist-windows/
|
||||||
release/
|
release/
|
||||||
node_modules/
|
node_modules/
|
||||||
# compiled backend binaries (built from source)
|
# compiled backend binaries (built from source)
|
||||||
|
|||||||
@@ -28,6 +28,22 @@ cd ../verstak-desktop
|
|||||||
./scripts/install-dev-plugins.sh
|
./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
|
## Alpha behaviour
|
||||||
|
|
||||||
- Browser Inbox archives processed captures and can restore them; permanent
|
- 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.
|
explicit unassigned scope. Journal creation is always a user action.
|
||||||
- Todo is optional: Overview uses it only when the Todo capability is present.
|
- Todo is optional: Overview uses it only when the Todo capability is present.
|
||||||
|
|
||||||
## Release package
|
## Publish a GitHub Release
|
||||||
|
|
||||||
```bash
|
```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
|
The publisher requires an authenticated [`gh`](https://cli.github.com/) CLI
|
||||||
`release/verstak-official-plugins-<version>.tar.gz` plus `SHA256SUMS`.
|
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
|
## 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() {
|
function persist() {
|
||||||
if (!api || !api.settings || typeof api.settings.write !== 'function') return Promise.resolve();
|
if (!api || !api.settings || typeof api.settings.write !== 'function') return syncNotifications();
|
||||||
return api.settings.write(GLOBAL_KEY, storageTodos(sortTodos(todos))).catch(function (err) {
|
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)));
|
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';
|
statusClass = 'error';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadStored() {
|
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) {
|
return api.settings.read().then(function (settings) {
|
||||||
todos = sortTodos(normalizeTodos((settings || {})[GLOBAL_KEY]));
|
todos = sortTodos(normalizeTodos((settings || {})[GLOBAL_KEY]));
|
||||||
|
return syncNotifications();
|
||||||
}).catch(function (err) {
|
}).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)));
|
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';
|
statusClass = 'error';
|
||||||
@@ -589,6 +616,7 @@
|
|||||||
searchInput.setAttribute('placeholder', tr('ui.search', null, 'Search todos'));
|
searchInput.setAttribute('placeholder', tr('ui.search', null, 'Search todos'));
|
||||||
addBtn.textContent = tr('ui.add', null, 'Add Todo');
|
addBtn.textContent = tr('ui.add', null, 'Add Todo');
|
||||||
render();
|
render();
|
||||||
|
syncNotifications().then(render);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,6 +19,9 @@
|
|||||||
"ui.unassigned": "Unassigned",
|
"ui.unassigned": "Unassigned",
|
||||||
"ui.saveError": "Could not save todos: {error}",
|
"ui.saveError": "Could not save todos: {error}",
|
||||||
"ui.loadError": "Could not load 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.titlePlaceholder": "Todo title",
|
||||||
"ui.descriptionPlaceholder": "Optional description",
|
"ui.descriptionPlaceholder": "Optional description",
|
||||||
"ui.priority.low": "Low",
|
"ui.priority.low": "Low",
|
||||||
|
|||||||
@@ -19,6 +19,9 @@
|
|||||||
"ui.unassigned": "Не назначено",
|
"ui.unassigned": "Не назначено",
|
||||||
"ui.saveError": "Не удалось сохранить задачи: {error}",
|
"ui.saveError": "Не удалось сохранить задачи: {error}",
|
||||||
"ui.loadError": "Не удалось загрузить задачи: {error}",
|
"ui.loadError": "Не удалось загрузить задачи: {error}",
|
||||||
|
"ui.notificationError": "Не удалось запланировать напоминания: {error}",
|
||||||
|
"ui.notificationTitle": "Напоминание о задаче",
|
||||||
|
"ui.notificationBody": "{title}",
|
||||||
"ui.titlePlaceholder": "Название задачи",
|
"ui.titlePlaceholder": "Название задачи",
|
||||||
"ui.descriptionPlaceholder": "Необязательное описание",
|
"ui.descriptionPlaceholder": "Необязательное описание",
|
||||||
"ui.priority.low": "Низкий",
|
"ui.priority.low": "Низкий",
|
||||||
|
|||||||
@@ -12,10 +12,14 @@
|
|||||||
"todo.list",
|
"todo.list",
|
||||||
"todo.workspace"
|
"todo.workspace"
|
||||||
],
|
],
|
||||||
|
"requires": [
|
||||||
|
"verstak/core/notifications/v1"
|
||||||
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"files.read",
|
"files.read",
|
||||||
"storage.namespace",
|
"storage.namespace",
|
||||||
"ui.register"
|
"ui.register",
|
||||||
|
"notifications.schedule"
|
||||||
],
|
],
|
||||||
"frontend": {
|
"frontend": {
|
||||||
"entry": "frontend/src/index.js"
|
"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 ==="
|
echo "=== verstak official plugins release $VERSION ==="
|
||||||
"$ROOT/scripts/check.sh"
|
"$ROOT/scripts/check.sh"
|
||||||
"$ROOT/scripts/build.sh"
|
"$ROOT/scripts/package-portable.sh" "$VERSION"
|
||||||
|
|
||||||
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"
|
|
||||||
|
|||||||
@@ -146,10 +146,12 @@ function loadComponent(document, emittedEvents) {
|
|||||||
|
|
||||||
function makeApi(initialSettings = {}, initialLocale = 'en') {
|
function makeApi(initialSettings = {}, initialLocale = 'en') {
|
||||||
const settings = { ...initialSettings };
|
const settings = { ...initialSettings };
|
||||||
|
const notificationCalls = [];
|
||||||
let locale = initialLocale;
|
let locale = initialLocale;
|
||||||
const localeListeners = [];
|
const localeListeners = [];
|
||||||
return {
|
return {
|
||||||
settings,
|
settings,
|
||||||
|
notificationCalls,
|
||||||
settingsApi: {
|
settingsApi: {
|
||||||
read: async (key) => (key ? settings[key] : { ...settings }),
|
read: async (key) => (key ? settings[key] : { ...settings }),
|
||||||
write: async (key, value) => {
|
write: async (key, value) => {
|
||||||
@@ -163,6 +165,11 @@ function makeApi(initialSettings = {}, initialLocale = 'en') {
|
|||||||
{ name: 'Project', relativePath: 'Project', type: 'folder' },
|
{ name: 'Project', relativePath: 'Project', type: 'folder' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
notifications: {
|
||||||
|
replace: async (items) => {
|
||||||
|
notificationCalls.push((Array.isArray(items) ? items : []).map((item) => ({ ...item })));
|
||||||
|
},
|
||||||
|
},
|
||||||
setLocale(nextLocale) {
|
setLocale(nextLocale) {
|
||||||
locale = nextLocale;
|
locale = nextLocale;
|
||||||
localeListeners.slice().forEach((listener) => listener(locale));
|
localeListeners.slice().forEach((listener) => listener(locale));
|
||||||
@@ -171,6 +178,7 @@ function makeApi(initialSettings = {}, initialLocale = 'en') {
|
|||||||
return {
|
return {
|
||||||
settings: this.settingsApi,
|
settings: this.settingsApi,
|
||||||
files: this.files,
|
files: this.files,
|
||||||
|
notifications: this.notifications,
|
||||||
i18n: {
|
i18n: {
|
||||||
getLocale: () => locale,
|
getLocale: () => locale,
|
||||||
t: (key, params, fallback) => {
|
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.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('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('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.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');
|
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.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 (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');
|
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');
|
apiState.setLocale('ru');
|
||||||
if (!container.textContent.includes('Задачи · Project') || !container.textContent.includes('Просрочено')) {
|
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) {
|
if (apiState.settings['todos:global'][0].status !== 'done' || !apiState.settings['todos:global'][0].completedAt) {
|
||||||
throw new Error('mark done did not persist completed state');
|
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', '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');
|
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