create-bridgething 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -12
- package/dist/index.mjs +476 -142
- package/package.json +20 -5
- package/template/_claude/skills/bridgething/SKILL.md +12 -17
- package/template/_claude/skills/bridgething/reference/develop.md +86 -66
- package/template/_claude/skills/bridgething/reference/extension.md +183 -0
- package/template/_claude/skills/bridgething/reference/sdk.md +86 -105
- package/template/_claude/skills/bridgething/reference/ship.md +19 -38
- package/template/_gitignore +1 -0
- package/template/package.json +6 -5
- package/template/scripts/bridgething.ts +1086 -0
- package/template/scripts/push.ts +212 -193
- package/template/settings/main.tsx +14 -13
- package/template/src/App.tsx +3 -6
- package/template/src/daemon.ts +14 -0
- package/template/tsconfig.json +1 -1
- package/template/vite.config.ts +5 -3
- package/template/vite.settings.config.ts +3 -3
- package/template-extension/_claude_append.md +23 -0
- package/template-extension/extension/main.ts +20 -0
- package/template-launcher/_claude_append.md +14 -30
- package/template-launcher/src/App.tsx +2 -5
- package/template-overlay/_claude_append.md +31 -45
- package/template-overlay/vite.overlay.config.ts +1 -1
- package/template-source/CLAUDE.md +104 -0
- package/template-source/README.md +57 -0
- package/template-source/_bunversion +1 -0
- package/template-source/_github/workflows/ci.yml +28 -0
- package/template-source/_github/workflows/publish.yml +33 -0
- package/template-source/_gitignore +7 -0
- package/template-source/_prettierrc.js +10 -0
- package/template-source/apps/.gitkeep +0 -0
- package/template-source/package.json +27 -0
- package/template-source/source.json +8 -0
- package/template-versions.json +6 -0
- package/template/CLAUDE.md +0 -95
- package/template/README.md +0 -70
package/template/tsconfig.json
CHANGED
package/template/vite.config.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import tailwindcss from '@tailwindcss/vite';
|
|
2
2
|
import react from '@vitejs/plugin-react';
|
|
3
3
|
import { defineConfig } from 'vite';
|
|
4
|
+
import { bridgething, daemonProxy } from './scripts/bridgething';
|
|
4
5
|
|
|
5
|
-
export default defineConfig({
|
|
6
|
-
plugins: [react(), tailwindcss()],
|
|
6
|
+
export default defineConfig(async () => ({
|
|
7
|
+
plugins: [react(), tailwindcss(), bridgething()],
|
|
7
8
|
build: {
|
|
8
9
|
target: 'es2022',
|
|
9
10
|
sourcemap: true,
|
|
10
11
|
},
|
|
11
12
|
server: {
|
|
12
13
|
host: true,
|
|
14
|
+
proxy: await daemonProxy(),
|
|
13
15
|
},
|
|
14
|
-
});
|
|
16
|
+
}));
|
|
@@ -26,14 +26,14 @@ function sizeGuard(): Plugin {
|
|
|
26
26
|
'',
|
|
27
27
|
'========================================================================',
|
|
28
28
|
` settings.html is ${kib} KiB.`,
|
|
29
|
-
` the
|
|
30
|
-
' trim dependencies or inline assets
|
|
29
|
+
` the device refuses to install a bundle whose settings page is over ${HARD_CAP}.`,
|
|
30
|
+
' trim dependencies or inline assets; the whole install fails, not just this page.',
|
|
31
31
|
'========================================================================',
|
|
32
32
|
'',
|
|
33
33
|
].join('\n'),
|
|
34
34
|
);
|
|
35
35
|
} else if (bytes > WARN_BYTES) {
|
|
36
|
-
console.warn(`settings.html is ${kib} KiB; keep it lean (install
|
|
36
|
+
console.warn(`settings.html is ${kib} KiB; keep it lean (install fails over ${HARD_CAP}).`);
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
39
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
## This project ships a native extension
|
|
2
|
+
|
|
3
|
+
`manifest.json` declares an `extension` block, so this bundle has two halves.
|
|
4
|
+
|
|
5
|
+
- `src/` is the webapp, running in the device's chromium kiosk.
|
|
6
|
+
- `extension/` is a Deno process on the desktop. The bridgething desktop app
|
|
7
|
+
spawns it while the app is installed and enabled, with or without a Car Thing
|
|
8
|
+
connected.
|
|
9
|
+
|
|
10
|
+
The two halves talk over the daemon's forward surface: `client.forward` in the
|
|
11
|
+
webapp, `device.send` and `ctx.on('message', ...)` in the extension. The daemon
|
|
12
|
+
delivers a forward while this webapp is the active one on that device.
|
|
13
|
+
|
|
14
|
+
Host access belongs in the extension, an ordinary Deno program with `npm:`,
|
|
15
|
+
`jsr:`, and `node:` available. Keep `src/` a view over what it sends. The webapp
|
|
16
|
+
also runs with nothing attached, so check `capabilities.available.forward` and
|
|
17
|
+
give the user something useful when it is false.
|
|
18
|
+
|
|
19
|
+
`manifest.json` starts with `"permissions": ["all"]`. Narrow it to the Deno
|
|
20
|
+
permissions you use before you publish.
|
|
21
|
+
|
|
22
|
+
The `ctx` contract, the permission grammar, the dev loop, and the gotchas:
|
|
23
|
+
`.claude/skills/bridgething/reference/extension.md`.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { asJson, defineExtension, json } from '@bridgething/extension';
|
|
2
|
+
|
|
3
|
+
defineExtension({
|
|
4
|
+
start(ctx) {
|
|
5
|
+
ctx.log.info(`__PROJECT_NAME__ extension up, data in ${ctx.dataDir}`);
|
|
6
|
+
|
|
7
|
+
ctx.on('device', event => {
|
|
8
|
+
if (event.type !== 'connected') return;
|
|
9
|
+
const greeting = ctx.config(event.device).greeting ?? 'hello';
|
|
10
|
+
ctx.log.info(`${event.device.name} connected; greeting is "${greeting}"`);
|
|
11
|
+
event.device.send(json({ type: 'greeting', text: greeting }));
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
ctx.on('message', (device, message) => {
|
|
15
|
+
const payload = asJson<Record<string, unknown>>(message);
|
|
16
|
+
if (!payload) return;
|
|
17
|
+
device.send(json({ type: 'echo', payload }));
|
|
18
|
+
});
|
|
19
|
+
},
|
|
20
|
+
});
|
|
@@ -1,40 +1,24 @@
|
|
|
1
|
-
|
|
2
1
|
## This project is a launcher
|
|
3
2
|
|
|
4
|
-
`manifest.json` declares `"role": "launcher"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
- The bundle is eligible for the device's **launcher slot**. Holding that slot
|
|
9
|
-
makes this the home screen: the boot default, the target of the Mode-button
|
|
10
|
-
gesture, and what the back button returns to.
|
|
3
|
+
`manifest.json` declares `"role": "launcher"`. The daemon keeps this bundle out
|
|
4
|
+
of `client.webapp.list`, so the grid you draw excludes itself. Holding the
|
|
5
|
+
device's launcher slot makes it the home screen: the boot default, the target of
|
|
6
|
+
the Mode gesture, and where the Back button returns to.
|
|
11
7
|
|
|
12
|
-
`bun run push` claims the slot and switches to it
|
|
13
|
-
|
|
14
|
-
phone app can do the same. The built-in hub stays installed and can never be
|
|
15
|
-
uninstalled, so releasing the slot always gets the stock home screen back. That
|
|
16
|
-
is the recovery path if a launcher you are building wedges the device.
|
|
8
|
+
`bun run push` claims the slot and switches to it. `bun run push --release` hands
|
|
9
|
+
it back to the built-in hub, the recovery path when a build wedges the device.
|
|
17
10
|
|
|
18
|
-
### What a home screen
|
|
11
|
+
### What a home screen covers
|
|
19
12
|
|
|
20
|
-
|
|
21
|
-
particular surface; if yours does less than the built-in hub, that is a choice,
|
|
22
|
-
not an error, and the user can switch back. The built-in hub covers:
|
|
13
|
+
A launcher implements as much or as little as you want. The built-in hub covers:
|
|
23
14
|
|
|
24
|
-
- the app grid (`client.webapp.list
|
|
25
|
-
|
|
26
|
-
- bluetooth
|
|
15
|
+
- the app grid (`client.webapp.list`, `.icon`, `.activate`, `.current`, plus
|
|
16
|
+
`onWebappInstalled` and `onWebappUninstalled`)
|
|
17
|
+
- bluetooth bonds, adapter alias, discoverable
|
|
27
18
|
- display brightness
|
|
28
19
|
- system info and health
|
|
29
20
|
- power: restart, shut down, factory reset
|
|
30
|
-
- OTA progress (`client.system.onOtaProgress`
|
|
31
|
-
|
|
32
|
-
The starter here implements only the grid. Read the built-in hub's source if you
|
|
33
|
-
want a reference for the rest; everything it uses is on the public client SDK.
|
|
34
|
-
|
|
35
|
-
### Onboarding is not your problem
|
|
21
|
+
- OTA progress (`client.system.onOtaProgress` and `.onOtaError`)
|
|
36
22
|
|
|
37
|
-
The
|
|
38
|
-
|
|
39
|
-
device is set up. A factory reset clears the launcher slot along with everything
|
|
40
|
-
else, which puts the built-in hub and its wizard back.
|
|
23
|
+
The starter here draws the grid. Everything the hub uses is on the public client
|
|
24
|
+
SDK.
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { BridgethingClient, type WebappInfo } from '@bridgething/client';
|
|
2
2
|
import { useEffect, useMemo, useState } from 'react';
|
|
3
|
-
|
|
4
|
-
const wsUrl =
|
|
5
|
-
import.meta.env.VITE_BRIDGETHING_URL ??
|
|
6
|
-
(typeof window !== 'undefined' ? `ws://${window.location.host}/` : 'ws://127.0.0.1:8891/');
|
|
3
|
+
import { daemonUrl } from './daemon';
|
|
7
4
|
|
|
8
5
|
type Entry = {
|
|
9
6
|
info: WebappInfo;
|
|
@@ -11,7 +8,7 @@ type Entry = {
|
|
|
11
8
|
};
|
|
12
9
|
|
|
13
10
|
export default function App() {
|
|
14
|
-
const client = useMemo(() => new BridgethingClient({ url:
|
|
11
|
+
const client = useMemo(() => new BridgethingClient({ url: daemonUrl() }), []);
|
|
15
12
|
const [entries, setEntries] = useState<Entry[] | null>(null);
|
|
16
13
|
const [error, setError] = useState<string | null>(null);
|
|
17
14
|
const [activating, setActivating] = useState<string | null>(null);
|
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
|
|
2
1
|
## This project is a system overlay
|
|
3
2
|
|
|
4
|
-
`manifest.json` declares `"overlay": "overlay.js"`.
|
|
5
|
-
device's
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
`bun run push` claims the slot for you and reloads the kiosk, so the overlay
|
|
10
|
-
appears over whatever app is showing. It deliberately does **not** switch to
|
|
11
|
-
this bundle's own page - switching away from the app you are testing against is
|
|
12
|
-
the opposite of what you want.
|
|
3
|
+
`manifest.json` declares `"overlay": "overlay.js"`. While this bundle holds the
|
|
4
|
+
device's overlay slot, the daemon injects that file into every webapp's document
|
|
5
|
+
as it loads. The page in `src/` renders when someone launches this bundle from
|
|
6
|
+
the hub.
|
|
13
7
|
|
|
14
|
-
`bun run push
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
`bun run push` claims the overlay slot and reloads the kiosk, so the overlay
|
|
9
|
+
appears over the webapp that is showing. `bun run push --release` hands the slot
|
|
10
|
+
back to the built-in overlay, the recovery path when a build misbehaves.
|
|
17
11
|
|
|
18
12
|
### The contract
|
|
19
13
|
|
|
@@ -23,42 +17,34 @@ The daemon prepends one global before your bundle:
|
|
|
23
17
|
window.__bridgethingOverlay = { origin: 'http://127.0.0.1:8891', surfaces: {...} };
|
|
24
18
|
```
|
|
25
19
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
their own. If every surface is off, the daemon injects nothing at all.
|
|
31
|
-
- **`origin`** is the kiosk origin. Check `location.origin` against it before
|
|
32
|
-
mounting so the bundle never runs in a page the daemon did not serve.
|
|
20
|
+
`surfaces` is the active webapp's declared profile, a boolean each for
|
|
21
|
+
`notifications`, `call`, `pairing`, `connection`, `volume`, and `voice`. Render
|
|
22
|
+
only the surfaces set to true; an app that draws its own volume indicator
|
|
23
|
+
declares `volume: false`. When every surface is false the daemon injects nothing.
|
|
33
24
|
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
`origin` is the kiosk origin. Compare `location.origin` against it before
|
|
26
|
+
mounting. Everything else comes from `@bridgething/client` over the local
|
|
27
|
+
websocket.
|
|
36
28
|
|
|
37
|
-
###
|
|
29
|
+
### Output constraints
|
|
38
30
|
|
|
39
|
-
`overlay.js` must be
|
|
40
|
-
a script string into another app's document, so it
|
|
41
|
-
|
|
42
|
-
|
|
31
|
+
`overlay.js` must be one self-contained file under 512 KiB. The daemon injects it
|
|
32
|
+
as a script string into another app's document, so it carries its own code and
|
|
33
|
+
styles and reaches nothing at runtime. `vite.overlay.config.ts` builds a single
|
|
34
|
+
inlined iife; keep that shape.
|
|
43
35
|
|
|
44
|
-
|
|
45
|
-
`overlay/
|
|
46
|
-
|
|
47
|
-
so
|
|
48
|
-
shadow root. Add `@theme` tokens or plain rules there. Because the shadow root
|
|
49
|
-
is closed, tailwind is told not to scan the project (`source(none)`) and only
|
|
50
|
-
generates what `overlay/main.tsx` uses - if you split the overlay across more
|
|
51
|
-
files, add each one with `@source`.
|
|
36
|
+
Style `overlay/main.tsx` with tailwind classes like the rest of the project.
|
|
37
|
+
`overlay/style.css` is imported with vite's `?inline`, so the compiled css mounts
|
|
38
|
+
into the shadow root as a string. Tailwind scans only `overlay/main.tsx`
|
|
39
|
+
(`source(none)`), so add an `@source` line for every file you split across.
|
|
52
40
|
|
|
53
|
-
|
|
41
|
+
### Keep these four from the starter
|
|
54
42
|
|
|
55
|
-
1.
|
|
56
|
-
2.
|
|
57
|
-
3.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
showing. Your overlay sits on top of every app on the device; swallowing keys
|
|
61
|
-
broadly breaks all of them.
|
|
43
|
+
1. The origin guard before mounting.
|
|
44
|
+
2. The `__bridgethingOverlayMounted` guard, so a second injection is a no-op.
|
|
45
|
+
3. The closed shadow root, which keeps your styles and the host app's apart.
|
|
46
|
+
4. Escape-only key handling on the capture phase, active only while something is
|
|
47
|
+
showing.
|
|
62
48
|
|
|
63
|
-
You
|
|
64
|
-
|
|
49
|
+
You run inside every webapp's page. A crash, a fullscreen paint, or a broad key
|
|
50
|
+
handler takes down all of them.
|
|
@@ -18,7 +18,7 @@ function sizeGuard(): Plugin {
|
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
20
|
if (bytes > WARN_BYTES) {
|
|
21
|
-
console.warn(`overlay.js is ${(bytes / 1024).toFixed(1)} KiB;
|
|
21
|
+
console.warn(`overlay.js is ${(bytes / 1024).toFixed(1)} KiB; install fails over ${HARD_CAP}.`);
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Building bridgething webapps in this repo
|
|
2
|
+
|
|
3
|
+
This repo is a bun workspace of webapps for the Spotify Car Thing, plus the pipeline that publishes them as a `catalog.v1` source on GitHub Pages. Each `apps/<slug>` is one webapp: a single page running full-screen in the chromium kiosk on the device, reaching the on-device daemon through `@bridgething/client`.
|
|
4
|
+
|
|
5
|
+
## The device
|
|
6
|
+
|
|
7
|
+
The screen is 800x480, landscape, and never resizes. The kiosk shows one webapp, so build in-app views rather than tabs or windows. Add an on-screen keyboard if the app needs text entry.
|
|
8
|
+
|
|
9
|
+
Listen with a `keydown` handler and a `wheel` handler on `window`.
|
|
10
|
+
|
|
11
|
+
| Control | Event |
|
|
12
|
+
| ------------ | ------------------------------------- |
|
|
13
|
+
| Preset 1-4 | `keydown` key `"1"` `"2"` `"3"` `"4"` |
|
|
14
|
+
| Mode | `keydown` key `"m"` |
|
|
15
|
+
| Back | `keydown` key `"Escape"` |
|
|
16
|
+
| Rotary wheel | `wheel` with horizontal `deltaX` |
|
|
17
|
+
| Touch | pointer and touch events |
|
|
18
|
+
|
|
19
|
+
Make horizontal wheel scroll move through the main list. Five fast presses of Mode returns to the launcher.
|
|
20
|
+
|
|
21
|
+
## The client
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { BridgethingClient } from '@bridgething/client';
|
|
25
|
+
import { useMemo } from 'react';
|
|
26
|
+
|
|
27
|
+
import { daemonUrl } from './daemon';
|
|
28
|
+
|
|
29
|
+
const client = useMemo(() => new BridgethingClient({ url: daemonUrl() }), []);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Construct it once and reuse it. It connects and reconnects on its own. Call `daemonUrl()` instead of a literal `ws://` address.
|
|
33
|
+
|
|
34
|
+
Now-playing and library data come from the phone's Spotify, so render a placeholder when no phone is connected. Fetch artwork with `client.asset.get` using the opaque id on the track.
|
|
35
|
+
|
|
36
|
+
Every surface: `player asset config store doc capabilities library audio notifications phone peer geo net hardware bluetooth system time voice lyrics webapp forward`. Each method is an event, a request, or a command.
|
|
37
|
+
|
|
38
|
+
Methods, types, and examples: `.claude/skills/bridgething/reference/sdk.md`. That skill lives once at the repo root and every app symlinks it, so it is one copy for the whole workspace. `bun run skills` refreshes it from the published `create-bridgething`; `bun run check` says when it is behind.
|
|
39
|
+
|
|
40
|
+
## Working on one app
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
bun run dev # the only app, against a connected Car Thing
|
|
44
|
+
bun run dev <slug> # name it when there is more than one
|
|
45
|
+
bun run --cwd apps/<slug> dev # always works
|
|
46
|
+
bun run --cwd apps/<slug> dev:device # show that server on the device's own screen
|
|
47
|
+
bun run --cwd apps/<slug> push # build and install onto the connected device
|
|
48
|
+
bun run --cwd apps/<slug> build # writes dist/
|
|
49
|
+
bun run --cwd apps/<slug> typecheck # checks src, settings and extension together
|
|
50
|
+
bun run --cwd apps/<slug> share # zips dist/ to hand to someone directly
|
|
51
|
+
bun run --cwd apps/<slug> update # brings the device to the latest bridgething release
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
An app with a variant or an extension carries its own `apps/<slug>/CLAUDE.md` with the parts specific to it.
|
|
55
|
+
|
|
56
|
+
Running and driving the app: `.claude/skills/bridgething/reference/develop.md`. Push, zip, update: `.claude/skills/bridgething/reference/ship.md`. The desktop-side Deno extension and its permission model, for an app with an `extension/` directory: `.claude/skills/bridgething/reference/extension.md`.
|
|
57
|
+
|
|
58
|
+
## The three files that describe an app
|
|
59
|
+
|
|
60
|
+
**`apps/<slug>/public/manifest.json`** is the app's identity as far as the device and the catalog are concerned.
|
|
61
|
+
|
|
62
|
+
- `id` identifies this webapp on the device. It is a uuidv7 generated once at scaffold time. **Never change it**: the device keys upgrade-in-place and the app's key-value namespace on it, so a new uuid orphans everyone's installed copy and its data.
|
|
63
|
+
- `version` is what the store publishes against. Move it with `bun run bump`, never by hand.
|
|
64
|
+
- `description` is the store tagline and must not be empty.
|
|
65
|
+
- `config` declares the settings the companion app edits and `client.config` reads.
|
|
66
|
+
- `permissions` grants `geo` and `net.proxy`.
|
|
67
|
+
- `art.heroPx` and `art.thumbPx` are the sizes artwork arrives at.
|
|
68
|
+
- `settings` names the page built from `settings/`, capped at 1 MiB. It talks to the companion app through `@bridgething/client/settings`.
|
|
69
|
+
- `extension` declares the desktop-side Deno process and the host permissions it gets.
|
|
70
|
+
|
|
71
|
+
**`apps/<slug>/catalog.json`** is the store listing and nothing else: `author`, `homepage`, `source`, an `icon` override, `screenshots`, and `min_libbridgething_version` (the oldest daemon the app works against). Never restate anything the manifest already says. An app that ships an extension must carry its `github.com` repo url in `source`, because the store shows that link next to the host permissions it is asking for.
|
|
72
|
+
|
|
73
|
+
**`apps/<slug>/CHANGELOG.md`** carries a `## <version>` section per release, which becomes that version's changelog in the catalog.
|
|
74
|
+
|
|
75
|
+
## Adding and shipping
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
bun run new <slug> [--extension | --launcher | --overlay]
|
|
79
|
+
bun run shot <slug> [--replace | --name <label>]
|
|
80
|
+
bun run bump <slug> <major | minor | patch | x.y.z> [-m "note"]
|
|
81
|
+
bun run check
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`new` scaffolds into `apps/<slug>`. Never add an app by hand: the scaffold generates the uuid, writes the store listing, wires the dev server to a connected device, and lands the tsconfig and scripts CI expects.
|
|
85
|
+
|
|
86
|
+
`shot` captures the kiosk over CDP into `apps/<slug>/screenshots/`. Chromium's debugging port is 9223 and is bound to the device's loopback, so it goes through an ssh tunnel; the command opens and closes one itself. Up to six, filename order, first is the store card.
|
|
87
|
+
|
|
88
|
+
`bump` writes `public/manifest.json` and `package.json` together and opens the changelog section. Editing one without the other fails the build.
|
|
89
|
+
|
|
90
|
+
`check` is the whole gate and is exactly what CI runs: typecheck, build, bundle, generate the catalog, validate it against `catalog.v1` and the cross-reference invariants. Run it before claiming anything works.
|
|
91
|
+
|
|
92
|
+
Pushing to main publishes. `bun run publish --dry-run` assembles the exact bytes that would be pushed, into `site/`, without pushing.
|
|
93
|
+
|
|
94
|
+
## Publishing rules that are not negotiable
|
|
95
|
+
|
|
96
|
+
- **A published version is immutable.** `published.json` on the `gh-pages` branch records the sha256 clients verify against. Changing an app means a new version, always. `check` fails a pull request that changed an app without bumping it.
|
|
97
|
+
- **Bundles are reproducible.** Timestamps are flattened before zipping so the same source gives the same digest. Do not add anything nondeterministic to a build.
|
|
98
|
+
- **`site/` is generated and gitignored.** Nothing about a release belongs on main.
|
|
99
|
+
- **CORS is a requirement.** A source must serve its catalog and its downloads with `Access-Control-Allow-Origin: *` or browser clients cannot read it. GitHub Pages does; most self-hosting does not.
|
|
100
|
+
- **`base_url` in `source.json` is where this catalog is served.** If you forked this repo, point it at your own pages site and give every app a fresh uuid, or you publish app ids that belong to someone else. `check` refuses when it disagrees with the origin remote.
|
|
101
|
+
|
|
102
|
+
## Comments
|
|
103
|
+
|
|
104
|
+
Terse, lowercase, self-contained, non-obvious WHY only, 120 columns. No historical cross-references, no pointers at other files, no emdashes. Most code here needs none.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# **SOURCE_NAME**
|
|
2
|
+
|
|
3
|
+
**SOURCE_DESCRIPTION**
|
|
4
|
+
|
|
5
|
+
Webapps for the Spotify Car Thing running [bridgething](https://bridgething.com).
|
|
6
|
+
|
|
7
|
+
## First run
|
|
8
|
+
|
|
9
|
+
1. Push this repo to `https://github.com/__REPO_OWNER__/__REPO_NAME__`.
|
|
10
|
+
2. In **Settings > Pages**, set the source to **Deploy from a branch**, branch `gh-pages`, folder `/ (root)`.
|
|
11
|
+
|
|
12
|
+
The catalog is published to `__BASE_URL__/catalog.v1.json`, which can be submitted to <bridgething.com/apps>
|
|
13
|
+
|
|
14
|
+
## Develop
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
bun run dev # develop the app against a connected bridgething instance
|
|
18
|
+
bun run dev:device # show the dev server on the car thing screen
|
|
19
|
+
bun run push # build and install to the device
|
|
20
|
+
bun run check # ensure the catalog is valid
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
With more than one app in `apps/` your commands must specify which one: `bun run dev __APP_SLUG__`.
|
|
24
|
+
|
|
25
|
+
Screenshot for the store listing:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
bun run shot __APP_SLUG__ # grabs what is on the screen
|
|
29
|
+
bun run shot __APP_SLUG__ --replace # overwrite
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Add another app
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
bun run new weather # a webapp
|
|
36
|
+
bun run new dashboard --extension # a webapp plus a desktop-side Deno process
|
|
37
|
+
bun run new home --launcher # a replacement home screen
|
|
38
|
+
bun run new hud --overlay # a system overlay drawn over every webapp
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Ship
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
bun run bump __APP_SLUG__ patch -m "Fix the wind direction arrow"
|
|
45
|
+
git commit -am "__APP_SLUG__: fix the wind direction arrow" && git push
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Pushing to main builds the apps and regenerates the catalog.
|
|
49
|
+
|
|
50
|
+
## Agent skill
|
|
51
|
+
|
|
52
|
+
`.claude/skills/bridgething/` holds the `/bridgething` skill.
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
bun run skills # refresh it from the published create-bridgething
|
|
56
|
+
bun run skills --check # check whether it is behind
|
|
57
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.4.0
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
check:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
with:
|
|
17
|
+
fetch-depth: 0
|
|
18
|
+
|
|
19
|
+
- uses: oven-sh/setup-bun@v2
|
|
20
|
+
with:
|
|
21
|
+
bun-version-file: .bun-version
|
|
22
|
+
|
|
23
|
+
- run: bun install --frozen-lockfile
|
|
24
|
+
|
|
25
|
+
- name: build, bundle and validate the catalog
|
|
26
|
+
env:
|
|
27
|
+
CHECK_BASE_REF: ${{ github.event_name == 'pull_request' && format('origin/{0}', github.base_ref) || '' }}
|
|
28
|
+
run: bun run check
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: publish
|
|
10
|
+
cancel-in-progress: false
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
publish:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- uses: oven-sh/setup-bun@v2
|
|
22
|
+
with:
|
|
23
|
+
bun-version-file: .bun-version
|
|
24
|
+
|
|
25
|
+
- run: bun install --frozen-lockfile
|
|
26
|
+
|
|
27
|
+
- name: identify the publisher
|
|
28
|
+
run: |
|
|
29
|
+
git config user.name "github-actions[bot]"
|
|
30
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
31
|
+
|
|
32
|
+
- name: build every unpublished version and push the catalog
|
|
33
|
+
run: bun run publish
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** @type {import("prettier").Config} */
|
|
2
|
+
export default {
|
|
3
|
+
bracketSpacing: true,
|
|
4
|
+
bracketSameLine: true,
|
|
5
|
+
singleQuote: true,
|
|
6
|
+
trailingComma: 'all',
|
|
7
|
+
arrowParens: 'avoid',
|
|
8
|
+
semi: true,
|
|
9
|
+
overrides: [{ files: ['*.ts', '*.js', '*.tsx', '*.jsx', '*.cjs', '*.mjs'], options: { printWidth: 120 } }],
|
|
10
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__REPO_NAME__",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"workspaces": [
|
|
6
|
+
"apps/*"
|
|
7
|
+
],
|
|
8
|
+
"scripts": {
|
|
9
|
+
"new": "bunx create-bridgething",
|
|
10
|
+
"dev": "bridgething-source run dev",
|
|
11
|
+
"dev:device": "bridgething-source run dev:device",
|
|
12
|
+
"build": "bridgething-source run build",
|
|
13
|
+
"push": "bridgething-source run push",
|
|
14
|
+
"share": "bridgething-source run share",
|
|
15
|
+
"check": "bridgething-source check",
|
|
16
|
+
"bump": "bridgething-source bump",
|
|
17
|
+
"shot": "bridgething-source shot",
|
|
18
|
+
"catalog": "bridgething-source catalog",
|
|
19
|
+
"publish": "bridgething-source publish",
|
|
20
|
+
"skills": "bridgething-source skills",
|
|
21
|
+
"format": "prettier --write \"*.json\" \"*.js\""
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"prettier": "^3.9.6",
|
|
25
|
+
"typescript": "^6.0.3"
|
|
26
|
+
}
|
|
27
|
+
}
|