Compare commits
No commits in common. "main" and "codex/alpha-product-ux" have entirely different histories.
main
...
codex/alph
40
README.md
40
README.md
|
|
@ -39,45 +39,14 @@ domains such as `youtube.com` or `x.com`.
|
||||||
Manual “Send page”, selection, link and file actions are separate. They create
|
Manual “Send page”, selection, link and file actions are separate. They create
|
||||||
Browser Inbox captures; they do not create a workspace or a journal entry.
|
Browser Inbox captures; they do not create a workspace or a journal entry.
|
||||||
|
|
||||||
## Firefox download and updates
|
## Firefox Release
|
||||||
|
|
||||||
The signed XPI is published on the [GitHub Releases page](https://github.com/mirivlad/verstak-browser-extension/releases).
|
|
||||||
Download the Firefox asset named `verstak-firefox-<version>.xpi` from the latest
|
|
||||||
release and open it in Firefox to install it.
|
|
||||||
|
|
||||||
After the first public release, installed copies check GitHub Releases for
|
|
||||||
updates through the release's `updates.json` asset. During the alpha phase each
|
|
||||||
published release is the current update channel.
|
|
||||||
|
|
||||||
## Firefox release publishing
|
|
||||||
|
|
||||||
Firefox signing uses `web-ext` and AMO credentials from an env file. The script
|
Firefox signing uses `web-ext` and AMO credentials from an env file. The script
|
||||||
requires `WEB_EXT_API_PROXY`; AMO upload and approval polling run through that
|
requires `WEB_EXT_API_PROXY`; AMO upload and approval polling run through that
|
||||||
proxy.
|
proxy.
|
||||||
|
|
||||||
Create the signed XPI locally, without publishing it:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
VERSTAK_BROWSER_ENV=/path/to/.env npm run release:firefox
|
VERSTAK_BROWSER_ENV=/home/mirivlad/git/verstak/.env npm run release:firefox
|
||||||
```
|
|
||||||
|
|
||||||
Publish the signed XPI and `updates.json` as the current GitHub Release:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
VERSTAK_BROWSER_ENV=/path/to/.env npm run publish:github
|
|
||||||
```
|
|
||||||
|
|
||||||
The publisher reads the version from `package.json` and requires an
|
|
||||||
authenticated GitHub CLI plus a clean local `main` equal to `origin/main`. It
|
|
||||||
creates and pushes the version tag if needed, then creates or updates the
|
|
||||||
GitHub Release. Re-running it for the same tag replaces the XPI and
|
|
||||||
`updates.json` assets. `npm run publish:firefox` remains as a compatible alias
|
|
||||||
for the same Firefox publishing flow.
|
|
||||||
|
|
||||||
For an explicit version check, pass the current tag after `--`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
VERSTAK_BROWSER_ENV=/path/to/.env npm run publish:github -- v2.0.5
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Release output:
|
Release output:
|
||||||
|
|
@ -85,9 +54,8 @@ Release output:
|
||||||
- `release/firefox/verstak-firefox-<version>.xpi`
|
- `release/firefox/verstak-firefox-<version>.xpi`
|
||||||
- `release/firefox/updates.json`
|
- `release/firefox/updates.json`
|
||||||
|
|
||||||
The XPI is signed as an unlisted/self-distributed Firefox extension; GitHub
|
The XPI is signed as an unlisted/self-distributed Firefox extension. Build and
|
||||||
Releases distribute that signed file. Build and release artifacts are local
|
release artifacts are local outputs and are not committed.
|
||||||
outputs and are not committed.
|
|
||||||
|
|
||||||
## Reproducible local package
|
## Reproducible local package
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Verstak Bridge",
|
"name": "Verstak Bridge",
|
||||||
"version": "2.0.5",
|
"version": "2.0.2",
|
||||||
"description": "Send pages, selections, links, and files to the local Verstak browser inbox.",
|
"description": "Send pages, selections, links, and files to the local Verstak browser inbox.",
|
||||||
"author": "Verstak",
|
"author": "Verstak",
|
||||||
"homepage_url": "https://github.com/mirivlad/verstak-browser-extension",
|
"homepage_url": "https://git.mirv.top/verstak/verstak-browser-extension",
|
||||||
"permissions": ["alarms", "contextMenus", "idle", "storage", "tabs", "windows"],
|
"permissions": ["alarms", "contextMenus", "idle", "storage", "tabs", "windows"],
|
||||||
"host_permissions": ["http://127.0.0.1/*", "http://localhost/*"],
|
"host_permissions": ["http://127.0.0.1/*", "http://localhost/*"],
|
||||||
"background": {
|
"background": {
|
||||||
|
|
|
||||||
|
|
@ -1,329 +0,0 @@
|
||||||
# GitHub Firefox Updates Implementation Plan
|
|
||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
||||||
|
|
||||||
**Goal:** Publish signed Firefox XPI releases to GitHub Releases and let installed alpha extensions discover them through a GitHub-hosted update manifest.
|
|
||||||
|
|
||||||
**Architecture:** Firefox keeps one immutable `update_url` targeting the current GitHub Release's `updates.json` asset. A small Node helper owns the tag, asset URL and update-manifest formatting so it has focused unit tests. The existing local signing command remains local; an explicit publishing command uploads its two artifacts to the public GitHub release.
|
|
||||||
|
|
||||||
**Tech Stack:** Manifest V2 JSON, Node.js CommonJS tests, Bash, `web-ext`, GitHub CLI (`gh`).
|
|
||||||
|
|
||||||
## Global Constraints
|
|
||||||
|
|
||||||
- Publish repository: `mirivlad/verstak-browser-extension`.
|
|
||||||
- Firefox addon ID: `verstak-bridge@verstak.app`.
|
|
||||||
- Initial GitHub release: `v2.0.3`; package and both browser manifests are version `2.0.3`.
|
|
||||||
- Every alpha GitHub release is ordinary/latest, not a prerelease.
|
|
||||||
- XPI remains Mozilla-signed as an unlisted/self-distributed addon before upload.
|
|
||||||
- `npm run release:firefox` remains a local sign-and-package action.
|
|
||||||
- `npm run publish:firefox` is the only command allowed to create or modify a GitHub Release.
|
|
||||||
- Generated `release/` output remains untracked.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 1: Make GitHub update metadata testable
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `scripts/firefox-github-release.js`
|
|
||||||
- Create: `scripts/test-firefox-github-release.js`
|
|
||||||
- Modify: `package.json:8-15`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Produces `releaseTag(version)`, `releaseAssetURL(version, assetName)`, and `updateManifest(addonID, version, assetName)` from `scripts/firefox-github-release.js`.
|
|
||||||
- `updateManifest` returns the object written as `updates.json` and uses the fixed GitHub repository.
|
|
||||||
- Later shell scripts invoke `node scripts/firefox-github-release.js write-updates <addon-id> <version> <asset-name> <output-path>`.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write the failing metadata test**
|
|
||||||
|
|
||||||
Create `scripts/test-firefox-github-release.js` with:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const assert = require('assert');
|
|
||||||
const release = require('./firefox-github-release');
|
|
||||||
|
|
||||||
const version = '2.0.3';
|
|
||||||
const asset = 'verstak-firefox-2.0.3.xpi';
|
|
||||||
assert.equal(release.releaseTag(version), 'v2.0.3');
|
|
||||||
assert.equal(
|
|
||||||
release.releaseAssetURL(version, asset),
|
|
||||||
'https://github.com/mirivlad/verstak-browser-extension/releases/download/v2.0.3/verstak-firefox-2.0.3.xpi',
|
|
||||||
);
|
|
||||||
assert.deepEqual(release.updateManifest('verstak-bridge@verstak.app', version, asset), {
|
|
||||||
addons: {
|
|
||||||
'verstak-bridge@verstak.app': {
|
|
||||||
updates: [{
|
|
||||||
version: '2.0.3',
|
|
||||||
update_link: 'https://github.com/mirivlad/verstak-browser-extension/releases/download/v2.0.3/verstak-firefox-2.0.3.xpi',
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
console.log('Firefox GitHub release metadata tests passed');
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Run the focused test and verify it fails**
|
|
||||||
|
|
||||||
Run: `node scripts/test-firefox-github-release.js`
|
|
||||||
|
|
||||||
Expected: failure because `scripts/firefox-github-release.js` does not yet exist.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Implement the metadata helper**
|
|
||||||
|
|
||||||
Create `scripts/firefox-github-release.js` with these exact exports and CLI:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const fs = require('fs');
|
|
||||||
const REPOSITORY = 'mirivlad/verstak-browser-extension';
|
|
||||||
|
|
||||||
function releaseTag(version) { return `v${version}`; }
|
|
||||||
function releaseAssetURL(version, assetName) {
|
|
||||||
return `https://github.com/${REPOSITORY}/releases/download/${releaseTag(version)}/${assetName}`;
|
|
||||||
}
|
|
||||||
function updateManifest(addonID, version, assetName) {
|
|
||||||
return { addons: { [addonID]: { updates: [{ version, update_link: releaseAssetURL(version, assetName) }] } } };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (require.main === module) {
|
|
||||||
const [, , command, addonID, version, assetName, outputPath] = process.argv;
|
|
||||||
if (command !== 'write-updates' || !addonID || !version || !assetName || !outputPath) process.exitCode = 2;
|
|
||||||
else fs.writeFileSync(outputPath, `${JSON.stringify(updateManifest(addonID, version, assetName), null, 2)}\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { REPOSITORY, releaseTag, releaseAssetURL, updateManifest };
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 4: Register and run the test**
|
|
||||||
|
|
||||||
Append `node scripts/test-firefox-github-release.js` to the existing `test`
|
|
||||||
script in `package.json`, then run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm test
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: all existing tests and `Firefox GitHub release metadata tests passed`.
|
|
||||||
|
|
||||||
- [ ] **Step 5: Commit and push**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add package.json scripts/firefox-github-release.js scripts/test-firefox-github-release.js
|
|
||||||
git commit -m "test: cover Firefox GitHub release metadata"
|
|
||||||
git push origin main
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 2: Point Firefox and local release artifacts at GitHub
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `firefox/manifest.json:8-17`
|
|
||||||
- Modify: `scripts/release-firefox-xpi.sh:29-70`
|
|
||||||
- Test: `scripts/test-firefox-github-release.js`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Firefox reads `https://github.com/mirivlad/verstak-browser-extension/releases/latest/download/updates.json`.
|
|
||||||
- `release-firefox-xpi.sh` invokes the Task 1 helper to write a versioned update manifest.
|
|
||||||
- It continues producing `release/firefox/verstak-firefox-<version>.xpi` and `release/firefox/updates.json` locally.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Extend the failing test with the stable manifest URL**
|
|
||||||
|
|
||||||
Add this assertion to `scripts/test-firefox-github-release.js` before its final
|
|
||||||
success message:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const firefoxManifest = require('../firefox/manifest.json');
|
|
||||||
assert.equal(
|
|
||||||
firefoxManifest.browser_specific_settings.gecko.update_url,
|
|
||||||
'https://github.com/mirivlad/verstak-browser-extension/releases/latest/download/updates.json',
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Run the focused test and verify it fails**
|
|
||||||
|
|
||||||
Run: `node scripts/test-firefox-github-release.js`
|
|
||||||
|
|
||||||
Expected: assertion failure because the manifest still uses `mirv.top`.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Replace the update endpoint and JSON writer**
|
|
||||||
|
|
||||||
In `firefox/manifest.json`, replace only the Gecko `update_url` value with:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"update_url": "https://github.com/mirivlad/verstak-browser-extension/releases/latest/download/updates.json"
|
|
||||||
```
|
|
||||||
|
|
||||||
In `scripts/release-firefox-xpi.sh`, remove `UPDATE_BASE_URL` and replace the
|
|
||||||
heredoc that writes `updates.json` with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
node scripts/firefox-github-release.js write-updates \
|
|
||||||
"$ADDON_ID" "$VERSION" "$RELEASE_XPI" "$RELEASE_DIR/updates.json"
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 4: Run targeted checks**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
node scripts/test-firefox-github-release.js
|
|
||||||
bash -n scripts/release-firefox-xpi.sh
|
|
||||||
npm test
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: all commands succeed; `dist/firefox/manifest.json` contains the
|
|
||||||
GitHub update URL after the build.
|
|
||||||
|
|
||||||
- [ ] **Step 5: Commit and push**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add firefox/manifest.json scripts/release-firefox-xpi.sh scripts/test-firefox-github-release.js
|
|
||||||
git commit -m "feat: host Firefox updates on GitHub Releases"
|
|
||||||
git push origin main
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 3: Add explicit GitHub publishing and public instructions
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `scripts/publish-firefox-github-release.sh`
|
|
||||||
- Modify: `package.json:8-15`
|
|
||||||
- Modify: `README.md:42-68`
|
|
||||||
- Test: `scripts/publish-firefox-github-release.sh`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- `npm run publish:firefox` calls `scripts/publish-firefox-github-release.sh`.
|
|
||||||
- The script runs the existing local release command, then uploads
|
|
||||||
`verstak-firefox-<version>.xpi` and `updates.json` to release tag
|
|
||||||
`v<version>` in `mirivlad/verstak-browser-extension`.
|
|
||||||
- A pre-existing tag receives asset replacement through `gh release upload --clobber`.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write the shell-contract test**
|
|
||||||
|
|
||||||
Create a Node test that reads the publish script and asserts the mandatory
|
|
||||||
commands are present:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const assert = require('assert');
|
|
||||||
const fs = require('fs');
|
|
||||||
const source = fs.readFileSync('scripts/publish-firefox-github-release.sh', 'utf8');
|
|
||||||
assert.match(source, /gh auth status/);
|
|
||||||
assert.match(source, /gh release create/);
|
|
||||||
assert.match(source, /gh release upload/);
|
|
||||||
assert.match(source, /--clobber/);
|
|
||||||
assert.match(source, /--latest/);
|
|
||||||
```
|
|
||||||
|
|
||||||
Place those assertions in `scripts/test-firefox-github-release.js` and run the
|
|
||||||
test. Expected: it fails before the publish script exists.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Implement an explicit idempotent publisher**
|
|
||||||
|
|
||||||
Create `scripts/publish-firefox-github-release.sh` with this behavior:
|
|
||||||
|
|
||||||
1. Set `ROOT_DIR`, `cd` to it and run `gh auth status`.
|
|
||||||
2. Run `./scripts/release-firefox-xpi.sh`.
|
|
||||||
3. Read `VERSION` from `dist/firefox/manifest.json`, set `TAG="v${VERSION}"`,
|
|
||||||
`REPOSITORY="mirivlad/verstak-browser-extension"`, and locate the two local
|
|
||||||
release assets by exact names.
|
|
||||||
4. Run `gh release view "$TAG" --repo "$REPOSITORY"`; when it is absent, run
|
|
||||||
`gh release create "$TAG" "$XPI" "$UPDATES" --repo "$REPOSITORY" --title
|
|
||||||
"Verstak Browser Extension $VERSION" --generate-notes --latest --target
|
|
||||||
"$(git rev-parse HEAD)"`.
|
|
||||||
5. When the release already exists, run `gh release upload "$TAG" "$XPI"
|
|
||||||
"$UPDATES" --repo "$REPOSITORY" --clobber`.
|
|
||||||
6. Print the GitHub release URL with `gh release view "$TAG" --repo
|
|
||||||
"$REPOSITORY" --json url --jq .url`.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Register the command and document it**
|
|
||||||
|
|
||||||
Add to `package.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"publish:firefox": "./scripts/publish-firefox-github-release.sh"
|
|
||||||
```
|
|
||||||
|
|
||||||
Rewrite the Firefox Release README section so it documents:
|
|
||||||
|
|
||||||
- the public [GitHub Releases](https://github.com/mirivlad/verstak-browser-extension/releases) page;
|
|
||||||
- `VERSTAK_BROWSER_ENV=/path/to/.env npm run publish:firefox`;
|
|
||||||
- that signing remains unlisted with Mozilla but distribution and automatic
|
|
||||||
updates use GitHub Release assets;
|
|
||||||
- that the first `v2.0.3` publish bootstraps the `latest/download/updates.json`
|
|
||||||
endpoint.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Run checks without publishing**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash -n scripts/publish-firefox-github-release.sh
|
|
||||||
node scripts/test-firefox-github-release.js
|
|
||||||
npm test
|
|
||||||
npm run build
|
|
||||||
git diff --check
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: all commands succeed. Do not invoke `npm run publish:firefox` in this
|
|
||||||
task; it creates a public GitHub Release and needs available AMO credentials.
|
|
||||||
|
|
||||||
- [ ] **Step 5: Commit and push**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add README.md package.json scripts/publish-firefox-github-release.sh scripts/test-firefox-github-release.js
|
|
||||||
git commit -m "feat: publish Firefox releases to GitHub"
|
|
||||||
git push origin main
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task 4: Bootstrap and verify the first public update release
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Generated: `release/firefox/verstak-firefox-2.0.3.xpi` (untracked)
|
|
||||||
- Generated: `release/firefox/updates.json` (untracked)
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Consumes the Task 3 `npm run publish:firefox` command and valid AMO signing credentials.
|
|
||||||
- Produces GitHub Release tag `v2.0.3` and public XPI/update-manifest assets.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Verify release credentials before write operations**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gh auth status
|
|
||||||
test -n "${VERSTAK_BROWSER_ENV:-}" || test -f .env
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: authenticated GitHub CLI and a configured environment file path for
|
|
||||||
the existing AMO signing command.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Publish the first release**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run publish:firefox
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: Mozilla signing succeeds, GitHub Release `v2.0.3` is latest, and it
|
|
||||||
contains `verstak-firefox-2.0.3.xpi` plus `updates.json`.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Verify public delivery**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gh release view v2.0.3 --repo mirivlad/verstak-browser-extension --json isLatest,url,assets
|
|
||||||
curl -fsSL https://github.com/mirivlad/verstak-browser-extension/releases/latest/download/updates.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: the release is latest and the JSON contains addon ID
|
|
||||||
`verstak-bridge@verstak.app`, version `2.0.3`, and the versioned GitHub XPI
|
|
||||||
asset URL.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Record release verification without committing assets**
|
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git status --short
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected: no generated `release/` assets are staged or committed.
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
# GitHub Releases for Firefox Updates
|
|
||||||
|
|
||||||
## Decision
|
|
||||||
|
|
||||||
Firefox XPI releases are published as ordinary public GitHub Releases in
|
|
||||||
`mirivlad/verstak-browser-extension`. During the alpha phase every such release
|
|
||||||
is marked as the repository's latest release, including alpha versions. There
|
|
||||||
are no other Verstak users whose automatic updates need a separate stable
|
|
||||||
channel yet.
|
|
||||||
|
|
||||||
## Update protocol
|
|
||||||
|
|
||||||
The Firefox manifest uses this stable endpoint:
|
|
||||||
|
|
||||||
```text
|
|
||||||
https://github.com/mirivlad/verstak-browser-extension/releases/latest/download/updates.json
|
|
||||||
```
|
|
||||||
|
|
||||||
Each release uploads two assets:
|
|
||||||
|
|
||||||
- `verstak-firefox-<version>.xpi`, signed by Mozilla as an unlisted addon;
|
|
||||||
- `updates.json`, whose `update_link` points to that same release tag's XPI
|
|
||||||
asset.
|
|
||||||
|
|
||||||
For version `2.0.3`, the versioned link is:
|
|
||||||
|
|
||||||
```text
|
|
||||||
https://github.com/mirivlad/verstak-browser-extension/releases/download/v2.0.3/verstak-firefox-2.0.3.xpi
|
|
||||||
```
|
|
||||||
|
|
||||||
The stable `latest/download` endpoint lets already installed extensions discover
|
|
||||||
the next version without changing the manifest on each release.
|
|
||||||
|
|
||||||
## Release commands
|
|
||||||
|
|
||||||
`npm run release:firefox` remains the local sign-and-package command. A new
|
|
||||||
explicit `npm run publish:firefox` command runs it, verifies `gh`
|
|
||||||
authentication, creates or reuses the `v<manifest-version>` GitHub Release,
|
|
||||||
and uploads the XPI plus `updates.json`. Re-running the command replaces only
|
|
||||||
those two assets, so a failed network upload can be retried safely.
|
|
||||||
|
|
||||||
Publishing requires the existing AMO credentials used for signing and a
|
|
||||||
GitHub-authenticated `gh` CLI. It does not use the invalid `mirv.top` update
|
|
||||||
endpoint.
|
|
||||||
|
|
||||||
## Bootstrap
|
|
||||||
|
|
||||||
There is currently no GitHub Release in this repository. After the code change,
|
|
||||||
the first `v2.0.3` signed release must be published before shipping a manifest
|
|
||||||
that points to `latest/download/updates.json`; until then that URL is a 404.
|
|
||||||
The current package, Chromium manifest and Firefox manifest all use version
|
|
||||||
`2.0.3`, so the initial tag and XPI asset name are unambiguous.
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
The README links users to the GitHub Releases page for downloading the signed
|
|
||||||
Firefox XPI and documents that releases are currently the auto-update channel.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
- add a focused script test for versioned GitHub URLs and generated
|
|
||||||
`updates.json`;
|
|
||||||
- run the existing extension test suite and build;
|
|
||||||
- run shell syntax validation for the publish script;
|
|
||||||
- verify a published release's assets and `latest/download/updates.json` with
|
|
||||||
unauthenticated HTTPS requests.
|
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
{
|
{
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "Verstak Bridge",
|
"name": "Verstak Bridge",
|
||||||
"version": "2.0.5",
|
"version": "2.0.2",
|
||||||
"description": "Send pages, selections, links, and files to the local Verstak browser inbox.",
|
"description": "Send pages, selections, links, and files to the local Verstak browser inbox.",
|
||||||
"author": "Verstak",
|
"author": "Verstak",
|
||||||
"homepage_url": "https://github.com/mirivlad/verstak-browser-extension",
|
"homepage_url": "https://git.mirv.top/verstak/verstak-browser-extension",
|
||||||
"browser_specific_settings": {
|
"browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
"id": "verstak-bridge@verstak.app",
|
"id": "verstak-bridge@verstak.app",
|
||||||
"strict_min_version": "115.0",
|
"strict_min_version": "115.0",
|
||||||
"update_url": "https://github.com/mirivlad/verstak-browser-extension/releases/latest/download/updates.json",
|
"update_url": "https://mirv.top/verstak/firefox/updates.json",
|
||||||
"data_collection_permissions": {
|
"data_collection_permissions": {
|
||||||
"required": ["none"]
|
"required": ["none"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"permissions": ["alarms", "contextMenus", "idle", "storage", "tabs", "http://127.0.0.1/*", "http://localhost/*"],
|
"permissions": ["alarms", "contextMenus", "idle", "storage", "tabs", "windows", "http://127.0.0.1/*", "http://localhost/*"],
|
||||||
"background": {
|
"background": {
|
||||||
"scripts": ["hostname.js", "activity-tracker.js", "protocol.js", "api.js", "queue.js", "i18n.js", "background.js"]
|
"scripts": ["hostname.js", "activity-tracker.js", "protocol.js", "api.js", "queue.js", "i18n.js", "background.js"]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "verstak-browser-extension",
|
"name": "verstak-browser-extension",
|
||||||
"version": "2.0.5",
|
"version": "2.0.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "verstak-browser-extension",
|
"name": "verstak-browser-extension",
|
||||||
"version": "2.0.5",
|
"version": "2.0.2",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"web-ext": "^8.3.0"
|
"web-ext": "^8.3.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
{
|
{
|
||||||
"name": "verstak-browser-extension",
|
"name": "verstak-browser-extension",
|
||||||
"version": "2.0.5",
|
"version": "2.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Verstak browser capture extension for Chromium and Firefox",
|
"description": "Verstak browser capture extension for Chromium and Firefox",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node scripts/build-extension.js",
|
"build": "node scripts/build-extension.js",
|
||||||
"test": "node scripts/test-manifests.js && node scripts/test-hostname.js && node scripts/test-activity-tracker.js && node scripts/test-protocol.js && node scripts/test-i18n.js && node scripts/test-popup-settings.js && node scripts/test-popup-select-style.js && node scripts/test-popup-catalog-fallback.js && node scripts/test-background-i18n.js && node scripts/test-firefox-github-release.js",
|
"test": "node scripts/test-hostname.js && node scripts/test-activity-tracker.js && node scripts/test-protocol.js && node scripts/test-i18n.js && node scripts/test-popup-settings.js && node scripts/test-popup-catalog-fallback.js && node scripts/test-background-i18n.js",
|
||||||
"sign:firefox": "./scripts/sign-firefox-xpi.sh",
|
"sign:firefox": "./scripts/sign-firefox-xpi.sh",
|
||||||
"release:firefox": "./scripts/release-firefox-xpi.sh",
|
"release:firefox": "./scripts/release-firefox-xpi.sh",
|
||||||
"publish:firefox": "./scripts/publish-firefox-github-release.sh",
|
|
||||||
"publish:github": "./scripts/publish-github-release.sh",
|
|
||||||
"release:package": "./scripts/package-release.sh"
|
"release:package": "./scripts/package-release.sh"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
const REPOSITORY = 'mirivlad/verstak-browser-extension';
|
|
||||||
|
|
||||||
function releaseTag(version) {
|
|
||||||
return `v${version}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function releaseAssetURL(version, assetName) {
|
|
||||||
return `https://github.com/${REPOSITORY}/releases/download/${releaseTag(version)}/${assetName}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateManifest(addonID, version, assetName) {
|
|
||||||
return {
|
|
||||||
addons: {
|
|
||||||
[addonID]: {
|
|
||||||
updates: [{
|
|
||||||
version,
|
|
||||||
update_link: releaseAssetURL(version, assetName),
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeUpdates(addonID, version, assetName, outputPath) {
|
|
||||||
fs.writeFileSync(outputPath, `${JSON.stringify(updateManifest(addonID, version, assetName), null, 2)}\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (require.main === module) {
|
|
||||||
const [, , command, addonID, version, assetName, outputPath] = process.argv;
|
|
||||||
if (command !== 'write-updates' || !addonID || !version || !assetName || !outputPath) {
|
|
||||||
console.error('usage: node scripts/firefox-github-release.js write-updates <addon-id> <version> <asset-name> <output-path>');
|
|
||||||
process.exitCode = 2;
|
|
||||||
} else {
|
|
||||||
writeUpdates(addonID, version, assetName, outputPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { REPOSITORY, releaseTag, releaseAssetURL, updateManifest, writeUpdates };
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
||||||
cd "$ROOT_DIR"
|
|
||||||
|
|
||||||
REPOSITORY="mirivlad/verstak-browser-extension"
|
|
||||||
SOURCE_DIR="${VERSTAK_FIREFOX_SOURCE_DIR:-dist/firefox}"
|
|
||||||
RELEASE_DIR="${VERSTAK_FIREFOX_RELEASE_DIR:-release/firefox}"
|
|
||||||
GIT_BIN="${GIT_BIN:-git}"
|
|
||||||
GH_BIN="${GH_BIN:-gh}"
|
|
||||||
|
|
||||||
if ! command -v "$GH_BIN" >/dev/null; then
|
|
||||||
echo "ERROR: gh CLI is required to publish a GitHub Release" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [[ "$("$GIT_BIN" branch --show-current)" != "main" ]]; then
|
|
||||||
echo "ERROR: GitHub releases must be published from main" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [[ -n "$("$GIT_BIN" status --porcelain)" ]]; then
|
|
||||||
echo "ERROR: 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 "ERROR: local main must match origin/main before publishing a release" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
./scripts/release-firefox-xpi.sh
|
|
||||||
|
|
||||||
VERSION="$(node -e "console.log(require('./${SOURCE_DIR}/manifest.json').version)")"
|
|
||||||
TAG="v${VERSION}"
|
|
||||||
XPI="$RELEASE_DIR/verstak-firefox-${VERSION}.xpi"
|
|
||||||
UPDATES="$RELEASE_DIR/updates.json"
|
|
||||||
|
|
||||||
for artifact in "$XPI" "$UPDATES"; do
|
|
||||||
if [[ ! -f "$artifact" ]]; then
|
|
||||||
echo "ERROR: release artifact not found: $artifact" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if "$GIT_BIN" rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
|
|
||||||
if [[ "$("$GIT_BIN" rev-parse "${TAG}^{commit}")" != "$HEAD" ]]; then
|
|
||||||
echo "ERROR: existing tag $TAG does not point at HEAD" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
"$GIT_BIN" tag -a "$TAG" -m "Release $TAG"
|
|
||||||
"$GIT_BIN" push origin "refs/tags/$TAG"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if "$GH_BIN" release view "$TAG" --repo "$REPOSITORY" >/dev/null 2>&1; then
|
|
||||||
"$GH_BIN" release upload "$TAG" "$XPI" "$UPDATES" --repo "$REPOSITORY" --clobber
|
|
||||||
else
|
|
||||||
"$GH_BIN" release create "$TAG" "$XPI" "$UPDATES" \
|
|
||||||
--repo "$REPOSITORY" \
|
|
||||||
--title "Verstak Browser Extension $VERSION" \
|
|
||||||
--generate-notes \
|
|
||||||
--latest \
|
|
||||||
--verify-tag
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "GitHub release:"
|
|
||||||
"$GH_BIN" release view "$TAG" --repo "$REPOSITORY" --json url --jq .url
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
||||||
VERSION="$(node -p "require('$ROOT/package.json').version")"
|
|
||||||
|
|
||||||
if [[ $# -gt 1 || ( $# -eq 1 && "$1" != "v$VERSION" && "$1" != "$VERSION" ) ]]; then
|
|
||||||
echo "usage: $0 [v$VERSION]" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec "$ROOT/scripts/publish-firefox-github-release.sh"
|
|
||||||
|
|
@ -32,6 +32,7 @@ load_env_file "$ENV_FILE"
|
||||||
SOURCE_DIR="${VERSTAK_FIREFOX_SOURCE_DIR:-dist/firefox}"
|
SOURCE_DIR="${VERSTAK_FIREFOX_SOURCE_DIR:-dist/firefox}"
|
||||||
ARTIFACTS_DIR="${WEB_EXT_ARTIFACTS_DIR:-web-ext-artifacts}"
|
ARTIFACTS_DIR="${WEB_EXT_ARTIFACTS_DIR:-web-ext-artifacts}"
|
||||||
RELEASE_DIR="${VERSTAK_FIREFOX_RELEASE_DIR:-release/firefox}"
|
RELEASE_DIR="${VERSTAK_FIREFOX_RELEASE_DIR:-release/firefox}"
|
||||||
|
UPDATE_BASE_URL="${VERSTAK_FIREFOX_UPDATE_BASE_URL:-https://mirv.top/verstak/firefox}"
|
||||||
|
|
||||||
./scripts/sign-firefox-xpi.sh
|
./scripts/sign-firefox-xpi.sh
|
||||||
|
|
||||||
|
|
@ -53,8 +54,20 @@ mkdir -p "$RELEASE_DIR"
|
||||||
RELEASE_XPI="verstak-firefox-${VERSION}.xpi"
|
RELEASE_XPI="verstak-firefox-${VERSION}.xpi"
|
||||||
cp "$SIGNED_XPI" "$RELEASE_DIR/$RELEASE_XPI"
|
cp "$SIGNED_XPI" "$RELEASE_DIR/$RELEASE_XPI"
|
||||||
|
|
||||||
node scripts/firefox-github-release.js write-updates \
|
cat > "$RELEASE_DIR/updates.json" <<EOF
|
||||||
"$ADDON_ID" "$VERSION" "$RELEASE_XPI" "$RELEASE_DIR/updates.json"
|
{
|
||||||
|
"addons": {
|
||||||
|
"${ADDON_ID}": {
|
||||||
|
"updates": [
|
||||||
|
{
|
||||||
|
"version": "${VERSION}",
|
||||||
|
"update_link": "${UPDATE_BASE_URL}/${RELEASE_XPI}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
echo "Firefox release artifacts:"
|
echo "Firefox release artifacts:"
|
||||||
echo "$RELEASE_DIR/$RELEASE_XPI"
|
echo "$RELEASE_DIR/$RELEASE_XPI"
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
const assert = require('assert');
|
|
||||||
const fs = require('fs');
|
|
||||||
const release = require('./firefox-github-release');
|
|
||||||
const packageManifest = require('../package.json');
|
|
||||||
const chromiumManifest = require('../chromium/manifest.json');
|
|
||||||
const firefoxManifest = require('../firefox/manifest.json');
|
|
||||||
|
|
||||||
const version = '2.0.5';
|
|
||||||
const asset = 'verstak-firefox-2.0.5.xpi';
|
|
||||||
|
|
||||||
assert.equal(packageManifest.version, version);
|
|
||||||
assert.equal(chromiumManifest.version, version);
|
|
||||||
assert.equal(firefoxManifest.version, version);
|
|
||||||
assert.equal(release.releaseTag(version), 'v2.0.5');
|
|
||||||
assert.equal(
|
|
||||||
release.releaseAssetURL(version, asset),
|
|
||||||
'https://github.com/mirivlad/verstak-browser-extension/releases/download/v2.0.5/verstak-firefox-2.0.5.xpi',
|
|
||||||
);
|
|
||||||
assert.deepEqual(release.updateManifest('verstak-bridge@verstak.app', version, asset), {
|
|
||||||
addons: {
|
|
||||||
'verstak-bridge@verstak.app': {
|
|
||||||
updates: [{
|
|
||||||
version: '2.0.5',
|
|
||||||
update_link: 'https://github.com/mirivlad/verstak-browser-extension/releases/download/v2.0.5/verstak-firefox-2.0.5.xpi',
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.equal(firefoxManifest.homepage_url, 'https://github.com/mirivlad/verstak-browser-extension');
|
|
||||||
assert.equal(
|
|
||||||
firefoxManifest.browser_specific_settings.gecko.update_url,
|
|
||||||
'https://github.com/mirivlad/verstak-browser-extension/releases/latest/download/updates.json',
|
|
||||||
);
|
|
||||||
|
|
||||||
const publisher = fs.readFileSync('scripts/publish-firefox-github-release.sh', 'utf8');
|
|
||||||
assert.match(publisher, /auth status/);
|
|
||||||
assert.match(publisher, /branch --show-current/);
|
|
||||||
assert.match(publisher, /tag -a/);
|
|
||||||
assert.match(publisher, /push origin/);
|
|
||||||
assert.match(publisher, /release create/);
|
|
||||||
assert.match(publisher, /release upload/);
|
|
||||||
assert.match(publisher, /--clobber/);
|
|
||||||
assert.match(publisher, /--latest/);
|
|
||||||
|
|
||||||
const genericPublisher = fs.readFileSync('scripts/publish-github-release.sh', 'utf8');
|
|
||||||
assert.match(genericPublisher, /publish-firefox-github-release\.sh/);
|
|
||||||
|
|
||||||
console.log('Firefox GitHub release metadata tests passed');
|
|
||||||
|
|
@ -29,7 +29,7 @@ assert.ok(Object.values(ru).every((value) => typeof value === 'string'));
|
||||||
const tEn = i18n.createTranslator({ en, ru }, 'en');
|
const tEn = i18n.createTranslator({ en, ru }, 'en');
|
||||||
const tRu = i18n.createTranslator({ en, ru }, 'ru');
|
const tRu = i18n.createTranslator({ en, ru }, 'ru');
|
||||||
assert.strictEqual(tRu('status.queued'), 'В очереди до запуска Верстака');
|
assert.strictEqual(tRu('status.queued'), 'В очереди до запуска Верстака');
|
||||||
assert.strictEqual(tEn('error.sendCapture'), 'Could not send the capture. Please try again.');
|
assert.strictEqual(tEn('error.value', { error: 'offline' }), 'Error: offline');
|
||||||
assert.strictEqual(tRu('missing', null, 'Fallback'), 'Fallback');
|
assert.strictEqual(tRu('missing', null, 'Fallback'), 'Fallback');
|
||||||
assert.strictEqual(tRu('missing.key'), 'missing.key');
|
assert.strictEqual(tRu('missing.key'), 'missing.key');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
const assert = require('assert');
|
|
||||||
|
|
||||||
const chromium = require('../chromium/manifest.json');
|
|
||||||
const firefox = require('../firefox/manifest.json');
|
|
||||||
|
|
||||||
assert.equal(chromium.version, firefox.version, 'browser packages must use the same version');
|
|
||||||
assert.equal(
|
|
||||||
firefox.permissions.includes('windows'),
|
|
||||||
false,
|
|
||||||
'Firefox does not accept the Chromium-only windows permission'
|
|
||||||
);
|
|
||||||
assert.equal(chromium.permissions.includes('windows'), true, 'Chromium still needs the windows permission');
|
|
||||||
|
|
||||||
console.log('browser manifest compatibility tests passed');
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
const assert = require('assert');
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
const css = fs.readFileSync(path.join(__dirname, '..', 'shared', 'popup', 'popup.css'), 'utf8');
|
|
||||||
|
|
||||||
assert.match(css, /select\s*\{[^}]*appearance:\s*none/, 'popup select must hide the native arrow');
|
|
||||||
assert.match(css, /select option\s*\{[^}]*background/, 'popup options must use the extension surface');
|
|
||||||
|
|
||||||
console.log('browser extension popup select style test passed');
|
|
||||||
|
|
@ -65,8 +65,6 @@ const elements = {};
|
||||||
});
|
});
|
||||||
|
|
||||||
let savedSettings = null;
|
let savedSettings = null;
|
||||||
let nextRequestError = null;
|
|
||||||
const technicalWarnings = [];
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
settings: {
|
settings: {
|
||||||
receiverUrl: 'http://127.0.0.1:47731/api/browser-inbox/v1/captures',
|
receiverUrl: 'http://127.0.0.1:47731/api/browser-inbox/v1/captures',
|
||||||
|
|
@ -82,11 +80,6 @@ const browser = {
|
||||||
runtime: {
|
runtime: {
|
||||||
getURL(relativePath) { return `extension://${relativePath}`; },
|
getURL(relativePath) { return `extension://${relativePath}`; },
|
||||||
sendMessage(message) {
|
sendMessage(message) {
|
||||||
if (nextRequestError) {
|
|
||||||
const error = nextRequestError;
|
|
||||||
nextRequestError = null;
|
|
||||||
return Promise.reject(new Error(error));
|
|
||||||
}
|
|
||||||
if (message.action === 'getState') return Promise.resolve(initialState);
|
if (message.action === 'getState') return Promise.resolve(initialState);
|
||||||
if (message.action === 'saveSettings') {
|
if (message.action === 'saveSettings') {
|
||||||
savedSettings = message.settings;
|
savedSettings = message.settings;
|
||||||
|
|
@ -114,24 +107,10 @@ function fetchCatalog(url) {
|
||||||
}
|
}
|
||||||
const i18nPath = path.join(__dirname, '..', 'shared', 'i18n.js');
|
const i18nPath = path.join(__dirname, '..', 'shared', 'i18n.js');
|
||||||
const popupPath = path.join(__dirname, '..', 'shared', 'popup', 'popup.js');
|
const popupPath = path.join(__dirname, '..', 'shared', 'popup', 'popup.js');
|
||||||
const popupSource = fs.readFileSync(popupPath, 'utf8');
|
const context = vm.createContext({ browser, console, document, Promise, btoa, fetch: fetchCatalog, navigator: { language: 'en-US' } });
|
||||||
assert.equal(/setStatus\(\s*(?:error\b|err\b|String\()/.test(popupSource), false);
|
|
||||||
const context = vm.createContext({
|
|
||||||
browser,
|
|
||||||
console: {
|
|
||||||
error: console.error,
|
|
||||||
log: console.log,
|
|
||||||
warn(...args) { technicalWarnings.push(args.map(String).join(' ')); },
|
|
||||||
},
|
|
||||||
document,
|
|
||||||
Promise,
|
|
||||||
btoa,
|
|
||||||
fetch: fetchCatalog,
|
|
||||||
navigator: { language: 'en-US' },
|
|
||||||
});
|
|
||||||
context.globalThis = context;
|
context.globalThis = context;
|
||||||
vm.runInContext(fs.readFileSync(i18nPath, 'utf8'), context, { filename: i18nPath });
|
vm.runInContext(fs.readFileSync(i18nPath, 'utf8'), context, { filename: i18nPath });
|
||||||
vm.runInContext(popupSource, context, { filename: popupPath });
|
vm.runInContext(fs.readFileSync(popupPath, 'utf8'), context, { filename: popupPath });
|
||||||
|
|
||||||
async function flush() {
|
async function flush() {
|
||||||
for (let i = 0; i < 16; i += 1) await Promise.resolve();
|
for (let i = 0; i < 16; i += 1) await Promise.resolve();
|
||||||
|
|
@ -177,14 +156,6 @@ async function flush() {
|
||||||
assert.strictEqual(savedSettings.language, 'en');
|
assert.strictEqual(savedSettings.language, 'en');
|
||||||
assert.strictEqual(savedSettings.passiveActivityEnabled, true);
|
assert.strictEqual(savedSettings.passiveActivityEnabled, true);
|
||||||
assert.deepStrictEqual(Array.from(savedSettings.passiveActivityExcludedDomains), ['youtube.com', 'x.com']);
|
assert.deepStrictEqual(Array.from(savedSettings.passiveActivityExcludedDomains), ['youtube.com', 'x.com']);
|
||||||
|
|
||||||
nextRequestError = '[plugin:verstak.browser-inbox] captures.create failed: receiver unavailable';
|
|
||||||
elements['capture-page'].click();
|
|
||||||
await flush();
|
|
||||||
assert.strictEqual(elements.status.textContent, 'Could not send the capture. Please try again.');
|
|
||||||
assert.equal(elements.status.textContent.includes('[plugin:'), false);
|
|
||||||
assert.ok(technicalWarnings.some((message) => message.includes('captures.create failed')));
|
|
||||||
|
|
||||||
console.log('browser extension popup localization/settings tests passed');
|
console.log('browser extension popup localization/settings tests passed');
|
||||||
})().catch((error) => {
|
})().catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "https://raw.githubusercontent.com/mirivlad/verstak-sdk/main/schemas/hostname-normalization-v1.json",
|
"$id": "https://git.mirv.top/verstak/verstak-sdk/schemas/hostname-normalization-v1.json",
|
||||||
"title": "Verstak canonical hostname normalization v1 test vectors",
|
"title": "Verstak canonical hostname normalization v1 test vectors",
|
||||||
"description": "Canonical hostnames are lowercase ASCII A-labels without a port or trailing DNS dot. Bare hostnames accept DNS names, IPv4, bracketed IPv6, localhost, and internal single-label names. URL inputs accept only HTTP(S). Invalid or excessively long input normalizes to an empty string.",
|
"description": "Canonical hostnames are lowercase ASCII A-labels without a port or trailing DNS dot. Bare hostnames accept DNS names, IPv4, bracketed IPv6, localhost, and internal single-label names. URL inputs accept only HTTP(S). Invalid or excessively long input normalizes to an empty string.",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,7 @@
|
||||||
"error.chooseFile": "Choose a file first",
|
"error.chooseFile": "Choose a file first",
|
||||||
"error.fileTooLarge": "File is too large for browser capture",
|
"error.fileTooLarge": "File is too large for browser capture",
|
||||||
"error.invalidReceiverUrl": "Receiver URL must start with http:// or https://",
|
"error.invalidReceiverUrl": "Receiver URL must start with http:// or https://",
|
||||||
"error.loadState": "Could not load the extension state. Please try again.",
|
"error.value": "Error: {error}",
|
||||||
"error.saveSettings": "Could not save settings. Please try again.",
|
|
||||||
"error.sendCapture": "Could not send the capture. Please try again.",
|
|
||||||
"error.readFile": "Could not read the file. Choose it again.",
|
|
||||||
"context.sendPage": "Send page to Verstak",
|
"context.sendPage": "Send page to Verstak",
|
||||||
"context.sendSelection": "Send selection to Verstak",
|
"context.sendSelection": "Send selection to Verstak",
|
||||||
"context.sendLink": "Send link to Verstak"
|
"context.sendLink": "Send link to Verstak"
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,7 @@
|
||||||
"error.chooseFile": "Сначала выберите файл",
|
"error.chooseFile": "Сначала выберите файл",
|
||||||
"error.fileTooLarge": "Файл слишком велик для отправки из браузера",
|
"error.fileTooLarge": "Файл слишком велик для отправки из браузера",
|
||||||
"error.invalidReceiverUrl": "URL приёмника должен начинаться с http:// или https://",
|
"error.invalidReceiverUrl": "URL приёмника должен начинаться с http:// или https://",
|
||||||
"error.loadState": "Не удалось загрузить данные расширения. Повторите попытку.",
|
"error.value": "Ошибка: {error}",
|
||||||
"error.saveSettings": "Не удалось сохранить настройки. Повторите попытку.",
|
|
||||||
"error.sendCapture": "Не удалось отправить материал. Повторите попытку.",
|
|
||||||
"error.readFile": "Не удалось прочитать файл. Выберите его ещё раз.",
|
|
||||||
"context.sendPage": "Отправить страницу в Верстак",
|
"context.sendPage": "Отправить страницу в Верстак",
|
||||||
"context.sendSelection": "Отправить выделение в Верстак",
|
"context.sendSelection": "Отправить выделение в Верстак",
|
||||||
"context.sendLink": "Отправить ссылку в Верстак"
|
"context.sendLink": "Отправить ссылку в Верстак"
|
||||||
|
|
|
||||||
|
|
@ -145,17 +145,7 @@ select {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
border: 1px solid #374151;
|
border: 1px solid #374151;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 7px 30px 7px 8px;
|
padding: 7px 8px;
|
||||||
appearance: none;
|
|
||||||
background-color: #0f172a;
|
|
||||||
background-image: linear-gradient(45deg, transparent 50%, #94a3b8 50%), linear-gradient(135deg, #94a3b8 50%, transparent 50%);
|
|
||||||
background-position: calc(100% - 16px) 50%, calc(100% - 11px) 50%;
|
|
||||||
background-size: 5px 5px, 5px 5px;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
color: #e5e7eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
select option {
|
|
||||||
background: #0f172a;
|
background: #0f172a;
|
||||||
color: #e5e7eb;
|
color: #e5e7eb;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,11 +118,6 @@
|
||||||
statusEl.textContent = text;
|
statusEl.textContent = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
function reportError(key, fallback, error) {
|
|
||||||
console.warn('[verstak.popup] request failed:', error);
|
|
||||||
setStatus(t(key, null, fallback));
|
|
||||||
}
|
|
||||||
|
|
||||||
function request(message) {
|
function request(message) {
|
||||||
return Promise.resolve(ext.runtime.sendMessage(message)).then(function (result) {
|
return Promise.resolve(ext.runtime.sendMessage(message)).then(function (result) {
|
||||||
if (result && result.error) throw new Error(result.error);
|
if (result && result.error) throw new Error(result.error);
|
||||||
|
|
@ -153,7 +148,7 @@
|
||||||
applyLocale(state.settings && state.settings.language || 'system');
|
applyLocale(state.settings && state.settings.language || 'system');
|
||||||
return state;
|
return state;
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
reportError('error.loadState', 'Could not load the extension state. Please try again.', error);
|
setStatus(error && error.message ? error.message : String(error));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,7 +174,7 @@
|
||||||
if (successMessage) setStatus(t('status.saved', null, 'Saved'));
|
if (successMessage) setStatus(t('status.saved', null, 'Saved'));
|
||||||
return state;
|
return state;
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
reportError('error.saveSettings', 'Could not save settings. Please try again.', error);
|
setStatus(error && error.message ? error.message : String(error));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,7 +188,7 @@
|
||||||
setStatus(t('status.done', null, 'Done'));
|
setStatus(t('status.done', null, 'Done'));
|
||||||
}
|
}
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
reportError('error.sendCapture', 'Could not send the capture. Please try again.', error);
|
setStatus(error && error.message ? error.message : String(error));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -223,7 +218,7 @@
|
||||||
fileDataBase64: arrayBufferToBase64(results[0])
|
fileDataBase64: arrayBufferToBase64(results[0])
|
||||||
});
|
});
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
reportError('error.readFile', 'Could not read the file. Choose it again.', error);
|
setStatus(error && error.message ? error.message : String(error));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue