32 Commits

Author SHA1 Message Date
Renovate Bot
63cfe3f87c Update dependency tailwindcss to v4
Some checks failed
renovate/artifacts Artifact file update failure
CI / build (pull_request) Failing after 4m43s
2025-12-30 00:03:29 +00:00
931c8f4370 Merge pull request 'Update https://git.quad4.io/actions/setup-pnpm action to v4.2.0' (#16) from renovate/https-git.quad4.io-actions-setup-pnpm-4.x into master
All checks were successful
CI / build (push) Successful in 1m26s
renovate / renovate (push) Successful in 5m32s
Reviewed-on: #16
2025-12-29 20:07:19 +00:00
2c5e258934 Merge pull request 'Update https://git.quad4.io/actions/setup-node action to v4.4.0' (#15) from renovate/https-git.quad4.io-actions-setup-node-4.x into master
All checks were successful
CI / build (push) Successful in 1m36s
renovate / renovate (push) Successful in 6m26s
Reviewed-on: #15
2025-12-29 20:07:11 +00:00
Renovate Bot
4ba4ecd722 Update https://git.quad4.io/actions/setup-pnpm action to v4.2.0
All checks were successful
CI / build (pull_request) Successful in 9m47s
2025-12-29 20:04:50 +00:00
Renovate Bot
812b47457d Update https://git.quad4.io/actions/setup-node action to v4.4.0
All checks were successful
CI / build (pull_request) Successful in 9m44s
2025-12-29 20:04:49 +00:00
5ec4eddd14 Merge pull request 'Update https://git.quad4.io/actions/checkout action to v4.3.1' (#11) from renovate/https-git.quad4.io-actions-checkout-4.x into master
Some checks failed
CI / build (push) Failing after 1m32s
renovate / renovate (push) Failing after 3m51s
Reviewed-on: #11
2025-12-29 20:01:39 +00:00
37012ac80a Merge pull request 'Update https://git.quad4.io/actions/setup-pnpm digest to a7487c7' (#9) from renovate/https-git.quad4.io-actions-setup-pnpm-digest into master
All checks were successful
renovate / renovate (push) Successful in 6m3s
CI / build (push) Successful in 9m44s
Reviewed-on: #9
2025-12-29 20:01:30 +00:00
69e551986a Merge pull request 'Update https://git.quad4.io/actions/setup-go action to v5.6.0' (#12) from renovate/https-git.quad4.io-actions-setup-go-5.x into master
Some checks failed
CI / build (push) Successful in 1m37s
renovate / renovate (push) Failing after 4m14s
Reviewed-on: #12
2025-12-29 20:01:20 +00:00
ivan
f8254c735b Merge pull request 'Update ghcr.io/renovatebot/renovate Docker tag to v42.66.11' (#10) from renovate/ghcr.io-renovatebot-renovate-42.x into master
All checks were successful
renovate / renovate (push) Successful in 4m57s
CI / build (push) Successful in 9m45s
Reviewed-on: #10
2025-12-29 00:21:15 +00:00
Renovate Bot
046e55f6d0 Update https://git.quad4.io/actions/setup-go action to v5.6.0
All checks were successful
CI / build (pull_request) Successful in 1m44s
2025-12-29 00:04:00 +00:00
Renovate Bot
21bef6d2d7 Update https://git.quad4.io/actions/checkout action to v4.3.1
Some checks failed
CI / build (pull_request) Failing after 1m45s
2025-12-29 00:03:57 +00:00
Renovate Bot
c5c6bea16e Update ghcr.io/renovatebot/renovate Docker tag to v42.66.11
All checks were successful
CI / build (pull_request) Successful in 9m44s
2025-12-29 00:03:56 +00:00
Renovate Bot
0865a2bcbc Update https://git.quad4.io/actions/setup-pnpm digest to a7487c7
All checks were successful
CI / build (pull_request) Successful in 9m48s
2025-12-28 14:51:41 +00:00
cf676eb14c Update Gitea publish workflow by adding input for tag name in release process
All checks were successful
renovate / renovate (push) Successful in 5m48s
CI / build (push) Successful in 9m44s
2025-12-28 08:47:40 -06:00
ivan
f8733d8e6f Merge pull request 'Update ghcr.io/renovatebot/renovate Docker tag to v42' (#4) from renovate/ghcr.io-renovatebot-renovate-42.x into master
Some checks failed
renovate / renovate (push) Has been cancelled
CI / build (push) Successful in 9m45s
Reviewed-on: #4
2025-12-28 06:59:44 +00:00
8204dbf811 Update Taskfile.yml
Some checks failed
renovate / renovate (push) Has been cancelled
CI / build (push) Has been cancelled
2025-12-28 00:56:49 -06:00
0fb281a783 Update Gitea workflow to specify exact version for task setup action and adjust versioning format
All checks were successful
CI / build (push) Successful in 1m0s
renovate / renovate (push) Successful in 5m11s
2025-12-28 00:50:08 -06:00
31948b8f9e Update Gitea workflow to use a custom action URL for checkout
Some checks failed
CI / build (push) Successful in 1m7s
renovate / renovate (push) Failing after 4m7s
2025-12-27 23:28:20 -06:00
6c23005368 Update Renovate configuration for Gitea workflows by adding regex managers for custom action URLs, including support for checkout, Go, Node, pnpm, task setup, SBOM generation, and release actions.
Some checks failed
renovate / renovate (push) Failing after 1s
CI / build (push) Successful in 1m9s
2025-12-27 23:27:40 -06:00
8998cc8253 Update Gitea CI workflow to use custom action URLs for checkout, Go, Node, and pnpm setup
Some checks failed
renovate / renovate (push) Failing after 1s
CI / build (push) Successful in 1m51s
2025-12-27 23:24:54 -06:00
0f250bc715 Remove
Some checks failed
CI / build (push) Failing after 6s
renovate / renovate (push) Failing after 6s
2025-12-27 23:19:06 -06:00
b9119877d0 Update Gitea workflows to use actions for pnpm, task setup, SBOM generation, and release creation 2025-12-27 23:18:59 -06:00
4ecf6a921c Update version number to 0.4.1 in package.json
Some checks failed
Publish / publish (push) Failing after 41s
renovate / renovate (push) Successful in 1m1s
CI / build (push) Successful in 1m11s
2025-12-27 23:02:08 -06:00
1d8fadd835 Add Gitea workflow for publishing with Go and Node setup
All checks were successful
renovate / renovate (push) Successful in 49s
CI / build (push) Successful in 1m14s
2025-12-27 23:01:26 -06:00
55eaf28514 Update error handling in verifier and improve error message display in VerificationModal
All checks were successful
CI / build (push) Successful in 1m0s
renovate / renovate (push) Successful in 1m14s
- Updated the error handling in loadVerifier to log detailed errors and provide clearer feedback on WASM script loading issues.
- Modified the error message display in VerificationModal to better format and separate error details for improved user experience.
2025-12-27 22:45:57 -06:00
1687815aad Update WebAssembly verifier and JavaScript execution environment
All checks were successful
CI / build (push) Successful in 1m0s
renovate / renovate (push) Successful in 1m5s
- Updated the integrity hash for the WebAssembly module in verifier.ts to ensure security compliance.
- Made various formatting improvements in wasm_exec.js for consistency, including string usage and whitespace cleanup.
- Removed redundant comments and streamlined function definitions for better readability.
2025-12-27 22:43:32 -06:00
ff35c0ec01 Add static/verifier to .prettierignore for improved formatting control 2025-12-27 22:42:12 -06:00
7cd4c58927 Update global and API rate limits for improved performance
All checks were successful
renovate / renovate (push) Successful in 58s
CI / build (push) Successful in 59s
- Increased GlobalRateLimit from 500 to 2000 to better handle higher traffic.
- Raised APIRateLimit from 150 to 500 to enhance API responsiveness.
2025-12-27 22:38:37 -06:00
9fb84eb228 Update global and API rate limits for enhanced performance
- Increased GlobalRateLimit from 100 to 500 to accommodate higher traffic.
- Raised APIRateLimit from 30 to 150 to improve API responsiveness and user experience.
2025-12-27 22:38:27 -06:00
0dfbacce37 Update README and frontend files for improved clarity and functionality
All checks were successful
CI / build (push) Successful in 1m0s
renovate / renovate (push) Successful in 1m6s
- Added a note in the README about using Taskfile for project management.
- Removed the crossOrigin attribute from the WebAssembly fetch request in verifier.ts for security compliance.
- Refactored the wasm_exec.js file for consistency in string usage and improved readability.
- Cleaned up whitespace in the SRI generation script to enhance code clarity.
2025-12-27 22:37:27 -06:00
e2c80671fa Add "Task" build and development process with Taskfile integration
Some checks failed
CI / build (push) Failing after 30s
renovate / renovate (push) Successful in 1m25s
- Added Taskfile.yml to streamline build, development, and testing tasks.
- Updated README to reflect new build instructions and development environment setup using `go-task`.
- Included `.taskfile.env` and `.task` in .dockerignore and .gitignore for better environment management.
- Modified asset loading in verifier.ts to include integrity and cross-origin attributes for security.
- Updated SRI generation script to handle both directory and single file inputs for improved flexibility.
2025-12-27 22:35:12 -06:00
Renovate Bot
fe0dd110f4 Update ghcr.io/renovatebot/renovate Docker tag to v42
All checks were successful
CI / build (pull_request) Successful in 10m58s
2025-12-27 22:30:17 +00:00
18 changed files with 494 additions and 318 deletions

View File

@@ -26,3 +26,5 @@ test-hashes.json
test_updater.txt test_updater.txt
test_handlers_hashes.json test_handlers_hashes.json
.taskfile.env
.task

View File

@@ -16,21 +16,21 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: https://git.quad4.io/actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Setup Go - name: Setup Go
uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 uses: https://git.quad4.io/actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0
with: with:
go-version: '1.25.4' go-version: '1.25.4'
cache: true cache: true
- name: Setup Node - name: Setup Node
uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0 uses: https://git.quad4.io/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: with:
node-version: '22' node-version: '22'
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d # v4.1.0 uses: https://git.quad4.io/actions/setup-pnpm@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
with: with:
version: 9 version: 9

View File

@@ -0,0 +1,61 @@
name: Publish
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag_name:
description: 'Tag name for the release'
required: true
type: string
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: https://git.quad4.io/actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup Go
uses: https://git.quad4.io/actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version: '1.25.4'
cache: true
- name: Setup Node
uses: https://git.quad4.io/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '22'
- name: Setup pnpm
uses: https://git.quad4.io/actions/setup-pnpm@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
with:
version: 10
- name: Install Task
uses: https://git.quad4.io/actions/setup-task@0ab1b2a65bc55236a3bc64cde78f80e20e8885c2 # v1
with:
version: '3.46.3'
- name: Generate SBOM
uses: https://git.quad4.io/actions/gh-gomod-generate-sbom@efc74245d6802c8cefd925620515442756c70d8f # v2
with:
version: v1
args: mod -licenses -json -output bom.json
- name: Build Everything
run: task all
env:
NODE_ENV: production
- name: Create Release and Upload Assets
uses: https://git.quad4.io/actions/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
with:
tag_name: ${{ github.event.inputs.tag_name || github.ref_name }}
files: |
software-station
bom.json
env:
GITHUB_TOKEN: ${{ secrets.GITEA_TOKEN }}

View File

@@ -12,9 +12,9 @@ on:
jobs: jobs:
renovate: renovate:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: ghcr.io/renovatebot/renovate:37.440.7 container: ghcr.io/renovatebot/renovate:42.66.11
steps: steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - uses: https://git.quad4.io/actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Fetch remote configuration - name: Fetch remote configuration
run: curl -sL https://git.quad4.io/Quad4-Extra/renovate-config/raw/branch/master/config.js -o config.js run: curl -sL https://git.quad4.io/Quad4-Extra/renovate-config/raw/branch/master/config.js -o config.js
- run: renovate - run: renovate

2
.gitignore vendored
View File

@@ -31,3 +31,5 @@ test_handlers_hashes.json
.DS_Store .DS_Store
Thumbs.db Thumbs.db
.taskfile.env
.task

View File

@@ -1,74 +0,0 @@
.PHONY: all build-frontend build-go build-wasm clean release run lint scan check format tidy test test-wasm dev docker-build
BINARY_NAME=software-station
FRONTEND_DIR=frontend
BUILD_DIR=build
VERIFIER_DIR=software-verifier
WASM_OUT=frontend/static/verifier
VERSION=$(shell grep '"version":' $(FRONTEND_DIR)/package.json | cut -d'"' -f4)
BUILD_DATE=$(shell date -u +'%Y-%m-%dT%H:%M:%SZ')
VCS_REF=$(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
all: build-wasm build-frontend build-go
dev:
@echo "Starting development environment..."
@pnpm --prefix $(FRONTEND_DIR) dev & go run main.go
build-wasm:
@echo "Building WASM verifier..."
mkdir -p $(WASM_OUT)
cp "$(shell go env GOROOT)/lib/wasm/wasm_exec.js" $(WASM_OUT)/wasm_exec.js
cd $(VERIFIER_DIR) && GOOS=js GOARCH=wasm go build -o ../$(WASM_OUT)/verifier.wasm .
build-frontend: build-wasm
cd $(FRONTEND_DIR) && pnpm install && pnpm build
@echo "Injecting SRI hashes..."
go run scripts/sri-gen/main.go
build-go:
go build -o $(BINARY_NAME) main.go
release: build-frontend
CGO_ENABLED=0 go build -ldflags="-s -w" -o $(BINARY_NAME) main.go
@echo "Release build complete: $(BINARY_NAME)"
run: all
./$(BINARY_NAME)
format:
go fmt ./...
cd $(FRONTEND_DIR) && pnpm run format
lint:
go vet ./...
cd $(FRONTEND_DIR) && pnpm run lint
scan:
gosec ./...
check:
cd $(FRONTEND_DIR) && pnpm run check
tidy: format lint check
test: test-wasm
go test -v -coverpkg=./... ./...
test-wasm:
cd $(VERIFIER_DIR) && go test -v ./...
clean:
rm -rf $(FRONTEND_DIR)/build
rm -rf $(WASM_OUT)
rm -f $(BINARY_NAME)
rm -f coverage.out
docker-build:
docker build \
--build-arg VERSION=$(VERSION) \
--build-arg BUILD_DATE=$(BUILD_DATE) \
--build-arg VCS_REF=$(VCS_REF) \
-t $(BINARY_NAME):latest \
-t $(BINARY_NAME):$(VERSION) .

View File

@@ -62,18 +62,16 @@ A software distribution platform for assets built and hosted on Gitea. Built wit
### Installation ### Installation
1. **Build the Frontend**: We use [Taskfile](https://taskfile.dev/) to manage the project.
1. **Build Everything (WASM, Frontend, Backend)**:
```bash ```bash
cd frontend go-task all
pnpm install
pnpm build
cd ..
``` ```
2. **Build and Run the Backend**: 2. **Run the Application**:
```bash ```bash
go build -o software-station . go-task run
./software-station -t YOUR_TOKEN -s https://your-gitea-instance.com -ua-blocklist ua-blocklist.txt
``` ```
### Docker (Recommended) ### Docker (Recommended)
@@ -107,23 +105,18 @@ The frontend uses Tailwind CSS. You can customize the look and feel in `frontend
## Development ## Development
Run the backend and frontend separately for a better development experience: Run the backend and frontend simultaneously with live reload (uses parallel tasks):
```bash ```bash
# Backend (with live reload using Air or just go run) go-task dev
go run main.go
# Frontend (Vite dev server)
cd frontend
pnpm dev
``` ```
## Testing ## Testing
We maintain a high test coverage (>60%). Run the test suite: Run the full test suite (including WASM tests):
```bash ```bash
go test -v -coverpkg=./... ./... go-task test
``` ```
## License ## License

160
Taskfile.yml Normal file
View File

@@ -0,0 +1,160 @@
version: '3'
vars:
BINARY_NAME: software-station
FRONTEND_DIR: frontend
BUILD_DIR: build
VERIFIER_DIR: software-verifier
WASM_OUT: frontend/static/verifier
VERSION:
sh: grep '"version":' frontend/package.json | cut -d'"' -f4
BUILD_DATE:
sh: date -u +'%Y-%m-%dT%H:%M:%SZ'
VCS_REF:
sh: git rev-parse --short HEAD 2>/dev/null || echo "unknown"
tasks:
default:
desc: Build everything
cmds:
- task: all
all:
desc: Build everything
deps: [build-go]
dev:
desc: Start development environment (parallel)
deps: [build-wasm]
cmds:
- task: dev-frontend
- task: dev-backend
parallel: true
dev-frontend:
internal: true
dir: "{{.FRONTEND_DIR}}"
cmds:
- pnpm dev
dev-backend:
internal: true
cmds:
- go run main.go
preview:
desc: Preview the production build
dir: "{{.FRONTEND_DIR}}"
cmds:
- pnpm preview
build-wasm:
desc: Build WASM verifier
sources:
- "{{.VERIFIER_DIR}}/**/*.go"
generates:
- "{{.WASM_OUT}}/verifier.wasm"
- "{{.WASM_OUT}}/wasm_exec.js"
cmds:
- mkdir -p {{.WASM_OUT}}
- cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" {{.WASM_OUT}}/wasm_exec.js
- cd {{.VERIFIER_DIR}} && GOOS=js GOARCH=wasm go build -o ../{{.WASM_OUT}}/verifier.wasm .
- go run scripts/sri-gen/main.go frontend/src/lib/verifier.ts
silent: true
build-frontend:
desc: Build Svelte frontend
deps: [build-wasm]
sources:
- "{{.FRONTEND_DIR}}/**/*"
- exclude: "{{.FRONTEND_DIR}}/node_modules/**/*"
- exclude: "{{.FRONTEND_DIR}}/build/**/*"
generates:
- "{{.FRONTEND_DIR}}/build/**/*"
cmds:
- cd {{.FRONTEND_DIR}} && pnpm install && pnpm build
- go run scripts/sri-gen/main.go
build-go:
desc: Build main Go application
deps: [build-frontend]
sources:
- "**/*.go"
- exclude: "{{.VERIFIER_DIR}}/**/*"
- exclude: "scripts/**/*"
generates:
- "{{.BINARY_NAME}}"
cmds:
- go build -o {{.BINARY_NAME}} main.go
release:
desc: Build release binary
deps: [build-frontend]
cmds:
- CGO_ENABLED=0 go build -ldflags="-s -w" -o {{.BINARY_NAME}} main.go
run:
desc: Run the application
deps: [all]
cmds:
- ./{{.BINARY_NAME}}
format:
desc: Format code
cmds:
- go fmt ./...
- cd {{.FRONTEND_DIR}} && pnpm run format
lint:
desc: Lint code
cmds:
- go vet ./...
- cd {{.FRONTEND_DIR}} && pnpm run lint
scan:
desc: Security scan
cmds:
- gosec ./...
check:
desc: Type check frontend
cmds:
- cd {{.FRONTEND_DIR}} && pnpm run check
tidy:
desc: Run format, lint, and check
cmds:
- task: format
- task: lint
- task: check
test:
desc: Run tests
deps: [test-wasm]
cmds:
- go test -v -coverpkg=./... ./...
test-wasm:
desc: Run WASM tests
dir: "{{.VERIFIER_DIR}}"
cmds:
- go test -v ./...
clean:
desc: Clean build artifacts
cmds:
- rm -rf {{.FRONTEND_DIR}}/build
- rm -rf {{.WASM_OUT}}
- rm -f {{.BINARY_NAME}}
- rm -f coverage.out
docker-build:
desc: Build Docker image
cmds:
- |
docker build \
--build-arg VERSION={{.VERSION}} \
--build-arg BUILD_DATE={{.BUILD_DATE}} \
--build-arg VCS_REF={{.VCS_REF}} \
-t {{.BINARY_NAME}}:latest \
-t {{.BINARY_NAME}}:{{.VERSION}} .

3
frontend/.gitignore vendored
View File

@@ -21,3 +21,6 @@ Thumbs.db
# Vite # Vite
vite.config.js.timestamp-* vite.config.js.timestamp-*
vite.config.ts.timestamp-* vite.config.ts.timestamp-*
.taskfile.env
.task

View File

@@ -3,4 +3,5 @@ node_modules
build build
dist dist
.DS_Store .DS_Store
static/verifier

View File

@@ -1,7 +1,7 @@
{ {
"name": "frontend", "name": "frontend",
"private": true, "private": true,
"version": "0.4.0", "version": "0.4.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
@@ -36,7 +36,7 @@
"svelte-check": "^4.3.5", "svelte-check": "^4.3.5",
"svelte-eslint-parser": "^1.4.1", "svelte-eslint-parser": "^1.4.1",
"svelte-i18n": "^4.0.1", "svelte-i18n": "^4.0.1",
"tailwindcss": "^3.4.19", "tailwindcss": "^4.0.0",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"vite": "^7.3.0" "vite": "^7.3.0"
} }

View File

@@ -400,7 +400,16 @@
</div> </div>
<div class="text-center px-4"> <div class="text-center px-4">
<p class="font-bold text-lg leading-tight">Verification Failed</p> <p class="font-bold text-lg leading-tight">Verification Failed</p>
<p class="text-xs text-muted-foreground mt-1">{errorMessage}</p> {#if errorMessage.includes('(')}
<p class="text-sm font-bold mt-1">
{errorMessage.split(' (')[0]}
</p>
<p class="text-[10px] text-muted-foreground mt-0.5 italic">
({errorMessage.split(' (')[1]}
</p>
{:else}
<p class="text-xs text-muted-foreground mt-1">{errorMessage}</p>
{/if}
</div> </div>
</div> </div>

View File

@@ -12,14 +12,19 @@ export async function loadVerifier() {
script.integrity = 'sha384-PWCs+V4BDf9yY1yjkD/p+9xNEs4iEbuvq+HezAOJiY3XL5GI6VyJXMsvnjiwNbce'; script.integrity = 'sha384-PWCs+V4BDf9yY1yjkD/p+9xNEs4iEbuvq+HezAOJiY3XL5GI6VyJXMsvnjiwNbce';
script.crossOrigin = 'anonymous'; script.crossOrigin = 'anonymous';
script.onload = () => resolve(); script.onload = () => resolve();
script.onerror = () => reject(new Error('Failed to load WASM executor script')); script.onerror = (e) => {
console.error('WASM executor script load error:', e);
reject(new Error('Failed to load WASM executor script (SRI mismatch or network error)'));
};
document.head.appendChild(script); document.head.appendChild(script);
}); });
} }
const go = new (window as any).Go(); const go = new (window as any).Go();
const result = await WebAssembly.instantiateStreaming( const result = await WebAssembly.instantiateStreaming(
fetch('/verifier/verifier.wasm'), fetch('/verifier/verifier.wasm', {
integrity: 'sha384-r/ciHEFn1SsJLxB/24OIDDJAb/oBbYWq5Tp/WksIkL3Kcsspi27fO7Hak5nZi8j4',
}),
go.importObject go.importObject
); );
go.run(result.instance); go.run(result.instance);

View File

Binary file not shown.

View File

@@ -2,30 +2,22 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
'use strict'; "use strict";
(() => { (() => {
const enosys = () => { const enosys = () => {
const err = new Error('not implemented'); const err = new Error("not implemented");
err.code = 'ENOSYS'; err.code = "ENOSYS";
return err; return err;
}; };
if (!globalThis.fs) { if (!globalThis.fs) {
let outputBuf = ''; let outputBuf = "";
globalThis.fs = { globalThis.fs = {
constants: { constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1, O_DIRECTORY: -1 }, // unused
O_WRONLY: -1,
O_RDWR: -1,
O_CREAT: -1,
O_TRUNC: -1,
O_APPEND: -1,
O_EXCL: -1,
O_DIRECTORY: -1,
}, // unused
writeSync(fd, buf) { writeSync(fd, buf) {
outputBuf += decoder.decode(buf); outputBuf += decoder.decode(buf);
const nl = outputBuf.lastIndexOf('\n'); const nl = outputBuf.lastIndexOf("\n");
if (nl != -1) { if (nl != -1) {
console.log(outputBuf.substring(0, nl)); console.log(outputBuf.substring(0, nl));
outputBuf = outputBuf.substring(nl + 1); outputBuf = outputBuf.substring(nl + 1);
@@ -40,147 +32,81 @@
const n = this.writeSync(fd, buf); const n = this.writeSync(fd, buf);
callback(null, n); callback(null, n);
}, },
chmod(path, mode, callback) { chmod(path, mode, callback) { callback(enosys()); },
callback(enosys()); chown(path, uid, gid, callback) { callback(enosys()); },
}, close(fd, callback) { callback(enosys()); },
chown(path, uid, gid, callback) { fchmod(fd, mode, callback) { callback(enosys()); },
callback(enosys()); fchown(fd, uid, gid, callback) { callback(enosys()); },
}, fstat(fd, callback) { callback(enosys()); },
close(fd, callback) { fsync(fd, callback) { callback(null); },
callback(enosys()); ftruncate(fd, length, callback) { callback(enosys()); },
}, lchown(path, uid, gid, callback) { callback(enosys()); },
fchmod(fd, mode, callback) { link(path, link, callback) { callback(enosys()); },
callback(enosys()); lstat(path, callback) { callback(enosys()); },
}, mkdir(path, perm, callback) { callback(enosys()); },
fchown(fd, uid, gid, callback) { open(path, flags, mode, callback) { callback(enosys()); },
callback(enosys()); read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
}, readdir(path, callback) { callback(enosys()); },
fstat(fd, callback) { readlink(path, callback) { callback(enosys()); },
callback(enosys()); rename(from, to, callback) { callback(enosys()); },
}, rmdir(path, callback) { callback(enosys()); },
fsync(fd, callback) { stat(path, callback) { callback(enosys()); },
callback(null); symlink(path, link, callback) { callback(enosys()); },
}, truncate(path, length, callback) { callback(enosys()); },
ftruncate(fd, length, callback) { unlink(path, callback) { callback(enosys()); },
callback(enosys()); utimes(path, atime, mtime, callback) { callback(enosys()); },
},
lchown(path, uid, gid, callback) {
callback(enosys());
},
link(path, link, callback) {
callback(enosys());
},
lstat(path, callback) {
callback(enosys());
},
mkdir(path, perm, callback) {
callback(enosys());
},
open(path, flags, mode, callback) {
callback(enosys());
},
read(fd, buffer, offset, length, position, callback) {
callback(enosys());
},
readdir(path, callback) {
callback(enosys());
},
readlink(path, callback) {
callback(enosys());
},
rename(from, to, callback) {
callback(enosys());
},
rmdir(path, callback) {
callback(enosys());
},
stat(path, callback) {
callback(enosys());
},
symlink(path, link, callback) {
callback(enosys());
},
truncate(path, length, callback) {
callback(enosys());
},
unlink(path, callback) {
callback(enosys());
},
utimes(path, atime, mtime, callback) {
callback(enosys());
},
}; };
} }
if (!globalThis.process) { if (!globalThis.process) {
globalThis.process = { globalThis.process = {
getuid() { getuid() { return -1; },
return -1; getgid() { return -1; },
}, geteuid() { return -1; },
getgid() { getegid() { return -1; },
return -1; getgroups() { throw enosys(); },
},
geteuid() {
return -1;
},
getegid() {
return -1;
},
getgroups() {
throw enosys();
},
pid: -1, pid: -1,
ppid: -1, ppid: -1,
umask() { umask() { throw enosys(); },
throw enosys(); cwd() { throw enosys(); },
}, chdir() { throw enosys(); },
cwd() { }
throw enosys();
},
chdir() {
throw enosys();
},
};
} }
if (!globalThis.path) { if (!globalThis.path) {
globalThis.path = { globalThis.path = {
resolve(...pathSegments) { resolve(...pathSegments) {
return pathSegments.join('/'); return pathSegments.join("/");
}, }
}; }
} }
if (!globalThis.crypto) { if (!globalThis.crypto) {
throw new Error( throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
'globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)'
);
} }
if (!globalThis.performance) { if (!globalThis.performance) {
throw new Error( throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
'globalThis.performance is not available, polyfill required (performance.now only)'
);
} }
if (!globalThis.TextEncoder) { if (!globalThis.TextEncoder) {
throw new Error('globalThis.TextEncoder is not available, polyfill required'); throw new Error("globalThis.TextEncoder is not available, polyfill required");
} }
if (!globalThis.TextDecoder) { if (!globalThis.TextDecoder) {
throw new Error('globalThis.TextDecoder is not available, polyfill required'); throw new Error("globalThis.TextDecoder is not available, polyfill required");
} }
const encoder = new TextEncoder('utf-8'); const encoder = new TextEncoder("utf-8");
const decoder = new TextDecoder('utf-8'); const decoder = new TextDecoder("utf-8");
globalThis.Go = class { globalThis.Go = class {
constructor() { constructor() {
this.argv = ['js']; this.argv = ["js"];
this.env = {}; this.env = {};
this.exit = (code) => { this.exit = (code) => {
if (code !== 0) { if (code !== 0) {
console.warn('exit code:', code); console.warn("exit code:", code);
} }
}; };
this._exitPromise = new Promise((resolve) => { this._exitPromise = new Promise((resolve) => {
@@ -193,17 +119,17 @@
const setInt64 = (addr, v) => { const setInt64 = (addr, v) => {
this.mem.setUint32(addr + 0, v, true); this.mem.setUint32(addr + 0, v, true);
this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
}; }
const setInt32 = (addr, v) => { const setInt32 = (addr, v) => {
this.mem.setUint32(addr + 0, v, true); this.mem.setUint32(addr + 0, v, true);
}; }
const getInt64 = (addr) => { const getInt64 = (addr) => {
const low = this.mem.getUint32(addr + 0, true); const low = this.mem.getUint32(addr + 0, true);
const high = this.mem.getInt32(addr + 4, true); const high = this.mem.getInt32(addr + 4, true);
return low + high * 4294967296; return low + high * 4294967296;
}; }
const loadValue = (addr) => { const loadValue = (addr) => {
const f = this.mem.getFloat64(addr, true); const f = this.mem.getFloat64(addr, true);
@@ -216,12 +142,12 @@
const id = this.mem.getUint32(addr, true); const id = this.mem.getUint32(addr, true);
return this._values[id]; return this._values[id];
}; }
const storeValue = (addr, v) => { const storeValue = (addr, v) => {
const nanHead = 0x7ff80000; const nanHead = 0x7FF80000;
if (typeof v === 'number' && v !== 0) { if (typeof v === "number" && v !== 0) {
if (isNaN(v)) { if (isNaN(v)) {
this.mem.setUint32(addr + 4, nanHead, true); this.mem.setUint32(addr + 4, nanHead, true);
this.mem.setUint32(addr, 0, true); this.mem.setUint32(addr, 0, true);
@@ -249,30 +175,30 @@
this._goRefCounts[id]++; this._goRefCounts[id]++;
let typeFlag = 0; let typeFlag = 0;
switch (typeof v) { switch (typeof v) {
case 'object': case "object":
if (v !== null) { if (v !== null) {
typeFlag = 1; typeFlag = 1;
} }
break; break;
case 'string': case "string":
typeFlag = 2; typeFlag = 2;
break; break;
case 'symbol': case "symbol":
typeFlag = 3; typeFlag = 3;
break; break;
case 'function': case "function":
typeFlag = 4; typeFlag = 4;
break; break;
} }
this.mem.setUint32(addr + 4, nanHead | typeFlag, true); this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
this.mem.setUint32(addr, id, true); this.mem.setUint32(addr, id, true);
}; }
const loadSlice = (addr) => { const loadSlice = (addr) => {
const array = getInt64(addr + 0); const array = getInt64(addr + 0);
const len = getInt64(addr + 8); const len = getInt64(addr + 8);
return new Uint8Array(this._inst.exports.mem.buffer, array, len); return new Uint8Array(this._inst.exports.mem.buffer, array, len);
}; }
const loadSliceOfValues = (addr) => { const loadSliceOfValues = (addr) => {
const array = getInt64(addr + 0); const array = getInt64(addr + 0);
@@ -282,18 +208,18 @@
a[i] = loadValue(array + i * 8); a[i] = loadValue(array + i * 8);
} }
return a; return a;
}; }
const loadString = (addr) => { const loadString = (addr) => {
const saddr = getInt64(addr + 0); const saddr = getInt64(addr + 0);
const len = getInt64(addr + 8); const len = getInt64(addr + 8);
return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
}; }
const testCallExport = (a, b) => { const testCallExport = (a, b) => {
this._inst.exports.testExport0(); this._inst.exports.testExport0();
return this._inst.exports.testExport(a, b); return this._inst.exports.testExport(a, b);
}; }
const timeOrigin = Date.now() - performance.now(); const timeOrigin = Date.now() - performance.now();
this.importObject = { this.importObject = {
@@ -308,7 +234,7 @@
// This changes the SP, thus we have to update the SP used by the imported function. // This changes the SP, thus we have to update the SP used by the imported function.
// func wasmExit(code int32) // func wasmExit(code int32)
'runtime.wasmExit': (sp) => { "runtime.wasmExit": (sp) => {
sp >>>= 0; sp >>>= 0;
const code = this.mem.getInt32(sp + 8, true); const code = this.mem.getInt32(sp + 8, true);
this.exited = true; this.exited = true;
@@ -321,7 +247,7 @@
}, },
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
'runtime.wasmWrite': (sp) => { "runtime.wasmWrite": (sp) => {
sp >>>= 0; sp >>>= 0;
const fd = getInt64(sp + 8); const fd = getInt64(sp + 8);
const p = getInt64(sp + 16); const p = getInt64(sp + 16);
@@ -330,50 +256,47 @@
}, },
// func resetMemoryDataView() // func resetMemoryDataView()
'runtime.resetMemoryDataView': (sp) => { "runtime.resetMemoryDataView": (sp) => {
sp >>>= 0; sp >>>= 0;
this.mem = new DataView(this._inst.exports.mem.buffer); this.mem = new DataView(this._inst.exports.mem.buffer);
}, },
// func nanotime1() int64 // func nanotime1() int64
'runtime.nanotime1': (sp) => { "runtime.nanotime1": (sp) => {
sp >>>= 0; sp >>>= 0;
setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
}, },
// func walltime() (sec int64, nsec int32) // func walltime() (sec int64, nsec int32)
'runtime.walltime': (sp) => { "runtime.walltime": (sp) => {
sp >>>= 0; sp >>>= 0;
const msec = new Date().getTime(); const msec = (new Date).getTime();
setInt64(sp + 8, msec / 1000); setInt64(sp + 8, msec / 1000);
this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
}, },
// func scheduleTimeoutEvent(delay int64) int32 // func scheduleTimeoutEvent(delay int64) int32
'runtime.scheduleTimeoutEvent': (sp) => { "runtime.scheduleTimeoutEvent": (sp) => {
sp >>>= 0; sp >>>= 0;
const id = this._nextCallbackTimeoutID; const id = this._nextCallbackTimeoutID;
this._nextCallbackTimeoutID++; this._nextCallbackTimeoutID++;
this._scheduledTimeouts.set( this._scheduledTimeouts.set(id, setTimeout(
id, () => {
setTimeout( this._resume();
() => { while (this._scheduledTimeouts.has(id)) {
// for some reason Go failed to register the timeout event, log and try again
// (temporary workaround for https://github.com/golang/go/issues/28975)
console.warn("scheduleTimeoutEvent: missed timeout event");
this._resume(); this._resume();
while (this._scheduledTimeouts.has(id)) { }
// for some reason Go failed to register the timeout event, log and try again },
// (temporary workaround for https://github.com/golang/go/issues/28975) getInt64(sp + 8),
console.warn('scheduleTimeoutEvent: missed timeout event'); ));
this._resume();
}
},
getInt64(sp + 8)
)
);
this.mem.setInt32(sp + 16, id, true); this.mem.setInt32(sp + 16, id, true);
}, },
// func clearTimeoutEvent(id int32) // func clearTimeoutEvent(id int32)
'runtime.clearTimeoutEvent': (sp) => { "runtime.clearTimeoutEvent": (sp) => {
sp >>>= 0; sp >>>= 0;
const id = this.mem.getInt32(sp + 8, true); const id = this.mem.getInt32(sp + 8, true);
clearTimeout(this._scheduledTimeouts.get(id)); clearTimeout(this._scheduledTimeouts.get(id));
@@ -381,13 +304,13 @@
}, },
// func getRandomData(r []byte) // func getRandomData(r []byte)
'runtime.getRandomData': (sp) => { "runtime.getRandomData": (sp) => {
sp >>>= 0; sp >>>= 0;
crypto.getRandomValues(loadSlice(sp + 8)); crypto.getRandomValues(loadSlice(sp + 8));
}, },
// func finalizeRef(v ref) // func finalizeRef(v ref)
'syscall/js.finalizeRef': (sp) => { "syscall/js.finalizeRef": (sp) => {
sp >>>= 0; sp >>>= 0;
const id = this.mem.getUint32(sp + 8, true); const id = this.mem.getUint32(sp + 8, true);
this._goRefCounts[id]--; this._goRefCounts[id]--;
@@ -400,13 +323,13 @@
}, },
// func stringVal(value string) ref // func stringVal(value string) ref
'syscall/js.stringVal': (sp) => { "syscall/js.stringVal": (sp) => {
sp >>>= 0; sp >>>= 0;
storeValue(sp + 24, loadString(sp + 8)); storeValue(sp + 24, loadString(sp + 8));
}, },
// func valueGet(v ref, p string) ref // func valueGet(v ref, p string) ref
'syscall/js.valueGet': (sp) => { "syscall/js.valueGet": (sp) => {
sp >>>= 0; sp >>>= 0;
const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
sp = this._inst.exports.getsp() >>> 0; // see comment above sp = this._inst.exports.getsp() >>> 0; // see comment above
@@ -414,31 +337,31 @@
}, },
// func valueSet(v ref, p string, x ref) // func valueSet(v ref, p string, x ref)
'syscall/js.valueSet': (sp) => { "syscall/js.valueSet": (sp) => {
sp >>>= 0; sp >>>= 0;
Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
}, },
// func valueDelete(v ref, p string) // func valueDelete(v ref, p string)
'syscall/js.valueDelete': (sp) => { "syscall/js.valueDelete": (sp) => {
sp >>>= 0; sp >>>= 0;
Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
}, },
// func valueIndex(v ref, i int) ref // func valueIndex(v ref, i int) ref
'syscall/js.valueIndex': (sp) => { "syscall/js.valueIndex": (sp) => {
sp >>>= 0; sp >>>= 0;
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
}, },
// valueSetIndex(v ref, i int, x ref) // valueSetIndex(v ref, i int, x ref)
'syscall/js.valueSetIndex': (sp) => { "syscall/js.valueSetIndex": (sp) => {
sp >>>= 0; sp >>>= 0;
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
}, },
// func valueCall(v ref, m string, args []ref) (ref, bool) // func valueCall(v ref, m string, args []ref) (ref, bool)
'syscall/js.valueCall': (sp) => { "syscall/js.valueCall": (sp) => {
sp >>>= 0; sp >>>= 0;
try { try {
const v = loadValue(sp + 8); const v = loadValue(sp + 8);
@@ -456,7 +379,7 @@
}, },
// func valueInvoke(v ref, args []ref) (ref, bool) // func valueInvoke(v ref, args []ref) (ref, bool)
'syscall/js.valueInvoke': (sp) => { "syscall/js.valueInvoke": (sp) => {
sp >>>= 0; sp >>>= 0;
try { try {
const v = loadValue(sp + 8); const v = loadValue(sp + 8);
@@ -473,7 +396,7 @@
}, },
// func valueNew(v ref, args []ref) (ref, bool) // func valueNew(v ref, args []ref) (ref, bool)
'syscall/js.valueNew': (sp) => { "syscall/js.valueNew": (sp) => {
sp >>>= 0; sp >>>= 0;
try { try {
const v = loadValue(sp + 8); const v = loadValue(sp + 8);
@@ -490,13 +413,13 @@
}, },
// func valueLength(v ref) int // func valueLength(v ref) int
'syscall/js.valueLength': (sp) => { "syscall/js.valueLength": (sp) => {
sp >>>= 0; sp >>>= 0;
setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
}, },
// valuePrepareString(v ref) (ref, int) // valuePrepareString(v ref) (ref, int)
'syscall/js.valuePrepareString': (sp) => { "syscall/js.valuePrepareString": (sp) => {
sp >>>= 0; sp >>>= 0;
const str = encoder.encode(String(loadValue(sp + 8))); const str = encoder.encode(String(loadValue(sp + 8)));
storeValue(sp + 16, str); storeValue(sp + 16, str);
@@ -504,20 +427,20 @@
}, },
// valueLoadString(v ref, b []byte) // valueLoadString(v ref, b []byte)
'syscall/js.valueLoadString': (sp) => { "syscall/js.valueLoadString": (sp) => {
sp >>>= 0; sp >>>= 0;
const str = loadValue(sp + 8); const str = loadValue(sp + 8);
loadSlice(sp + 16).set(str); loadSlice(sp + 16).set(str);
}, },
// func valueInstanceOf(v ref, t ref) bool // func valueInstanceOf(v ref, t ref) bool
'syscall/js.valueInstanceOf': (sp) => { "syscall/js.valueInstanceOf": (sp) => {
sp >>>= 0; sp >>>= 0;
this.mem.setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16) ? 1 : 0); this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
}, },
// func copyBytesToGo(dst []byte, src ref) (int, bool) // func copyBytesToGo(dst []byte, src ref) (int, bool)
'syscall/js.copyBytesToGo': (sp) => { "syscall/js.copyBytesToGo": (sp) => {
sp >>>= 0; sp >>>= 0;
const dst = loadSlice(sp + 8); const dst = loadSlice(sp + 8);
const src = loadValue(sp + 32); const src = loadValue(sp + 32);
@@ -532,7 +455,7 @@
}, },
// func copyBytesToJS(dst ref, src []byte) (int, bool) // func copyBytesToJS(dst ref, src []byte) (int, bool)
'syscall/js.copyBytesToJS': (sp) => { "syscall/js.copyBytesToJS": (sp) => {
sp >>>= 0; sp >>>= 0;
const dst = loadValue(sp + 8); const dst = loadValue(sp + 8);
const src = loadSlice(sp + 16); const src = loadSlice(sp + 16);
@@ -546,21 +469,20 @@
this.mem.setUint8(sp + 48, 1); this.mem.setUint8(sp + 48, 1);
}, },
debug: (value) => { "debug": (value) => {
console.log(value); console.log(value);
}, },
}, }
}; };
} }
async run(instance) { async run(instance) {
if (!(instance instanceof WebAssembly.Instance)) { if (!(instance instanceof WebAssembly.Instance)) {
throw new Error('Go.run: WebAssembly.Instance expected'); throw new Error("Go.run: WebAssembly.Instance expected");
} }
this._inst = instance; this._inst = instance;
this.mem = new DataView(this._inst.exports.mem.buffer); this.mem = new DataView(this._inst.exports.mem.buffer);
this._values = [ this._values = [ // JS values that Go currently has references to, indexed by reference id
// JS values that Go currently has references to, indexed by reference id
NaN, NaN,
0, 0,
null, null,
@@ -570,8 +492,7 @@
this, this,
]; ];
this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
this._ids = new Map([ this._ids = new Map([ // mapping from JS values to reference ids
// mapping from JS values to reference ids
[0, 1], [0, 1],
[null, 2], [null, 2],
[true, 3], [true, 3],
@@ -579,7 +500,7 @@
[globalThis, 5], [globalThis, 5],
[this, 6], [this, 6],
]); ]);
this._idPool = []; // unused ids that have been garbage collected this._idPool = []; // unused ids that have been garbage collected
this.exited = false; // whether the Go program has exited this.exited = false; // whether the Go program has exited
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
@@ -587,7 +508,7 @@
const strPtr = (str) => { const strPtr = (str) => {
const ptr = offset; const ptr = offset;
const bytes = encoder.encode(str + '\0'); const bytes = encoder.encode(str + "\0");
new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
offset += bytes.length; offset += bytes.length;
if (offset % 8 !== 0) { if (offset % 8 !== 0) {
@@ -621,7 +542,7 @@
// Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr. // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
const wasmMinDataAddr = 4096 + 8192; const wasmMinDataAddr = 4096 + 8192;
if (offset >= wasmMinDataAddr) { if (offset >= wasmMinDataAddr) {
throw new Error('total length of command line and environment variables exceeds limit'); throw new Error("total length of command line and environment variables exceeds limit");
} }
this._inst.exports.run(argc, argv); this._inst.exports.run(argc, argv);
@@ -633,7 +554,7 @@
_resume() { _resume() {
if (this.exited) { if (this.exited) {
throw new Error('Go program has already exited'); throw new Error("Go program has already exited");
} }
this._inst.exports.resume(); this._inst.exports.resume();
if (this.exited) { if (this.exited) {
@@ -650,5 +571,5 @@
return event.result; return event.result;
}; };
} }
}; }
})(); })();

View File

@@ -25,9 +25,9 @@ const (
HeavyDownloaderLimit = rate.Limit(256 * KB) // 256KB/s HeavyDownloaderLimit = rate.Limit(256 * KB) // 256KB/s
// Rate Limiting // Rate Limiting
GlobalRateLimit = 100 GlobalRateLimit = 2000
GlobalRateWindow = 1 * time.Minute GlobalRateWindow = 1 * time.Minute
APIRateLimit = 30 APIRateLimit = 500
APIRateWindow = 1 * time.Minute APIRateWindow = 1 * time.Minute
) )

View File

@@ -1,3 +1,48 @@
{ {
"$schema": "https://docs.renovatebot.com/renovate-schema.json" "$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
],
"regexManagers": [
{
"fileMatch": ["^\\.gitea/workflows/.*\\.yml$"],
"matchStrings": [
"uses: https://git\\.quad4\\.io/actions/(?<packageName>checkout|setup-go|setup-node)@(?<currentDigest>[a-f0-9]{40}) # (?<currentValue>v[0-9]+(\\.[0-9]+)*)"
],
"depNameTemplate": "actions/{{{packageName}}}",
"datasourceTemplate": "github-tags"
},
{
"fileMatch": ["^\\.gitea/workflows/.*\\.yml$"],
"matchStrings": [
"uses: https://git\\.quad4\\.io/actions/setup-pnpm@(?<currentDigest>[a-f0-9]{40}) # (?<currentValue>v[0-9]+(\\.[0-9]+)*)"
],
"depNameTemplate": "pnpm/action-setup",
"datasourceTemplate": "github-tags"
},
{
"fileMatch": ["^\\.gitea/workflows/.*\\.yml$"],
"matchStrings": [
"uses: https://git\\.quad4\\.io/actions/setup-task@(?<currentDigest>[a-f0-9]{40}) # (?<currentValue>v[0-9]+(\\.[0-9]+)*)"
],
"depNameTemplate": "arduino/setup-task",
"datasourceTemplate": "github-tags"
},
{
"fileMatch": ["^\\.gitea/workflows/.*\\.yml$"],
"matchStrings": [
"uses: https://git\\.quad4\\.io/actions/gh-gomod-generate-sbom@(?<currentDigest>[a-f0-9]{40}) # (?<currentValue>v[0-9]+(\\.[0-9]+)*)"
],
"depNameTemplate": "CycloneDX/gh-gomod-generate-sbom",
"datasourceTemplate": "github-tags"
},
{
"fileMatch": ["^\\.gitea/workflows/.*\\.yml$"],
"matchStrings": [
"uses: https://git\\.quad4\\.io/actions/action-gh-release@(?<currentDigest>[a-f0-9]{40}) # (?<currentValue>v[0-9]+(\\.[0-9]+)*)"
],
"depNameTemplate": "softprops/action-gh-release",
"datasourceTemplate": "github-tags"
}
]
} }

View File

@@ -12,23 +12,34 @@ import (
) )
func main() { func main() {
buildDir := "frontend/build" // Default behavior: process HTML in frontend/build
target := "frontend/build"
if len(os.Args) > 1 { if len(os.Args) > 1 {
buildDir = os.Args[1] target = os.Args[1]
} }
fmt.Printf("Generating SRI hashes for assets in %s...\n", buildDir) info, err := os.Stat(target)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
err := filepath.Walk(buildDir, func(path string, info os.FileInfo, err error) error { if info.IsDir() {
if err != nil { fmt.Printf("Generating SRI hashes for assets in %s...\n", target)
return err err = filepath.Walk(target, func(path string, info os.FileInfo, err error) error {
} if err != nil {
return err
if !info.IsDir() && strings.HasSuffix(path, ".html") { }
return processHTML(path, buildDir) if !info.IsDir() && strings.HasSuffix(path, ".html") {
} return processHTML(path, target)
return nil }
}) return nil
})
} else {
// If a single file is provided (like verifier.ts), process it specially
fmt.Printf("Updating SRI hashes in %s...\n", target)
err = processSourceFile(target)
}
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err) fmt.Fprintf(os.Stderr, "Error: %v\n", err)
@@ -37,16 +48,12 @@ func main() {
} }
func processHTML(htmlPath, buildDir string) error { func processHTML(htmlPath, buildDir string) error {
content, err := os.ReadFile(filepath.Clean(htmlPath)) // #nosec G304 content, err := os.ReadFile(filepath.Clean(htmlPath))
if err != nil { if err != nil {
return err return err
} }
updated := string(content) updated := string(content)
// Regex to find script and link tags that don't have integrity yet
// Matches: <script src="/_app/..."
// Matches: <link rel="stylesheet" href="/_app/..."
scriptRegex := regexp.MustCompile(`<(script|link)\s+([^>]*)(src|href)=["'](/[^"']+)["']([^>]*)>`) scriptRegex := regexp.MustCompile(`<(script|link)\s+([^>]*)(src|href)=["'](/[^"']+)["']([^>]*)>`)
matches := scriptRegex.FindAllStringSubmatch(updated, -1) matches := scriptRegex.FindAllStringSubmatch(updated, -1)
@@ -55,12 +62,10 @@ func processHTML(htmlPath, buildDir string) error {
attr := match[3] attr := match[3]
url := match[4] url := match[4]
// Only process local assets starting with /
if !strings.HasPrefix(url, "/") || strings.HasPrefix(url, "//") { if !strings.HasPrefix(url, "/") || strings.HasPrefix(url, "//") {
continue continue
} }
// Skip if integrity already exists
if strings.Contains(tag, "integrity=") { if strings.Contains(tag, "integrity=") {
continue continue
} }
@@ -68,7 +73,6 @@ func processHTML(htmlPath, buildDir string) error {
filePath := filepath.Join(buildDir, url) filePath := filepath.Join(buildDir, url)
hash, err := calculateSHA384(filePath) hash, err := calculateSHA384(filePath)
if err != nil { if err != nil {
// Asset might not exist (e.g. dynamic URL or external-ish local path)
fmt.Printf(" Skipping %s: %v\n", url, err) fmt.Printf(" Skipping %s: %v\n", url, err)
continue continue
} }
@@ -80,14 +84,58 @@ func processHTML(htmlPath, buildDir string) error {
} }
if updated != string(content) { if updated != string(content) {
return os.WriteFile(filepath.Clean(htmlPath), []byte(updated), 0644) // #nosec G306 return os.WriteFile(filepath.Clean(htmlPath), []byte(updated), 0644)
}
return nil
}
func processSourceFile(sourcePath string) error {
content, err := os.ReadFile(filepath.Clean(sourcePath))
if err != nil {
return err
} }
updated := string(content)
// We need a way to map the asset to the SRI. For verifier.ts, we know the assets.
assets := map[string]string{
"wasm_exec.js": "frontend/static/verifier/wasm_exec.js",
"verifier.wasm": "frontend/static/verifier/verifier.wasm",
}
for assetName, assetPath := range assets {
hash, err := calculateSHA384(assetPath)
if err != nil {
fmt.Printf(" Warning: could not calculate hash for %s: %v\n", assetName, err)
continue
}
// Find the line that mentions the asset and update the NEXT integrity string
assetEscaped := strings.ReplaceAll(assetName, ".", "\\.")
assetPattern := regexp.MustCompile(`['"](/verifier/)?` + assetEscaped + `['"][\s\S]*?sha384-([^'"]+)`)
matches := assetPattern.FindAllStringSubmatchIndex(updated, -1)
// Process from end to start to not mess up indices
for i := len(matches) - 1; i >= 0; i-- {
match := matches[i]
oldHashStart, oldHashEnd := match[4], match[5]
oldHash := updated[oldHashStart:oldHashEnd]
if oldHash != hash {
updated = updated[:oldHashStart] + hash + updated[oldHashEnd:]
fmt.Printf(" Updated SRI for %s in %s\n", assetName, sourcePath)
}
}
}
if updated != string(content) {
return os.WriteFile(filepath.Clean(sourcePath), []byte(updated), 0644)
}
return nil return nil
} }
func calculateSHA384(path string) (string, error) { func calculateSHA384(path string) (string, error) {
f, err := os.Open(filepath.Clean(path)) // #nosec G304 f, err := os.Open(filepath.Clean(path))
if err != nil { if err != nil {
return "", err return "", err
} }