rockstar-strudel 1.0.0
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/LICENSE +661 -0
- package/PLAN.md +227 -0
- package/README.md +123 -0
- package/package.json +31 -0
- package/src/index.js +210 -0
- package/test/rockstar.test.js +159 -0
package/PLAN.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# Plan: shipping `rockstar-strudel` from a RockstarLang/rockstar fork
|
|
2
|
+
|
|
3
|
+
This document describes every step needed to wire the existing Starship WASM
|
|
4
|
+
engine up to the `rockstar` template-tag module in this repo so that
|
|
5
|
+
strudel.cc users can simply write:
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import { init, rockstar } from 'https://esm.sh/rockstar-strudel'
|
|
9
|
+
await init('https://<your-username>.github.io/rockstar/wasm/wwwroot/_framework/dotnet.js')
|
|
10
|
+
const data = await rockstar`Shout 42`
|
|
11
|
+
// data === [42]
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Background: why anything needs to change
|
|
17
|
+
|
|
18
|
+
The Starship engine is already built and running at
|
|
19
|
+
`https://codewithrockstar.com/wasm/`. The issue is that browsers enforce the
|
|
20
|
+
**Same-Origin Policy**: a page on `strudel.cc` cannot load a JS/WASM module
|
|
21
|
+
from `codewithrockstar.com` unless that server explicitly opts in via a CORS
|
|
22
|
+
header (`Access-Control-Allow-Origin: *`).
|
|
23
|
+
|
|
24
|
+
`codewithrockstar.com` uses a **custom domain**, which means CORS headers must
|
|
25
|
+
be configured at the CDN/DNS level (e.g. Cloudflare) by the site owner — a
|
|
26
|
+
change that can't be made via a GitHub PR to the repo.
|
|
27
|
+
|
|
28
|
+
However, **GitHub Pages on `*.github.io` domains serves all static assets with
|
|
29
|
+
`Access-Control-Allow-Origin: *` built in**, at no extra configuration cost.
|
|
30
|
+
This means a fork of the repo deployed to GitHub Pages at its default
|
|
31
|
+
`<you>.github.io/rockstar` URL has CORS working immediately, with no CDN
|
|
32
|
+
setup needed.
|
|
33
|
+
|
|
34
|
+
The complete build pipeline (`.NET` WASM compile → Jekyll site build →
|
|
35
|
+
GitHub Pages deploy) is already automated in the repo's GitHub Actions
|
|
36
|
+
workflows, so enabling it on a fork is a matter of enabling Pages in the
|
|
37
|
+
fork's settings.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Option A — Fork and host on GitHub Pages (unblocked today)
|
|
42
|
+
|
|
43
|
+
This is the fastest path. No CDN, no Cloudflare, no extra accounts needed.
|
|
44
|
+
|
|
45
|
+
### Step 1 — Fork `RockstarLang/rockstar`
|
|
46
|
+
|
|
47
|
+
Fork it on GitHub (keep it **public** so the free GitHub Pages tier is
|
|
48
|
+
available).
|
|
49
|
+
|
|
50
|
+
### Step 2 — Enable GitHub Pages on the fork
|
|
51
|
+
|
|
52
|
+
1. Go to your fork → **Settings → Pages**
|
|
53
|
+
2. Under *Build and deployment*, set Source to **GitHub Actions**
|
|
54
|
+
(not a branch — the workflow handles deployment itself)
|
|
55
|
+
3. Save.
|
|
56
|
+
|
|
57
|
+
### Step 3 — Trigger the first build
|
|
58
|
+
|
|
59
|
+
The build pipeline is three chained workflows:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
build-rockstar-2.0
|
|
63
|
+
└─► release-rockstar-engine
|
|
64
|
+
└─► build-and-deploy-codewithrockstar.com → GitHub Pages
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Trigger the first one manually:
|
|
68
|
+
- Go to **Actions → build-rockstar-2.0 → Run workflow** (pick `main`)
|
|
69
|
+
|
|
70
|
+
This will:
|
|
71
|
+
1. Build the Starship .NET engine and run its tests
|
|
72
|
+
2. Compile the WASM with `dotnet publish Starship/Rockstar.Wasm -c Release`
|
|
73
|
+
3. Copy the WASM into the Jekyll site and deploy it to GitHub Pages
|
|
74
|
+
|
|
75
|
+
After a few minutes your site will be live at:
|
|
76
|
+
```
|
|
77
|
+
https://<your-username>.github.io/rockstar/
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
And the WASM loader will be at:
|
|
81
|
+
```
|
|
82
|
+
https://<your-username>.github.io/rockstar/wasm/wwwroot/_framework/dotnet.js
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Step 4 — Point `rockstar-strudel` at your fork
|
|
86
|
+
|
|
87
|
+
Update `DEFAULT_DOTNET_URL` in `src/index.js`:
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
const DEFAULT_DOTNET_URL =
|
|
91
|
+
'https://<your-username>.github.io/rockstar/wasm/wwwroot/_framework/dotnet.js';
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Or leave the default pointing at `codewithrockstar.com` and let users pass
|
|
95
|
+
their own URL via `init()`:
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
await init('https://<your-username>.github.io/rockstar/wasm/wwwroot/_framework/dotnet.js')
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Step 5 — Publish the npm package
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
cd rockstar-strudel
|
|
105
|
+
npm publish --access public
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Users can then import from `https://esm.sh/rockstar-strudel`.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Option B — PR / issue to `RockstarLang/rockstar` (long-term fix)
|
|
113
|
+
|
|
114
|
+
A PR to the repo **cannot** fix CORS for the custom domain by itself — that
|
|
115
|
+
change must be made in the Cloudflare (or equivalent CDN) dashboard by the
|
|
116
|
+
site owner. The most useful thing you can do is:
|
|
117
|
+
|
|
118
|
+
1. **Open an issue** explaining the strudel.cc use case and asking them to add
|
|
119
|
+
`Access-Control-Allow-Origin: *` to the `/wasm/` path in their CDN config.
|
|
120
|
+
A single Cloudflare Transform Rule would fix it permanently for all users.
|
|
121
|
+
|
|
122
|
+
2. Optionally include a **PR that adds a `_headers` file** as a signal of
|
|
123
|
+
intent (it has no effect on a custom domain, but documents the desired
|
|
124
|
+
config):
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
# codewithrockstar.com/_headers
|
|
128
|
+
/wasm/*
|
|
129
|
+
Access-Control-Allow-Origin: *
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Once the upstream site adds the header, update `DEFAULT_DOTNET_URL` back to
|
|
133
|
+
`https://codewithrockstar.com/wasm/wwwroot/_framework/dotnet.js` so users
|
|
134
|
+
get the canonical URL by default.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Summary
|
|
139
|
+
|
|
140
|
+
| Path | Works today? | Effort |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| Fork → GitHub Pages (Option A) | ✅ Yes, ~20 min | Fork + enable Pages + trigger build |
|
|
143
|
+
| PR/issue to upstream (Option B) | ⏳ Depends on maintainer | Low effort, uncertain timeline |
|
|
144
|
+
|
|
145
|
+
**Do both**: use Option A to unblock yourself right now, open an upstream
|
|
146
|
+
issue (Option B) so the permanent fix lands in `codewithrockstar.com`.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Local smoke-test (optional but recommended)
|
|
151
|
+
|
|
152
|
+
To verify `src/index.js` against a local WASM build before publishing:
|
|
153
|
+
|
|
154
|
+
### Build the WASM locally
|
|
155
|
+
|
|
156
|
+
You need the **.NET 9 SDK** (`dotnet --version` should show `9.x`).
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
cd Starship
|
|
160
|
+
dotnet workload install wasm-tools
|
|
161
|
+
dotnet publish Rockstar.Wasm -c Release -o ../wasm-publish
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The output in `../wasm-publish/wwwroot/` contains:
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
_framework/
|
|
168
|
+
dotnet.js ← the JS loader
|
|
169
|
+
dotnet.native.js
|
|
170
|
+
dotnet.runtime.js
|
|
171
|
+
dotnet.wasm ← the .NET runtime (~7 MB, AOT-compiled in Release)
|
|
172
|
+
Rockstar.Wasm.wasm ← the Rockstar engine
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Run the integration test
|
|
176
|
+
|
|
177
|
+
Create a minimal HTML file in the same directory as the WASM:
|
|
178
|
+
|
|
179
|
+
```html
|
|
180
|
+
<!-- wasm-publish/wwwroot/test.html -->
|
|
181
|
+
<script type="module">
|
|
182
|
+
import { init, rockstar } from '/path/to/rockstar-strudel/src/index.js'
|
|
183
|
+
|
|
184
|
+
// localhost is in the allowed URL list, so no allowlist update needed
|
|
185
|
+
await init('http://localhost:8080/_framework/dotnet.js')
|
|
186
|
+
|
|
187
|
+
const result = await rockstar`
|
|
188
|
+
My heart is 123
|
|
189
|
+
Let your love be 456
|
|
190
|
+
Put 789 into the night
|
|
191
|
+
Shout my heart. Scream your love. Whisper the night.
|
|
192
|
+
`
|
|
193
|
+
console.assert(JSON.stringify(result) === '[123,456,789]',
|
|
194
|
+
'Expected [123,456,789], got ' + JSON.stringify(result))
|
|
195
|
+
document.body.textContent = JSON.stringify(result)
|
|
196
|
+
</script>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Serve from localhost (same origin as the WASM avoids CORS entirely):
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
cd wasm-publish/wwwroot
|
|
203
|
+
npx serve -p 8080
|
|
204
|
+
# open http://localhost:8080/test.html
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Summary checklist
|
|
210
|
+
|
|
211
|
+
- [ ] Fork `RockstarLang/rockstar` on GitHub (keep public)
|
|
212
|
+
- [ ] Fork Settings → Pages → Source: **GitHub Actions**
|
|
213
|
+
- [ ] Actions → `build-rockstar-2.0` → **Run workflow** (triggers the full chain)
|
|
214
|
+
- [ ] Wait for Pages deployment; note your URL: `https://<you>.github.io/rockstar/`
|
|
215
|
+
- [ ] Update `DEFAULT_DOTNET_URL` in `src/index.js` to your fork's WASM URL
|
|
216
|
+
(or let users pass it to `init()`)
|
|
217
|
+
- [ ] `npm publish --access public` the `rockstar-strudel` package
|
|
218
|
+
- [ ] Verify in strudel.cc:
|
|
219
|
+
```js
|
|
220
|
+
import { init, rockstar } from 'https://esm.sh/rockstar-strudel'
|
|
221
|
+
await init('https://<you>.github.io/rockstar/wasm/wwwroot/_framework/dotnet.js')
|
|
222
|
+
const data = await rockstar`Shout 42` // [42]
|
|
223
|
+
```
|
|
224
|
+
- [ ] Open an issue on `RockstarLang/rockstar` asking them to add
|
|
225
|
+
`Access-Control-Allow-Origin: *` to `/wasm/` at their CDN level, so
|
|
226
|
+
the default URL can eventually point back at `codewithrockstar.com`
|
|
227
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# rockstar-strudel
|
|
2
|
+
|
|
3
|
+
Run [Rockstar](https://codewithrockstar.com) programs from the
|
|
4
|
+
[strudel.cc](https://strudel.cc) live-coding REPL (or any browser-based JS
|
|
5
|
+
environment) via a simple template-tag function.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
const data = await rockstar`
|
|
9
|
+
My heart is 123
|
|
10
|
+
Let your love be 456
|
|
11
|
+
Put 789 into the night
|
|
12
|
+
Shout my heart. Scream your love. Whisper the night.
|
|
13
|
+
`
|
|
14
|
+
// data === [123, 456, 789]
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Every value printed by `Say` / `Shout` / `Scream` / `Whisper` becomes one
|
|
18
|
+
element of the returned array. Values that parse as finite numbers are
|
|
19
|
+
returned as JS `number`; everything else is returned as a `string`.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Using it in strudel.cc
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
import { init, rockstar } from 'https://esm.sh/rockstar-strudel'
|
|
27
|
+
|
|
28
|
+
// Pre-warm the WASM engine while other code loads (optional but recommended)
|
|
29
|
+
await init()
|
|
30
|
+
|
|
31
|
+
// Run a Rockstar program
|
|
32
|
+
const notes = await rockstar`
|
|
33
|
+
Tommy was 60
|
|
34
|
+
Build Tommy up, up, up, up
|
|
35
|
+
Shout Tommy
|
|
36
|
+
Build Tommy up
|
|
37
|
+
Shout Tommy
|
|
38
|
+
Build Tommy up, up
|
|
39
|
+
Shout Tommy
|
|
40
|
+
`
|
|
41
|
+
// notes === [64, 65, 67]
|
|
42
|
+
|
|
43
|
+
note(notes).sound("piano").slow(2)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Template interpolations
|
|
47
|
+
|
|
48
|
+
JavaScript values can be spliced into the source, letting you parameterise
|
|
49
|
+
programs from strudel patterns:
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
const root = 60
|
|
53
|
+
const data = await rockstar`
|
|
54
|
+
Tommy was ${root}
|
|
55
|
+
Build Tommy up, up, up, up
|
|
56
|
+
Shout Tommy
|
|
57
|
+
`
|
|
58
|
+
// data === [64]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## API
|
|
64
|
+
|
|
65
|
+
### `rockstar(strings, ...values)` → `Promise<Array<number|string>>`
|
|
66
|
+
|
|
67
|
+
Tagged-template function. Runs the Rockstar source code and resolves with an
|
|
68
|
+
array of every printed value.
|
|
69
|
+
|
|
70
|
+
### `init([dotnetUrl])` → `Promise<void>`
|
|
71
|
+
|
|
72
|
+
Pre-loads the WASM engine. Optionally accepts a custom `dotnet.js` URL (see
|
|
73
|
+
[PLAN.md](PLAN.md) for hosting your own copy with CORS headers).
|
|
74
|
+
|
|
75
|
+
### `buildSource(strings, ...values)` → `string`
|
|
76
|
+
|
|
77
|
+
Pure helper that reconstructs the full source string from a tagged-template
|
|
78
|
+
call. Exported for testing.
|
|
79
|
+
|
|
80
|
+
### `coerce(line)` → `number | string | undefined`
|
|
81
|
+
|
|
82
|
+
Pure helper that converts a raw WASM callback line to a typed JS value.
|
|
83
|
+
Exported for testing.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## How it works
|
|
88
|
+
|
|
89
|
+
The Rockstar **Starship** engine is a .NET 9 application compiled to
|
|
90
|
+
WebAssembly. The built WASM is hosted at
|
|
91
|
+
`https://codewithrockstar.com/wasm/`. This package dynamically imports
|
|
92
|
+
`dotnet.js` from that URL, initialises the runtime, and calls
|
|
93
|
+
`RockstarRunner.Run(source, outputCallback, stdin, args)`, which is
|
|
94
|
+
`[JSExport]`'d from C#.
|
|
95
|
+
|
|
96
|
+
Each call to `Say`/`Shout`/`Scream`/`Whisper` in the Rockstar program triggers
|
|
97
|
+
the callback with the printed string (plus a trailing newline added by
|
|
98
|
+
`WasmIO.WriteLine` in C#). The tag strips whitespace and coerces numeric
|
|
99
|
+
strings to `number`.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## CORS requirement
|
|
104
|
+
|
|
105
|
+
`codewithrockstar.com` must serve its `/wasm/` assets with the header
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
Access-Control-Allow-Origin: *
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Without this, browsers will block the cross-origin `import()` of `dotnet.js`.
|
|
112
|
+
See [PLAN.md](PLAN.md) for the exact steps needed in the rockstar fork.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Development
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npm test # run the 19 unit tests (pure JS logic, no WASM required)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Integration testing (actual Rockstar program execution) requires a browser
|
|
123
|
+
environment where the WASM can load; see [PLAN.md](PLAN.md) for details.
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rockstar-strudel",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Run Rockstar lang programs via the Starship WASM engine, returning output as a JS array. Designed for use in the strudel.cc live-coding REPL.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node --test test/*.test.js"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/stretchyboy/rockstar-strudel.git"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"rockstar",
|
|
19
|
+
"strudel",
|
|
20
|
+
"live-coding",
|
|
21
|
+
"music",
|
|
22
|
+
"wasm",
|
|
23
|
+
"template-tag"
|
|
24
|
+
],
|
|
25
|
+
"author": "",
|
|
26
|
+
"license": "AGPL-3.0",
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/stretchyboy/rockstar-strudel/issues"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/stretchyboy/rockstar-strudel#readme"
|
|
31
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rockstar-strudel
|
|
3
|
+
*
|
|
4
|
+
* Runs Rockstar lang programs via the Starship WebAssembly engine and returns
|
|
5
|
+
* each printed value as an element of a JavaScript array, for use in the
|
|
6
|
+
* strudel.cc live-coding REPL.
|
|
7
|
+
*
|
|
8
|
+
* Quick start in strudel.cc
|
|
9
|
+
* ─────────────────────────
|
|
10
|
+
* import { rockstar } from 'https://esm.sh/rockstar-strudel'
|
|
11
|
+
*
|
|
12
|
+
* const data = await rockstar`
|
|
13
|
+
* My heart is 123
|
|
14
|
+
* Let your love be 456
|
|
15
|
+
* Put 789 into the night
|
|
16
|
+
* Shout my heart. Scream your love. Whisper the night.
|
|
17
|
+
* `
|
|
18
|
+
* // data === [123, 456, 789]
|
|
19
|
+
*
|
|
20
|
+
* CORS requirement
|
|
21
|
+
* ────────────────
|
|
22
|
+
* The WASM engine is loaded from https://codewithrockstar.com/wasm/.
|
|
23
|
+
* That origin must serve its WASM assets with
|
|
24
|
+
* Access-Control-Allow-Origin: *
|
|
25
|
+
* See PLAN.md for instructions on enabling this in a rockstar fork.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** Default URL of the .NET WASM loader published by codewithrockstar.com. */
|
|
29
|
+
const DEFAULT_DOTNET_URL =
|
|
30
|
+
'https://codewithrockstar.com/wasm/wwwroot/_framework/dotnet.js';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* URL prefixes that are considered safe for loading the dotnet.js WASM
|
|
34
|
+
* runtime. Calls to `init()` with a URL that does not start with one of
|
|
35
|
+
* these prefixes (or match the github.io pattern) are rejected to prevent
|
|
36
|
+
* loading arbitrary remote code.
|
|
37
|
+
* Extend this list if you host the WASM yourself on a trusted CDN.
|
|
38
|
+
*/
|
|
39
|
+
export const ALLOWED_URL_PREFIXES = [
|
|
40
|
+
'https://codewithrockstar.com/',
|
|
41
|
+
'https://cdn.jsdelivr.net/',
|
|
42
|
+
'https://unpkg.com/',
|
|
43
|
+
'http://localhost:',
|
|
44
|
+
'http://127.0.0.1:',
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
/** github.io subdomains (e.g. username.github.io) are also trusted. */
|
|
48
|
+
export const GITHUB_IO_PATTERN = /^https:\/\/[^.]+\.github\.io\//;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Returns true if the given dotnetUrl is trusted for WASM loading.
|
|
52
|
+
* Exported for testing.
|
|
53
|
+
* @param {string} url
|
|
54
|
+
* @returns {boolean}
|
|
55
|
+
*/
|
|
56
|
+
export function isTrustedUrl(url) {
|
|
57
|
+
return (
|
|
58
|
+
ALLOWED_URL_PREFIXES.some((prefix) => url.startsWith(prefix)) ||
|
|
59
|
+
GITHUB_IO_PATTERN.test(url)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The single cached promise that resolves to the RockstarRunner export object.
|
|
65
|
+
* Kept at module scope so the WASM runtime is initialised at most once per
|
|
66
|
+
* page/worker load.
|
|
67
|
+
* @type {Promise<object> | null}
|
|
68
|
+
*/
|
|
69
|
+
let _runnerPromise = null;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Pre-load the Rockstar WASM engine.
|
|
73
|
+
*
|
|
74
|
+
* Called automatically on the first use of the `rockstar` tag, but you can
|
|
75
|
+
* call it earlier to eliminate the cold-start delay on the first program run.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} [dotnetUrl]
|
|
78
|
+
* Override the dotnet.js loader URL.
|
|
79
|
+
* Useful when you host the WASM assets yourself (e.g. after following the
|
|
80
|
+
* steps in PLAN.md to add CORS headers to your own rockstar fork deployment).
|
|
81
|
+
* Defaults to DEFAULT_DOTNET_URL.
|
|
82
|
+
* @returns {Promise<void>}
|
|
83
|
+
*/
|
|
84
|
+
export async function init(dotnetUrl) {
|
|
85
|
+
if (!_runnerPromise) {
|
|
86
|
+
_runnerPromise = _loadRunner(dotnetUrl ?? DEFAULT_DOTNET_URL);
|
|
87
|
+
}
|
|
88
|
+
await _runnerPromise;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Internal: dynamically import the dotnet.js WASM loader, initialise the
|
|
93
|
+
* .NET runtime, and return the JSExport'd RockstarRunner object.
|
|
94
|
+
*
|
|
95
|
+
* dotnet.js resolves all sibling WASM/assembly blobs relative to its own URL,
|
|
96
|
+
* so as long as the hosting origin sets CORS headers the runtime loads without
|
|
97
|
+
* any additional configuration.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} dotnetUrl
|
|
100
|
+
* @returns {Promise<object>} Resolves to `exports.Rockstar.Wasm.RockstarRunner`
|
|
101
|
+
*/
|
|
102
|
+
async function _loadRunner(dotnetUrl) {
|
|
103
|
+
if (!isTrustedUrl(dotnetUrl)) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`Untrusted dotnet.js URL: "${dotnetUrl}". ` +
|
|
106
|
+
`Must start with one of: ${ALLOWED_URL_PREFIXES.join(', ')} ` +
|
|
107
|
+
`or be a *.github.io URL. ` +
|
|
108
|
+
`Add your CDN prefix to ALLOWED_URL_PREFIXES in src/index.js if needed.`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
// eslint-disable-next-line no-eval -- dynamic import from a runtime URL
|
|
112
|
+
const { dotnet } = await import(/* webpackIgnore: true */ dotnetUrl);
|
|
113
|
+
const { getAssemblyExports, getConfig } = await dotnet
|
|
114
|
+
.withDiagnosticTracing(false)
|
|
115
|
+
.create();
|
|
116
|
+
const config = getConfig();
|
|
117
|
+
const exports = await getAssemblyExports(config.mainAssemblyName);
|
|
118
|
+
return exports.Rockstar.Wasm.RockstarRunner;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Return the cached runner promise, initialising with the default URL if
|
|
123
|
+
* `init()` has not been called yet.
|
|
124
|
+
* @returns {Promise<object>}
|
|
125
|
+
*/
|
|
126
|
+
function _runner() {
|
|
127
|
+
if (!_runnerPromise) _runnerPromise = _loadRunner(DEFAULT_DOTNET_URL);
|
|
128
|
+
return _runnerPromise;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Coerce a single raw output line (as delivered by the WASM callback) to a
|
|
133
|
+
* typed JavaScript value.
|
|
134
|
+
*
|
|
135
|
+
* - Trailing `\r\n` / `\n` (added by `WasmIO.WriteLine` in C#) is stripped.
|
|
136
|
+
* - Blank lines after stripping are ignored (returns `undefined`).
|
|
137
|
+
* - Finite numbers are returned as JS `number`.
|
|
138
|
+
* - Everything else is returned as a `string`.
|
|
139
|
+
*
|
|
140
|
+
* @param {string} line Raw callback argument from the WASM engine.
|
|
141
|
+
* @returns {number | string | undefined}
|
|
142
|
+
*/
|
|
143
|
+
export function coerce(line) {
|
|
144
|
+
const trimmed = line.trimEnd();
|
|
145
|
+
if (trimmed === '') return undefined;
|
|
146
|
+
const num = Number(trimmed);
|
|
147
|
+
return Number.isFinite(num) ? num : trimmed;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Build the full Rockstar source string from a tagged-template call's parts.
|
|
152
|
+
*
|
|
153
|
+
* Template interpolations are stringified and spliced in, so you can
|
|
154
|
+
* parameterise programs:
|
|
155
|
+
*
|
|
156
|
+
* const n = 10;
|
|
157
|
+
* await rockstar`Tommy was ${n}\nShout Tommy`
|
|
158
|
+
* // equivalent to running: Tommy was 10 \n Shout Tommy
|
|
159
|
+
*
|
|
160
|
+
* @param {TemplateStringsArray} strings
|
|
161
|
+
* @param {...*} values
|
|
162
|
+
* @returns {string}
|
|
163
|
+
*/
|
|
164
|
+
export function buildSource(strings, ...values) {
|
|
165
|
+
return strings.reduce((acc, str, i) => {
|
|
166
|
+
const interpolated = values[i - 1];
|
|
167
|
+
return acc + (interpolated !== undefined ? String(interpolated) : '') + str;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Template tag that executes a Rockstar program and returns an array of every
|
|
173
|
+
* value printed by the program (via `Say` / `Shout` / `Scream` / `Whisper`).
|
|
174
|
+
*
|
|
175
|
+
* Values that parse as finite numbers are returned as JS `number`; everything
|
|
176
|
+
* else is returned as a `string`.
|
|
177
|
+
*
|
|
178
|
+
* The WASM engine is loaded lazily on the first call and cached for subsequent
|
|
179
|
+
* calls. You can call `init()` first to pre-warm the engine if desired.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* const data = await rockstar`
|
|
183
|
+
* My heart is 123
|
|
184
|
+
* Let your love be 456
|
|
185
|
+
* Put 789 into the night
|
|
186
|
+
* Shout my heart. Scream your love. Whisper the night.
|
|
187
|
+
* `
|
|
188
|
+
* // data === [123, 456, 789]
|
|
189
|
+
*
|
|
190
|
+
* @param {TemplateStringsArray} strings
|
|
191
|
+
* @param {...*} values
|
|
192
|
+
* @returns {Promise<Array<number|string>>}
|
|
193
|
+
*/
|
|
194
|
+
export async function rockstar(strings, ...values) {
|
|
195
|
+
const code = buildSource(strings, ...values);
|
|
196
|
+
const runner = await _runner();
|
|
197
|
+
const outputs = [];
|
|
198
|
+
|
|
199
|
+
await runner.Run(
|
|
200
|
+
code,
|
|
201
|
+
(line) => {
|
|
202
|
+
const value = coerce(line);
|
|
203
|
+
if (value !== undefined) outputs.push(value);
|
|
204
|
+
},
|
|
205
|
+
/* stdin */ '',
|
|
206
|
+
/* args */ ''
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
return outputs;
|
|
210
|
+
}
|