Weaver has three build lanes: unpacked local testing, signed test CRXs, and public Store ZIPs. The unpacked lane supports both live development and one-time builds. Local and signed test builds are deliberately test-branded; Chrome Web Store and Microsoft Edge Add-ons packages are always production-branded.
| Goal | Command | What to use |
|---|---|---|
| Live development | pnpm run dev:test |
Load local_builds/vite-test-unpacked/. |
| One-time unpacked build | pnpm run build:test |
Load local_builds/vite-test-build/. |
| Managed-browser testing | pnpm run package:test |
Build on a trusted machine; transfer only the generated CRX. |
| Public release | pnpm run release:chrome or pnpm run release:edge |
Review the metadata; upload the target ZIP from artifacts/. |
Browser policy determines whether unpacked extensions or local CRXs are allowed. These workflows do not bypass device policy.
A machine that only receives and installs a signed test CRX does not need the source checkout, pnpm, dependencies, or signing key.
Run all commands from the repository root. Use the exact Node.js version recorded in .node-version. On a machine used to develop or build Weaver, enable Corepack if pnpm is not already available, then install the exact locked dependencies. These commands are the same in macOS/Linux shells and Windows PowerShell:
corepack enable
pnpm install --frozen-lockfile
Generated builds, CRXs, environment files, and common private-key formats are ignored by Git. This is a safety net, not a security boundary: keep signing keys outside the checkout, and never force-add them or generated packages.
Weaver is a single-owner repository. Ordinary development does not use review pull requests: validate locally, commit the intended changes, merge any temporary task branch into main, and push main. Task branches are optional organization, not an approval boundary.
GitHub CI is an automated verification signal rather than a merge gate. It runs after changes reach main; it also runs on Dependabot pull requests so those machine-generated proposals can be checked before the owner chooses whether to apply them. Store packaging, tags, uploads, and publication remain separate explicit release actions.
Dependabot checks npm and GitHub Actions twice a year. Routine minor and patch updates are grouped to reduce maintenance noise, while major npm upgrades remain separate so their migration and validation can be handled deliberately. React, React DOM, and their type packages are grouped because the runtime packages must use exactly matching versions. @types/node stays on the same major as the Node.js version in .node-version.
Dependabot pull requests are automated maintenance proposals, not a human review workflow. The owner may use their checks as evidence, apply the coordinated update directly to main, and close the proposals as superseded. Security-update proposals may appear outside the twice-yearly routine schedule when GitHub finds a relevant vulnerability.
Version-update cooldowns do not delay Dependabot security updates. Repository administrators must enable Dependabot alerts and security updates separately in GitHub settings; .github/dependabot.yml does not enable those features.
When updating dependencies locally, regenerate and install the lockfile with the pinned Node.js and pnpm versions, then run:
pnpm audit --audit-level high
pnpm run check
pnpm run build:test
pnpm run package:preview:chrome
pnpm run package:preview:edge
Start the test-branded Vite development build:
pnpm run dev:test
pnpm run dev is an alias for the same command. Vite writes the live unpacked extension to local_builds/vite-test-unpacked/. Use one of these package scripts rather than invoking bare Vite when you expect test branding; Vite’s default development mode intentionally falls back to production branding.
On managed Chrome profiles, Load unpacked can be disabled independently of signed test-CRX installation. If Chrome reports Extension installation is blocked by policy, an approved signed extension ID does not make the unpacked lane available. Use a development profile or device whose policy permits unpacked extensions, or stay on the signed test-CRX workflow.
In a Chromium browser:
chrome://extensions.local_builds/vite-test-unpacked/.chrome://extensions after manifest or background-worker changes when Chrome does not pick them up automatically.The Extensions page and toolbar show the violet test icon with its light-blue heart, the name Weaver Test - Window & Tab Manager, and the tooltip Open Weaver Test. This keeps a development install visibly distinct from the public extension.
For a one-time unpacked test build without a development server, run:
pnpm run build:test
Load local_builds/vite-test-build/. The live server and one-time build use separate directories so running build:test cannot clear the extension files currently served by dev:test.
Use a signed test CRX when unpacked extensions are unavailable but browser policy permits installing a CRX. Build it on a trusted development/build machine, then transfer only the CRX to the target device.
Use a dedicated test key, never a Store signing identity, and keep the private key outside the checkout. The paths and ID values below are placeholders; replace them with your own values rather than entering them literally.
If you already have a dedicated Weaver test key, skip key generation and reuse it. Generating a new key creates a different extension ID, a separate Chrome installation, and cannot update an extension installed with the old key.
Only when intentionally creating a new test identity, generate an unencrypted 2048-bit RSA PEM. On macOS or Linux with OpenSSL:
test_key_path="/absolute/secure/path/weaver-test.pem"
openssl genrsa -out "$test_key_path" 2048
chmod 600 "$test_key_path"
On Windows PowerShell, use OpenSSL if it is already installed from a trusted source:
$testKeyPath = 'C:\absolute\secure\path\weaver-test.pem'
openssl genrsa -out $testKeyPath 2048
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
icacls $testKeyPath /inheritance:r /grant:r "${currentUser}:(F)"
icacls $testKeyPath
Review the final icacls output and make sure the key is not accessible to broad user groups. The builder verifies that a Windows key is a regular RSA file outside the checkout, but Windows ACLs do not map to the POSIX owner-mode check and must be reviewed separately.
The builder expects an unencrypted PEM, so protect it with filesystem access controls and a secure backup. Copy the same PEM only to another trusted packaging machine when that machine must produce updates for the same test identity. Reusing it on macOS and Windows preserves one test extension ID; creating a key on each machine does not.
Derive the stable Chrome extension ID once for that key:
WEAVER_TEST_KEY_PATH="/absolute/secure/path/weaver-test.pem" \
pnpm run test:extension-id
Record the printed 32-character ID for future builds. The extension ID is a public identifier; the PEM is the private signing key and must remain secret.
The equivalent PowerShell flow is:
$testKeyPath = 'C:\absolute\secure\path\weaver-test.pem'
$env:WEAVER_TEST_KEY_PATH = $testKeyPath
pnpm run test:extension-id
Replace both placeholder values before running this command:
WEAVER_TEST_KEY_PATH="/absolute/secure/path/weaver-test.pem" \
WEAVER_TEST_EXTENSION_ID="paste-the-printed-32-character-id-here" \
pnpm run package:test
package:test runs the shared test, typecheck, lint, and format checks before the packaging safety checks. It does not replace the user-run browser acceptance step for the resulting CRX.
On PowerShell, set the same values for the current terminal session:
$env:WEAVER_TEST_KEY_PATH = $testKeyPath
$env:WEAVER_TEST_EXTENSION_ID = 'paste-the-printed-32-character-id-here'
pnpm run package:test
If browser discovery fails, set an absolute executable path before packaging. For example:
$env:WEAVER_CHROME_PATH = 'C:\Program Files\Google\Chrome\Application\chrome.exe'
pnpm run package:test
By default, the builder creates a chronological version in the form 99.<years-since-2000>.<UTC-day-of-year>.<UTC-minute-of-day>, such as 99.26.220.801. If you build twice in one minute or need to exceed an already-installed version, add an explicit, higher four-part version to the command. For example, if 99.26.220.801 is installed, use WEAVER_TEST_VERSION=99.26.220.802. The builder never overwrites an existing CRX, and Chrome requires a higher version for an in-place update.
In PowerShell, set an explicit version for the current terminal and run the package command again:
$env:WEAVER_TEST_VERSION = '99.26.220.802'
pnpm run package:test
Clear the session settings and overrides when packaging is finished:
Remove-Item Env:WEAVER_TEST_KEY_PATH -ErrorAction SilentlyContinue
Remove-Item Env:WEAVER_TEST_EXTENSION_ID -ErrorAction SilentlyContinue
Remove-Item Env:WEAVER_TEST_VERSION -ErrorAction SilentlyContinue
Remove-Item Env:WEAVER_CHROME_PATH -ErrorAction SilentlyContinue
Before returning the CRX, the builder automatically:
local_builds/weaver-test-<version>.crx;Set WEAVER_CHROME_PATH to an absolute Chrome, Chromium, or Brave executable on the build machine if the builder cannot find one automatically.
Transfer only the generated CRX to the target device; do not transfer the PEM, source checkout, or build environment. Compare the transferred file’s SHA-256 checksum with the value reported by the builder before installing it.
On macOS, recompute the checksum using the exact path printed by the builder:
shasum -a 256 "/path/printed/by/the/builder.crx"
On Linux:
sha256sum "/path/printed/by/the/builder.crx"
On Windows PowerShell:
Get-FileHash -Algorithm SHA256 'C:\path\printed\by\the\builder.crx'
Recompute it again after transfer and compare the full hexadecimal digest, not only the filename or file size.
Where browser policy allows it, drag the newer CRX onto chrome://extensions. Reusing the same key keeps the extension ID stable, and a higher version updates the existing test installation in place. The test extension has no automatic update URL, so each update must be built, transferred, and installed manually. If local CRX installation is blocked, the device’s browser policy or administrator must provide the installation path.
Create the production Chrome package:
pnpm run release:chrome
Create the production Edge package:
pnpm run release:edge
The package commands are identical in macOS/Linux shells and Windows PowerShell. Build on a trusted source machine. Upload only the Store ZIP; retain its JSON metadata with the release record so the reviewed checksum and source provenance remain available.
Before either release, confirm the intended branch and commit and verify the release version in package.json. Strict Store packaging requires the exact Node.js version in .node-version, the exact pnpm version in packageManager, and a clean Git working tree. If a v<version> tag already exists, it must identify the commit being packaged; this prevents a version from being silently rebuilt from different source.
Each command runs the relevant tests, type checks, lint/format checks, target-specific production build, and package validation. Use these release commands rather than the lower-level zip:* scripts so the correct target is rebuilt before packaging.
Review the target-specific ZIP and its matching JSON metadata in artifacts/ before upload. The metadata records the ZIP checksum, source commit, clean-tree result, version-tag resolution, lockfile checksum, Node and pnpm versions, and creation time. Packaging never overwrites a different artifact for the same target and version; an identical rerun is idempotent. Do not upload a test CRX, preview ZIP, or unpacked test directory.
Treat source validation and browser acceptance as separate gates. For each target ZIP:
chrome://extensions or edge://extensions, enable Developer mode, and load that extracted directory as an unpacked extension.The Store adds distribution signing later, but this check exercises the exact files that will be uploaded. Record the tested target, version, ZIP checksum, browser version, and result in the release notes or review record.
The generated Store ZIP contains no private key, manifest key, or test branding. It uses the normal Weaver name, tooltip, and production icons; the Store handles distribution signing. Upload and publication remain explicit manual release steps.
assets/extension-icons/test/, not public/, so Vite does not copy it into ordinary builds.test selects test branding, and pnpm run dev deliberately aliases that mode. Every other mode—including Vite’s default development mode, unknown modes, and edge—uses production branding.local_builds/; production builds use dist/.--preview packages to exercise both target packaging contracts without weakening the strict release gate. Preview packages are never release candidates.scripts/build-release.mjs.For ordinary source validation without packaging, run:
pnpm run validate
pnpm run build:edge