update deployment

This commit is contained in:
2024-09-29 18:17:10 +02:00
parent 2f0d8f2e1d
commit ca8bfac3bc
13 changed files with 426 additions and 24 deletions

View File

@@ -1,13 +1,9 @@
name: Build
on:
push:
tags:
- "v*"
workflow_dispatch:
on: [push]
jobs:
CI:
build:
name: 🔨🚀 Build and deploy
runs-on: ubuntu-latest
steps:
@@ -25,10 +21,10 @@ jobs:
uses: pnpm/action-setup@v4
with:
version: 8
- name: 🐉 Use Node.js 18.16.x
- name: 🐉 Use Node.js 22.4.x
uses: actions/setup-node@v3
with:
node-version: 18.16.x
node-version: 22.4.x
cache: "pnpm"
- name: ⏬ Install Node dependencies
run: pnpm install
@@ -38,17 +34,17 @@ jobs:
- name: 🔨 Build site
run: pnpm build
- name: 📦 Upload build artifacts
uses: actions/upload-artifact@v3.1.2
with:
name: build
path: build
- name: Disable jekyll
run: touch build/.nojekyll
- name: Custom domain
run: echo 'manager.charachorder.com' > build/CNAME
- run: git config user.name github-actions
- run: git config user.email github-actions@github.com
- run: git --work-tree build add --all
- run: git commit -m "Automatic Deploy action run by github-actions"
- run: git push origin HEAD:gh-pages --force
- name: Setup SSH
run: |
install -m 600 -D /dev/null ~/.ssh/id_rsa
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_rsa
echo "${{ secrets.DEPLOY_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
- name: Publish Stable
if: ${{ github.ref == 'refs/heads/v*' }}
run: rsync -rav --mkpath --delete build/ deploy@charachorder.io:/home/deploy/www/
- name: Publish Branch
run: rsync -rav --mkpath --delete build/ deploy@charachorder.io:/home/deploy/ref/${GITHUB_REF##*/}
- name: Publish Commit
run: rsync -rav --mkpath --delete build/ deploy@charachorder.io:/home/deploy/ref/${{ github.sha }}

View File

@@ -18,6 +18,8 @@ const config = {
"update",
"offline_pin",
"warning",
"dangerous",
"check",
"cable",
"person",
"sync",

1
src/env.d.ts vendored
View File

@@ -15,6 +15,7 @@ interface ImportMetaEnv {
readonly VITE_LATEST_FIRMWARE: string;
readonly VITE_STORE_URL: string;
readonly VITE_MATRIX_URL: string;
readonly VITE_FIRMWARE_URL: string;
}
interface ImportMeta {

View File

@@ -0,0 +1,16 @@
<script lang="ts">
let { children } = $props();
</script>
<h1><a href="/ota-update/">Firmware Update</a></h1>
{@render children()}
<style lang="scss">
h1 {
margin-block: 1em;
padding: 0;
font-size: 3em;
font-weight: 400;
}
</style>

View File

@@ -0,0 +1,2 @@
export const prerender = false;
export const ssr = false;

View File

@@ -1,15 +1,91 @@
<script lang="ts">
import { serialPort } from "$lib/serial/connection";
import { slide } from "svelte/transition";
let { data } = $props();
let files: FileList | null = $state(null);
$effect(() => {
const file = files?.[0];
if (file && $serialPort) {
$serialPort.updateFirmware(file);
}
});
let currentDevice = $derived(
$serialPort
? `${$serialPort.device.toLowerCase()}_${$serialPort.chipset.toLowerCase()}`
: undefined,
);
</script>
<ul>
{#each data.devices as device}
<li>
<a href="./{device.name}/" class:highlight={device.name === currentDevice}
>{device.name}</a
>
</li>
{/each}
</ul>
{#if !currentDevice}
<aside transition:slide>Connect your device to see which one you need</aside>
{/if}
<input type="file" accept=".bin" bind:files />
<style lang="scss">
ul {
display: flex;
list-style: none;
gap: 8px;
}
li {
margin: 0;
padding: 0;
}
a {
outline: 1px solid var(--md-sys-color-outline);
border-radius: 8px;
transition:
background-color 200ms ease,
color 200ms ease,
outline-offset 200ms ease,
outline-color 200ms ease;
}
@keyframes highlight {
0% {
outline-offset: 0;
}
100% {
outline-offset: 4px;
}
}
@keyframes wiggle {
0% {
transform: rotate(0deg);
scale: 1;
}
50% {
transform: rotate(-5deg);
}
100% {
transform: rotate(-5deg);
scale: 1.1;
}
}
.highlight {
outline-width: 2px;
outline-color: var(--md-sys-color-primary);
animation: wiggle 500ms ease 2 alternate;
background-color: var(--md-sys-color-primary-container);
color: var(--md-sys-color-on-primary-container);
}
</style>

View File

@@ -0,0 +1,9 @@
import type { PageLoad } from "./$types";
import type { DirectoryListing } from "./listing";
export const load = (async ({ fetch }) => {
const result = await fetch(import.meta.env.VITE_FIRMWARE_URL);
const data = await result.json();
return { devices: data as DirectoryListing[] };
}) satisfies PageLoad;

View File

@@ -0,0 +1,85 @@
<script lang="ts">
let { data } = $props();
let showPrerelease = $state(false);
</script>
<div class="title">
<h2>Versions available for <em>{data.device}</em></h2>
<label
>Include Pre-releases<input
type="checkbox"
bind:checked={showPrerelease}
/></label
>
</div>
{#if data.versions}
<ul>
{#each data.versions as version}
{@const isPrerelease = version.name.includes("-")}
<li class:pre-release={isPrerelease}>
<a href="./{version.name}/"
>{version.name}
<time datetime={version.mtime}
>{new Date(version.mtime).toLocaleDateString()}</time
></a
>
</li>
{/each}
</ul>
{:else}
<h2>The device {data.device} does not exist.</h2>
{/if}
<style lang="scss">
.pre-release {
margin-inline-start: 2em;
}
ul {
list-style: none;
padding: 0;
}
li {
height: 2em;
overflow: hidden;
transition:
height 200ms ease,
opacity 200ms ease;
}
label {
padding: 0;
opacity: 0.6;
}
.title {
display: flex;
flex-direction: column;
justify-content: flex-start;
h2 {
margin-block-end: 0;
em {
font-style: normal;
color: var(--md-sys-color-primary);
}
}
}
time {
opacity: 0.5;
&:before {
content: "•";
padding-inline: 0.4ch;
}
}
div.title:has(input:not(:checked)) ~ ul .pre-release {
height: 0;
opacity: 0;
}
</style>

View File

@@ -0,0 +1,16 @@
import type { PageLoad } from "./$types";
import type { DirectoryListing } from "../listing";
export const load = (async ({ fetch, params }) => {
const result = await fetch(
`${import.meta.env.VITE_FIRMWARE_URL}/${params.device}/`,
);
const data = await result.json();
return {
versions: (data as DirectoryListing[]).sort((a, b) =>
b.name.localeCompare(a.name),
),
device: params.device,
};
}) satisfies PageLoad;

View File

@@ -0,0 +1,163 @@
<script lang="ts">
import { serialPort } from "$lib/serial/connection";
import { slide } from "svelte/transition";
let { data } = $props();
let currentDevice = $derived(
$serialPort
? `${$serialPort.device.toLowerCase()}_${$serialPort.chipset.toLowerCase()}`
: undefined,
);
let isCorrectDevice = $derived(
currentDevice ? currentDevice === data.device : undefined,
);
let uf2Url = $derived(
data.uf2
? `${import.meta.env.VITE_FIRMWARE_URL}${data.device}/${data.version}/${data.uf2.name}`
: undefined,
);
let otaUrl = $derived(
data.ota
? `${import.meta.env.VITE_FIRMWARE_URL}${data.device}/${data.version}/${data.ota.name}`
: undefined,
);
/**
* Bytes to respective units
*/
function toByteUnit(value: number) {
if (value < 1024) {
return `${value}B`;
} else if (value < 1024 * 1024) {
return `${(value / 1024).toFixed(2)}KB`;
} else {
return `${(value / 1024 / 1024).toFixed(2)}MB`;
}
}
</script>
<div>
<h2>
Update <em
class="device"
class:correct-device={isCorrectDevice === true}
class:incorrect-device={isCorrectDevice === false}>{data.device}</em
>
to <em class="version">{data.version}</em>
</h2>
<ul class="files">
{#if data.uf2}
<li>
<a target="_blank" download href={uf2Url}
>{data.uf2.name} <span class="icon">download</span><span class="size"
>{toByteUnit(data.uf2.size)}</span
></a
>
</li>
{/if}
{#if data.ota}
<li>
<a target="_blank" download href={otaUrl}
>{data.ota.name} <span class="icon">download</span><span class="size"
>{toByteUnit(data.uf2.size)}</span
></a
>
</li>
{/if}
</ul>
{#if isCorrectDevice === false}
<div transition:slide class="incorrect-device">
These files are incompatible with your device
</div>
{/if}
<section>
<h3>OTA Upate</h3>
{#if data.ota}
<p>OTA update</p>
{:else}
<em>There are no OTA files for this device.</em>
{/if}
</section>
<hr />
<h3>Other options</h3>
<section>
<h4>Via UF2</h4>
<ol>
<li>Backup your device</li>
<li>Reboot to bootloader</li>
<li>Save CURRENT.UF2 to the new drive</li>
<li>Restore</li>
</ol>
</section>
<section>
<h4>Via Serial</h4>
<p>WIP</p>
</section>
</div>
<style lang="scss">
h2 > em {
font-style: normal;
transition: color 200ms ease;
}
hr {
color: var(--md-sys-color-outline);
margin-block: 3em;
margin-inline: 5em;
border-style: dashed;
}
.files {
list-style: none;
display: flex;
padding: 0;
gap: 8px;
a {
display: grid;
grid-template-columns: 1fr auto;
grid-template-rows: 1fr;
border: 1px solid var(--md-sys-color-outline);
border-radius: 8px;
font-size: 0.9em;
height: auto;
.size {
font-size: 0.8em;
opacity: 0.8;
}
.icon {
padding-inline-start: 0.4em;
grid-column: 2;
grid-row: 1 / span 2;
}
}
}
.version {
color: var(--md-sys-color-secondary);
}
.device {
opacity: 0.6;
}
.correct-device {
color: var(--md-sys-color-primary);
opacity: 1;
}
.incorrect-device {
color: var(--md-sys-color-error);
}
</style>

View File

@@ -0,0 +1,20 @@
import type { PageLoad } from "./$types";
import type { FileListing, Listing } from "../../listing";
export const load = (async ({ fetch, params }) => {
const result = await fetch(
`${import.meta.env.VITE_FIRMWARE_URL}/${params.device}/${params.version}/`,
);
const data: Listing[] = await result.json();
return {
uf2: data.find(
(entry) => entry.type === "file" && entry.name === "CURRENT.UF2",
) as FileListing,
ota: data.find(
(entry) => entry.type === "file" && entry.name === "firmware.bin",
),
version: params.version,
device: params.device,
};
}) satisfies PageLoad;

View File

@@ -0,0 +1,14 @@
export type Listing = FileListing | DirectoryListing;
export interface DirectoryListing {
name: string;
type: "directory";
mtime: string;
}
export interface FileListing {
name: string;
type: "file";
mtime: string;
size: number;
}

View File

@@ -23,6 +23,7 @@ process.env["VITE_LEARN_URL"] = "https://www.iq-eq.io/";
process.env["VITE_LATEST_FIRMWARE"] = "1.1.4";
process.env["VITE_STORE_URL"] = "https://www.charachorder.com/";
process.env["VITE_MATRIX_URL"] = "https://charachorder.io/";
process.env["VITE_FIRMWARE_URL"] = "https://charachorder.io/firmware/";
export default defineConfig({
build: {
@@ -54,10 +55,11 @@ export default defineConfig({
workbox: {
// https://vite-pwa-org.netlify.app/frameworks/sveltekit.html#globpatterns
globPatterns: [
"client/**/*.{js,map,css,ico,woff2,csv,png,webp,svg,webmanifest}",
"client/**/*.{js,css,ico,woff2,csv,png,webp,svg,webmanifest}",
"prerendered/**/*.html",
],
ignoreURLParametersMatching: [/^import|redirectUrl|loginToken$/],
maximumFileSizeToCacheInBytes: 10 * 1024 * 1024,
},
manifest: {
name: "CharaChorder Device Manager",