@playcademy/vite-plugin 1.2.1-beta.4 → 1.2.1-beta.5
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/dist/index.js +310 -35
- package/dist/server/hotkeys/toggle-mode.d.ts +1 -1
- package/dist/server/middleware.d.ts +3 -2
- package/dist/server/modes.d.ts +15 -2
- package/dist/server/state.d.ts +10 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/internal.d.ts +13 -3
- package/dist/types/options.d.ts +78 -10
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -23757,6 +23757,7 @@ var POSTHOG_CONFIG = {
|
|
|
23757
23757
|
uiHost: "https://us.posthog.com"
|
|
23758
23758
|
};
|
|
23759
23759
|
// ../constants/src/timeback.ts
|
|
23760
|
+
var VALID_E_LEVELS = ["E1", "E2", "E3", "E4"];
|
|
23760
23761
|
var TIMEBACK_ORG_SOURCED_ID = "PLAYCADEMY";
|
|
23761
23762
|
var TIMEBACK_ORG_NAME = "Playcademy Studios";
|
|
23762
23763
|
var TIMEBACK_ORG_TYPE = "department";
|
|
@@ -24314,7 +24315,7 @@ import path2 from "node:path";
|
|
|
24314
24315
|
// package.json
|
|
24315
24316
|
var package_default = {
|
|
24316
24317
|
name: "@playcademy/vite-plugin",
|
|
24317
|
-
version: "1.2.1-beta.
|
|
24318
|
+
version: "1.2.1-beta.5",
|
|
24318
24319
|
type: "module",
|
|
24319
24320
|
exports: {
|
|
24320
24321
|
".": {
|
|
@@ -24347,7 +24348,7 @@ var package_default = {
|
|
|
24347
24348
|
"@types/bun": "1.3.5"
|
|
24348
24349
|
},
|
|
24349
24350
|
peerDependencies: {
|
|
24350
|
-
"@playcademy/sdk": ">=0.
|
|
24351
|
+
"@playcademy/sdk": ">=0.17.0",
|
|
24351
24352
|
typescript: "^5 || ^6",
|
|
24352
24353
|
vite: "^5 || ^6 || ^7 || ^8"
|
|
24353
24354
|
}
|
|
@@ -24635,6 +24636,7 @@ var serverState = {
|
|
|
24635
24636
|
dashboardApp: null,
|
|
24636
24637
|
viteServer: null,
|
|
24637
24638
|
currentMode: "platform",
|
|
24639
|
+
parentOptions: null,
|
|
24638
24640
|
timebackRoleOverride: null,
|
|
24639
24641
|
platformRoleOverride: null
|
|
24640
24642
|
};
|
|
@@ -24647,6 +24649,12 @@ function hasActiveServers() {
|
|
|
24647
24649
|
function getCurrentMode() {
|
|
24648
24650
|
return serverState.currentMode;
|
|
24649
24651
|
}
|
|
24652
|
+
function getParentOptions() {
|
|
24653
|
+
return serverState.parentOptions;
|
|
24654
|
+
}
|
|
24655
|
+
function setParentOptions(parent) {
|
|
24656
|
+
serverState.parentOptions = parent;
|
|
24657
|
+
}
|
|
24650
24658
|
function setCurrentMode(mode) {
|
|
24651
24659
|
serverState.currentMode = mode;
|
|
24652
24660
|
}
|
|
@@ -24703,14 +24711,31 @@ import path5 from "node:path";
|
|
|
24703
24711
|
import { CONFIG_FILE_NAMES } from "playcademy/constants";
|
|
24704
24712
|
|
|
24705
24713
|
// src/server/modes.ts
|
|
24714
|
+
function assertChildParentOptions(parent) {
|
|
24715
|
+
if (!parent) {
|
|
24716
|
+
throw new Error("[Playcademy] mode: 'child' requires the `parent` option, e.g. parent: { intent: { lessonId: 'my-lesson', eLevel: 'E2' } }");
|
|
24717
|
+
}
|
|
24718
|
+
if (!parent.intent) {
|
|
24719
|
+
throw new Error("[Playcademy] mode: 'child' requires `parent.intent`: the child game boots straight into the lesson it names");
|
|
24720
|
+
}
|
|
24721
|
+
if (!parent.intent.lessonId) {
|
|
24722
|
+
throw new Error("[Playcademy] `parent.intent.lessonId` is required and must be non-empty");
|
|
24723
|
+
}
|
|
24724
|
+
if (!parent.intent.eLevel || !VALID_E_LEVELS.includes(parent.intent.eLevel)) {
|
|
24725
|
+
throw new Error("[Playcademy] `parent.intent.eLevel` is required, one of 'E1' | 'E2' | 'E3' | 'E4'");
|
|
24726
|
+
}
|
|
24727
|
+
}
|
|
24706
24728
|
function isShellMode(mode) {
|
|
24707
24729
|
return mode !== "standalone";
|
|
24708
24730
|
}
|
|
24709
|
-
function getNextMode(mode) {
|
|
24731
|
+
function getNextMode(mode, includeChild) {
|
|
24710
24732
|
if (mode === "platform") {
|
|
24711
24733
|
return "demo";
|
|
24712
24734
|
}
|
|
24713
24735
|
if (mode === "demo") {
|
|
24736
|
+
return includeChild ? "child" : "standalone";
|
|
24737
|
+
}
|
|
24738
|
+
if (mode === "child") {
|
|
24714
24739
|
return "standalone";
|
|
24715
24740
|
}
|
|
24716
24741
|
return "platform";
|
|
@@ -25887,7 +25912,7 @@ var package_default2;
|
|
|
25887
25912
|
var init_package = __esm(() => {
|
|
25888
25913
|
package_default2 = {
|
|
25889
25914
|
name: "@playcademy/sandbox",
|
|
25890
|
-
version: "0.7.1-beta.
|
|
25915
|
+
version: "0.7.1-beta.5",
|
|
25891
25916
|
description: "Local development server for Playcademy game development",
|
|
25892
25917
|
type: "module",
|
|
25893
25918
|
exports: {
|
|
@@ -181514,10 +181539,24 @@ var shell_default2 = `<!doctype html>
|
|
|
181514
181539
|
height: 100vh;
|
|
181515
181540
|
overflow: hidden;
|
|
181516
181541
|
}
|
|
181517
|
-
|
|
181518
|
-
position: fixed;
|
|
181542
|
+
.corner-top-left {
|
|
181519
181543
|
top: 0.5rem;
|
|
181520
181544
|
left: 0.5rem;
|
|
181545
|
+
}
|
|
181546
|
+
.corner-top-right {
|
|
181547
|
+
top: 0.5rem;
|
|
181548
|
+
right: 0.5rem;
|
|
181549
|
+
}
|
|
181550
|
+
.corner-bottom-left {
|
|
181551
|
+
bottom: 0.5rem;
|
|
181552
|
+
left: 0.5rem;
|
|
181553
|
+
}
|
|
181554
|
+
.corner-bottom-right {
|
|
181555
|
+
bottom: 0.5rem;
|
|
181556
|
+
right: 0.5rem;
|
|
181557
|
+
}
|
|
181558
|
+
#badge {
|
|
181559
|
+
position: fixed;
|
|
181521
181560
|
background: rgba(23, 23, 23, 0.9);
|
|
181522
181561
|
color: white;
|
|
181523
181562
|
padding: 0.25rem 0.5rem;
|
|
@@ -181532,6 +181571,28 @@ var shell_default2 = `<!doctype html>
|
|
|
181532
181571
|
#badge.offline {
|
|
181533
181572
|
background: rgba(211, 47, 47, 0.9);
|
|
181534
181573
|
}
|
|
181574
|
+
#relay {
|
|
181575
|
+
position: fixed;
|
|
181576
|
+
background: rgba(23, 23, 23, 0.9);
|
|
181577
|
+
color: #e0e0e0;
|
|
181578
|
+
padding: 0.5rem 0.75rem;
|
|
181579
|
+
border-radius: 4px;
|
|
181580
|
+
font-family: 'VT323', monospace;
|
|
181581
|
+
font-size: 1rem;
|
|
181582
|
+
z-index: 9999;
|
|
181583
|
+
backdrop-filter: blur(10px);
|
|
181584
|
+
letter-spacing: 0.05em;
|
|
181585
|
+
max-width: 26rem;
|
|
181586
|
+
}
|
|
181587
|
+
#relay .dim {
|
|
181588
|
+
color: #9e9e9e;
|
|
181589
|
+
}
|
|
181590
|
+
#relay-report {
|
|
181591
|
+
color: #7cfc98;
|
|
181592
|
+
}
|
|
181593
|
+
#relay-report.ignored {
|
|
181594
|
+
color: #f87171;
|
|
181595
|
+
}
|
|
181535
181596
|
#frame {
|
|
181536
181597
|
position: absolute;
|
|
181537
181598
|
inset: 0;
|
|
@@ -181545,11 +181606,26 @@ var shell_default2 = `<!doctype html>
|
|
|
181545
181606
|
</style>
|
|
181546
181607
|
</head>
|
|
181547
181608
|
<body>
|
|
181548
|
-
<div id="badge">PLAYCADEMY</div>
|
|
181609
|
+
<div id="badge" class="corner-{{BADGE_POSITION}}">PLAYCADEMY</div>
|
|
181610
|
+
<div id="relay" class="hidden corner-{{RELAY_PANEL_POSITION}}">
|
|
181611
|
+
<div id="relay-intent"></div>
|
|
181612
|
+
<div id="relay-time" class="dim">active 0s / paused 0s</div>
|
|
181613
|
+
<div id="relay-report"></div>
|
|
181614
|
+
</div>
|
|
181549
181615
|
<iframe id="frame" class="hidden"></iframe>
|
|
181550
181616
|
|
|
181551
181617
|
<script type="module">
|
|
181552
|
-
const {
|
|
181618
|
+
const {
|
|
181619
|
+
MessageEvents,
|
|
181620
|
+
messaging,
|
|
181621
|
+
HANDSHAKE_RESEND_INTERVAL_MS,
|
|
181622
|
+
HANDSHAKE_MAX_DURATION_MS,
|
|
181623
|
+
SDK_SUPPORTS_CHILD_MODE,
|
|
181624
|
+
} = await import('/@playcademy/shell-sdk')
|
|
181625
|
+
|
|
181626
|
+
// ============================================================
|
|
181627
|
+
// Templated config (filled in by the dev server middleware)
|
|
181628
|
+
// ============================================================
|
|
181553
181629
|
|
|
181554
181630
|
const CONFIG = {
|
|
181555
181631
|
sandboxUrl: '{{SANDBOX_URL}}',
|
|
@@ -181557,27 +181633,149 @@ var shell_default2 = `<!doctype html>
|
|
|
181557
181633
|
gameToken: '{{GAME_TOKEN}}',
|
|
181558
181634
|
gameUrl: '{{GAME_URL}}' || undefined,
|
|
181559
181635
|
mode: '{{MODE}}',
|
|
181560
|
-
timebackJson:
|
|
181636
|
+
timebackJson: {{TIMEBACK_DATA}},
|
|
181637
|
+
parentJson: {{PARENT_DATA}},
|
|
181561
181638
|
hideBadge: '{{HIDE_BADGE}}' === 'true',
|
|
181639
|
+
hideRelayPanel: '{{HIDE_RELAY_PANEL}}' === 'true',
|
|
181562
181640
|
}
|
|
181563
181641
|
|
|
181564
|
-
|
|
181642
|
+
function parseJsonOrNull(raw) {
|
|
181565
181643
|
try {
|
|
181566
|
-
return JSON.parse(
|
|
181644
|
+
return JSON.parse(raw)
|
|
181567
181645
|
} catch {
|
|
181568
181646
|
return null
|
|
181569
181647
|
}
|
|
181570
|
-
}
|
|
181648
|
+
}
|
|
181571
181649
|
|
|
181572
|
-
const
|
|
181573
|
-
const
|
|
181650
|
+
const timeback = parseJsonOrNull(CONFIG.timebackJson)
|
|
181651
|
+
const parentContext = parseJsonOrNull(CONFIG.parentJson)
|
|
181574
181652
|
|
|
181575
|
-
if (CONFIG.
|
|
181576
|
-
|
|
181653
|
+
if (CONFIG.mode === 'child' && !SDK_SUPPORTS_CHILD_MODE) {
|
|
181654
|
+
console.error(
|
|
181655
|
+
"[Playcademy] mode 'child' needs an @playcademy/sdk that ships client.parent (>= 0.16.0); the project's installed SDK predates it. Upgrade the SDK dependency.",
|
|
181656
|
+
)
|
|
181577
181657
|
}
|
|
181578
181658
|
|
|
181659
|
+
// ============================================================
|
|
181660
|
+
// DOM + logging
|
|
181661
|
+
// ============================================================
|
|
181662
|
+
|
|
181663
|
+
const badge = document.getElementById('badge')
|
|
181664
|
+
const frame = document.getElementById('frame')
|
|
181665
|
+
|
|
181579
181666
|
const log = (...args) => window.PLAYCADEMY_DEBUG && console.log('[DevShell]', ...args)
|
|
181580
181667
|
|
|
181668
|
+
// ============================================================
|
|
181669
|
+
// Child-mode relay panel
|
|
181670
|
+
// ============================================================
|
|
181671
|
+
// In child mode the shell plays the parent game, and the game's
|
|
181672
|
+
// relayed messages replace every backend POST it would otherwise
|
|
181673
|
+
// make - they ARE the integration's output, so the panel shows
|
|
181674
|
+
// them: the opened run, live timing, and the final report.
|
|
181675
|
+
|
|
181676
|
+
const relayPanel = (() => {
|
|
181677
|
+
const root = document.getElementById('relay')
|
|
181678
|
+
const els = {
|
|
181679
|
+
intent: document.getElementById('relay-intent'),
|
|
181680
|
+
time: document.getElementById('relay-time'),
|
|
181681
|
+
report: document.getElementById('relay-report'),
|
|
181682
|
+
}
|
|
181683
|
+
|
|
181684
|
+
// Each relay message is a cumulative snapshot of ONE window
|
|
181685
|
+
// (keyed by windowStartedAtMs), and the tracker rotates
|
|
181686
|
+
// windows on its 15s accounting cadence and on tab-hide.
|
|
181687
|
+
// Keep the latest snapshot per window; sum the windows.
|
|
181688
|
+
const windows = new Map()
|
|
181689
|
+
let sawBeat = false
|
|
181690
|
+
let reported = false
|
|
181691
|
+
|
|
181692
|
+
const isTrackerRelay = type =>
|
|
181693
|
+
type === MessageEvents.TIMEBACK_ACTIVITY_START ||
|
|
181694
|
+
type === MessageEvents.TIMEBACK_HEARTBEAT_RELAY ||
|
|
181695
|
+
type === MessageEvents.TIMEBACK_ACTIVITY_END
|
|
181696
|
+
|
|
181697
|
+
function show(intent) {
|
|
181698
|
+
const { lessonId, eLevel } = intent ?? {}
|
|
181699
|
+
els.intent.textContent = \`intent: \${lessonId}\${eLevel ? \` (\${eLevel})\` : ''}\`
|
|
181700
|
+
root.classList.remove('hidden')
|
|
181701
|
+
}
|
|
181702
|
+
|
|
181703
|
+
function onMessage(type, payload) {
|
|
181704
|
+
if (type === MessageEvents.READY) {
|
|
181705
|
+
// A reloaded game document is a fresh launch.
|
|
181706
|
+
reported = false
|
|
181707
|
+
windows.clear()
|
|
181708
|
+
els.report.textContent = ''
|
|
181709
|
+
els.report.classList.remove('ignored')
|
|
181710
|
+
return
|
|
181711
|
+
}
|
|
181712
|
+
|
|
181713
|
+
// One report per launch, exactly like a production
|
|
181714
|
+
// parent: after the report the books are closed, so a
|
|
181715
|
+
// replay loop fails here, at the developer's desk.
|
|
181716
|
+
if (reported && isTrackerRelay(type)) {
|
|
181717
|
+
els.report.textContent = '⚠ ignored: already reported (one report per launch)'
|
|
181718
|
+
els.report.classList.add('ignored')
|
|
181719
|
+
console.error(
|
|
181720
|
+
'[DevShell] this launch already reported; ignoring tracker relays, exactly as a parent game would. One report per launch: report once, then exit.',
|
|
181721
|
+
)
|
|
181722
|
+
return
|
|
181723
|
+
}
|
|
181724
|
+
|
|
181725
|
+
if (type === MessageEvents.TIMEBACK_ACTIVITY_START) {
|
|
181726
|
+
// A fresh run's timing must not sum with the last
|
|
181727
|
+
// run's windows.
|
|
181728
|
+
windows.clear()
|
|
181729
|
+
console.log('[DevShell] child relay: activity start', payload)
|
|
181730
|
+
}
|
|
181731
|
+
|
|
181732
|
+
if (type === MessageEvents.TIMEBACK_HEARTBEAT_RELAY) {
|
|
181733
|
+
const key = payload?.windowStartedAtMs
|
|
181734
|
+
|
|
181735
|
+
if (typeof key === 'number') {
|
|
181736
|
+
windows.set(key, {
|
|
181737
|
+
activeMs: payload?.timingData?.activeMs ?? 0,
|
|
181738
|
+
pausedMs: payload?.timingData?.pausedMs ?? 0,
|
|
181739
|
+
})
|
|
181740
|
+
}
|
|
181741
|
+
|
|
181742
|
+
let activeMs = 0
|
|
181743
|
+
let pausedMs = 0
|
|
181744
|
+
|
|
181745
|
+
for (const w of windows.values()) {
|
|
181746
|
+
activeMs += w.activeMs
|
|
181747
|
+
pausedMs += w.pausedMs
|
|
181748
|
+
}
|
|
181749
|
+
|
|
181750
|
+
els.time.textContent = \`active \${Math.round(activeMs / 1000)}s / paused \${Math.round(pausedMs / 1000)}s\`
|
|
181751
|
+
|
|
181752
|
+
// Dim zeros that never brighten mean the game never
|
|
181753
|
+
// called startActivity().
|
|
181754
|
+
if (!sawBeat) {
|
|
181755
|
+
sawBeat = true
|
|
181756
|
+
els.time.classList.remove('dim')
|
|
181757
|
+
}
|
|
181758
|
+
}
|
|
181759
|
+
|
|
181760
|
+
if (type === MessageEvents.TIMEBACK_ACTIVITY_END) {
|
|
181761
|
+
const score = payload?.scoreData
|
|
181762
|
+
// durationSeconds is the FULL sitting;
|
|
181763
|
+
// sessionTimingData is only the post-flush remainder
|
|
181764
|
+
// (near zero by design).
|
|
181765
|
+
const seconds = payload?.timingData?.durationSeconds
|
|
181766
|
+
els.report.textContent = \`report: \${score?.correctQuestions}/\${score?.totalQuestions} correct, xp \${payload?.xpEarned ?? 'n/a'}, \${seconds}s (full report in console)\`
|
|
181767
|
+
console.log('[DevShell] child relay: end-activity report', payload)
|
|
181768
|
+
reported = true
|
|
181769
|
+
}
|
|
181770
|
+
}
|
|
181771
|
+
|
|
181772
|
+
return { show, onMessage }
|
|
181773
|
+
})()
|
|
181774
|
+
|
|
181775
|
+
// ============================================================
|
|
181776
|
+
// Sandbox liveness
|
|
181777
|
+
// ============================================================
|
|
181778
|
+
|
|
181581
181779
|
async function checkSandbox() {
|
|
181582
181780
|
// Use the public /health endpoint as a pure liveness probe.
|
|
181583
181781
|
// We used to hit /api/users/me here, but that's \`requireNonAnonymous\`
|
|
@@ -181598,14 +181796,25 @@ var shell_default2 = `<!doctype html>
|
|
|
181598
181796
|
}
|
|
181599
181797
|
}
|
|
181600
181798
|
|
|
181601
|
-
|
|
181799
|
+
// ============================================================
|
|
181800
|
+
// INIT handshake (shell → game)
|
|
181801
|
+
// ============================================================
|
|
181802
|
+
|
|
181803
|
+
function initHandshake() {
|
|
181602
181804
|
const payload = {
|
|
181603
181805
|
baseUrl: CONFIG.sandboxUrl,
|
|
181604
181806
|
gameUrl: CONFIG.gameUrl,
|
|
181605
181807
|
gameId: CONFIG.gameId,
|
|
181606
181808
|
token: CONFIG.gameToken,
|
|
181607
181809
|
mode: CONFIG.mode,
|
|
181608
|
-
timeback
|
|
181810
|
+
// A production parent never forwards its own timeback
|
|
181811
|
+
// user context to a child; mirror that omission.
|
|
181812
|
+
timeback: CONFIG.mode === 'child' ? undefined : timeback,
|
|
181813
|
+
parent: parentContext ?? undefined,
|
|
181814
|
+
// Child embedders consume the relay stream (the panel
|
|
181815
|
+
// below), so the game's tracker must not beacon its own
|
|
181816
|
+
// backend on unload — same flag embed.launch() sets.
|
|
181817
|
+
hasHeartbeatRelay: CONFIG.mode === 'child' ? true : undefined,
|
|
181609
181818
|
}
|
|
181610
181819
|
|
|
181611
181820
|
let interval, timeout
|
|
@@ -181628,11 +181837,14 @@ var shell_default2 = `<!doctype html>
|
|
|
181628
181837
|
frame.classList.remove('hidden')
|
|
181629
181838
|
log('Frame loaded, starting handshake')
|
|
181630
181839
|
send()
|
|
181631
|
-
interval = setInterval(send,
|
|
181840
|
+
interval = setInterval(send, HANDSHAKE_RESEND_INTERVAL_MS)
|
|
181841
|
+
// The SDK's constant exceeds the game's 25s INIT wait so
|
|
181842
|
+
// a struggling game can send INIT_ERROR before the shell
|
|
181843
|
+
// gives up.
|
|
181632
181844
|
timeout = setTimeout(() => {
|
|
181633
181845
|
stop()
|
|
181634
181846
|
log('Handshake timeout')
|
|
181635
|
-
},
|
|
181847
|
+
}, HANDSHAKE_MAX_DURATION_MS)
|
|
181636
181848
|
}
|
|
181637
181849
|
|
|
181638
181850
|
messaging.listen(MessageEvents.READY, () => {
|
|
@@ -181643,29 +181855,67 @@ var shell_default2 = `<!doctype html>
|
|
|
181643
181855
|
frame.src = '/'
|
|
181644
181856
|
}
|
|
181645
181857
|
|
|
181646
|
-
|
|
181647
|
-
|
|
181648
|
-
|
|
181649
|
-
|
|
181650
|
-
|
|
181651
|
-
|
|
181858
|
+
// ============================================================
|
|
181859
|
+
// Game → shell message routing
|
|
181860
|
+
// ============================================================
|
|
181861
|
+
// The wire shape is { type, payload }. Everything the game posts
|
|
181862
|
+
// gets logged and re-dispatched locally so shell-side listeners
|
|
181863
|
+
// (messaging.listen) can consume it; in child mode the relay
|
|
181864
|
+
// panel also gets each payload.
|
|
181865
|
+
|
|
181866
|
+
window.addEventListener('message', event => {
|
|
181867
|
+
if (event.source !== frame.contentWindow) return
|
|
181868
|
+
|
|
181869
|
+
const { type, payload } = event.data || {}
|
|
181870
|
+
if (!type?.startsWith('PLAYCADEMY_')) return
|
|
181871
|
+
|
|
181872
|
+
log('Message:', type, payload)
|
|
181873
|
+
|
|
181874
|
+
if (CONFIG.mode === 'child') {
|
|
181875
|
+
relayPanel.onMessage(type, payload)
|
|
181652
181876
|
}
|
|
181877
|
+
|
|
181878
|
+
messaging.send(type, payload)
|
|
181653
181879
|
})
|
|
181654
181880
|
|
|
181655
|
-
|
|
181881
|
+
// ============================================================
|
|
181882
|
+
// Boot
|
|
181883
|
+
// ============================================================
|
|
181884
|
+
|
|
181885
|
+
if (CONFIG.hideBadge) {
|
|
181886
|
+
badge.classList.add('hidden')
|
|
181887
|
+
}
|
|
181888
|
+
|
|
181889
|
+
if (CONFIG.mode === 'child' && parentContext) {
|
|
181890
|
+
badge.textContent = 'PLAYCADEMY CHILD'
|
|
181891
|
+
|
|
181892
|
+
if (!CONFIG.hideRelayPanel) {
|
|
181893
|
+
relayPanel.show(parentContext.intent)
|
|
181894
|
+
}
|
|
181895
|
+
}
|
|
181896
|
+
|
|
181897
|
+
checkSandbox().then(() => initHandshake())
|
|
181656
181898
|
</script>
|
|
181657
181899
|
</body>
|
|
181658
181900
|
</html>
|
|
181659
181901
|
`;
|
|
181660
181902
|
|
|
181661
181903
|
// src/server/middleware.ts
|
|
181904
|
+
function jsonScriptLiteral(json6) {
|
|
181905
|
+
return JSON.stringify(json6).replace(/</g, String.raw`\u003c`);
|
|
181906
|
+
}
|
|
181662
181907
|
function generateLoaderHTML(sandboxUrl, gameSlug, gameId, options, gameUrl) {
|
|
181663
181908
|
const timebackJson = generateTimebackJson(options.timeback);
|
|
181664
181909
|
const mode = getCurrentMode();
|
|
181910
|
+
const parentOptions = getParentOptions();
|
|
181911
|
+
const parentJson = mode === "child" && parentOptions ? JSON.stringify({
|
|
181912
|
+
gameId: parentOptions.gameId ?? "dev-parent-game",
|
|
181913
|
+
intent: parentOptions.intent
|
|
181914
|
+
}) : "null";
|
|
181665
181915
|
const tokenIdentity = mode === "demo" ? "anonymous" : getPlatformRoleOverride() ?? "player";
|
|
181666
181916
|
const gameToken = createSandboxGameToken(gameSlug, tokenIdentity);
|
|
181667
181917
|
const handshakeGameId = gameId || gameSlug;
|
|
181668
|
-
return shell_default2.replace(/{{SANDBOX_URL}}/g, sandboxUrl).replace(/{{GAME_ID}}/g, handshakeGameId).replace(/{{GAME_TOKEN}}/g, gameToken).replace(/{{GAME_URL}}/g, gameUrl || "").replace(/{{MODE}}/g, isShellMode(mode) ? mode : "platform").replace(/{{TIMEBACK_DATA}}/g, timebackJson).replace(/{{HIDE_BADGE}}/g, String(options.hideBadge));
|
|
181918
|
+
return shell_default2.replace(/{{SANDBOX_URL}}/g, () => sandboxUrl).replace(/{{GAME_ID}}/g, () => handshakeGameId).replace(/{{GAME_TOKEN}}/g, () => gameToken).replace(/{{GAME_URL}}/g, () => gameUrl || "").replace(/{{MODE}}/g, () => isShellMode(mode) ? mode : "platform").replace(/{{TIMEBACK_DATA}}/g, () => jsonScriptLiteral(timebackJson)).replace(/{{PARENT_DATA}}/g, () => jsonScriptLiteral(parentJson)).replace(/{{HIDE_BADGE}}/g, () => String(options.display.hideBadge)).replace(/{{BADGE_POSITION}}/g, () => options.display.badgePosition).replace(/{{HIDE_RELAY_PANEL}}/g, () => String(options.display.hideRelayPanel)).replace(/{{RELAY_PANEL_POSITION}}/g, () => options.display.relayPanelPosition);
|
|
181669
181919
|
}
|
|
181670
181920
|
function devServerMiddleware(server, sandbox, gameUrl, options) {
|
|
181671
181921
|
server.middlewares.use("/", (req, res, next) => {
|
|
@@ -181724,7 +181974,7 @@ async function recreateSandbox(options) {
|
|
|
181724
181974
|
if (sandbox.project && serverState.gameBackend) {
|
|
181725
181975
|
const gameUrl = `http://localhost:${serverState.gameBackend.port}`;
|
|
181726
181976
|
devServerMiddleware(viteServer, sandbox, gameUrl, {
|
|
181727
|
-
|
|
181977
|
+
display: platformModeOptions.display,
|
|
181728
181978
|
timeback: timebackDisabled ? undefined : {
|
|
181729
181979
|
baseCourses: sandbox.project.timebackCourses ?? [],
|
|
181730
181980
|
overrides: timebackOptions
|
|
@@ -182029,7 +182279,7 @@ async function configurePlatformMode(server, viteConfig, options) {
|
|
|
182029
182279
|
overrides: timebackOptions
|
|
182030
182280
|
};
|
|
182031
182281
|
devServerMiddleware(server, sandbox, gameUrl, {
|
|
182032
|
-
|
|
182282
|
+
display: options.display,
|
|
182033
182283
|
timeback: timebackContext
|
|
182034
182284
|
});
|
|
182035
182285
|
}
|
|
@@ -182081,13 +182331,21 @@ async function configureStandaloneMode(server, viteConfig, options) {
|
|
|
182081
182331
|
// src/server/hotkeys/toggle-mode.ts
|
|
182082
182332
|
async function toggleMode(options) {
|
|
182083
182333
|
const currentMode = getCurrentMode();
|
|
182084
|
-
|
|
182334
|
+
let newMode = getNextMode(currentMode, getParentOptions() !== null);
|
|
182085
182335
|
const viteServer = getViteServerRef();
|
|
182086
182336
|
const prefix2 = createLogPrefix("playcademy");
|
|
182087
182337
|
if (!viteServer) {
|
|
182088
182338
|
options.viteConfig.logger.error(`${prefix2} ${import_picocolors16.red("Cannot toggle mode: no Vite server reference")}`);
|
|
182089
182339
|
return;
|
|
182090
182340
|
}
|
|
182341
|
+
if (newMode === "child") {
|
|
182342
|
+
try {
|
|
182343
|
+
assertChildParentOptions(getParentOptions());
|
|
182344
|
+
} catch (error89) {
|
|
182345
|
+
options.viteConfig.logger.error(`${prefix2} ${import_picocolors16.red(errorMessage(error89))}`);
|
|
182346
|
+
newMode = getNextMode(newMode, false);
|
|
182347
|
+
}
|
|
182348
|
+
}
|
|
182091
182349
|
await cleanupServers();
|
|
182092
182350
|
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
182093
182351
|
setCurrentMode(newMode);
|
|
@@ -182104,7 +182362,7 @@ async function toggleMode(options) {
|
|
|
182104
182362
|
function toggleModeHotkey(options) {
|
|
182105
182363
|
return {
|
|
182106
182364
|
key: "m",
|
|
182107
|
-
description: `${import_picocolors16.cyan(import_picocolors16.bold("[playcademy]"))} cycle
|
|
182365
|
+
description: `${import_picocolors16.cyan(import_picocolors16.bold("[playcademy]"))} cycle dev mode`,
|
|
182108
182366
|
action: () => toggleMode(options)
|
|
182109
182367
|
};
|
|
182110
182368
|
}
|
|
@@ -182145,8 +182403,12 @@ async function configureServerHook(server, context) {
|
|
|
182145
182403
|
if (!context.viteConfig) {
|
|
182146
182404
|
throw new Error("[Playcademy] Vite config not resolved before configureServer");
|
|
182147
182405
|
}
|
|
182406
|
+
if (context.options.mode === "child") {
|
|
182407
|
+
assertChildParentOptions(context.options.parent);
|
|
182408
|
+
}
|
|
182148
182409
|
setViteServerRef(server);
|
|
182149
182410
|
setCurrentMode(context.options.mode);
|
|
182411
|
+
setParentOptions(context.options.parent ?? null);
|
|
182150
182412
|
setupProcessShutdownHandlers();
|
|
182151
182413
|
await registerAssetMiddleware(server, context.viteConfig.root, context.options.configPath);
|
|
182152
182414
|
if (hasActiveServers()) {
|
|
@@ -182164,7 +182426,7 @@ async function configureServerHook(server, context) {
|
|
|
182164
182426
|
seed: context.options.seed,
|
|
182165
182427
|
memoryOnly: context.options.memoryOnly,
|
|
182166
182428
|
databasePath: context.options.databasePath,
|
|
182167
|
-
|
|
182429
|
+
display: context.options.display,
|
|
182168
182430
|
gameBackendPort: context.options.gameBackendPort,
|
|
182169
182431
|
configPath: context.options.configPath,
|
|
182170
182432
|
timeback: context.options.timeback
|
|
@@ -182193,7 +182455,14 @@ function resolveShellSdkId(id) {
|
|
|
182193
182455
|
}
|
|
182194
182456
|
function loadShellSdk(id) {
|
|
182195
182457
|
if (id === SHELL_SDK_ID) {
|
|
182196
|
-
return
|
|
182458
|
+
return [
|
|
182459
|
+
`export { MessageEvents, messaging } from '@playcademy/sdk'`,
|
|
182460
|
+
`import * as internal from '@playcademy/sdk/internal'`,
|
|
182461
|
+
`export const HANDSHAKE_RESEND_INTERVAL_MS = internal.HANDSHAKE_RESEND_INTERVAL_MS ?? 300`,
|
|
182462
|
+
`export const HANDSHAKE_MAX_DURATION_MS = internal.HANDSHAKE_MAX_DURATION_MS ?? 30000`,
|
|
182463
|
+
`export const SDK_SUPPORTS_CHILD_MODE = internal.HANDSHAKE_MAX_DURATION_MS !== undefined`
|
|
182464
|
+
].join(`
|
|
182465
|
+
`);
|
|
182197
182466
|
}
|
|
182198
182467
|
}
|
|
182199
182468
|
|
|
@@ -182266,6 +182535,7 @@ function resolveOptions(options) {
|
|
|
182266
182535
|
return {
|
|
182267
182536
|
configPath: options.configPath,
|
|
182268
182537
|
mode: options.mode ?? "platform",
|
|
182538
|
+
parent: options.parent,
|
|
182269
182539
|
gameBackendPort: options.gameBackendPort ?? DEFAULT_PORTS4.GAME,
|
|
182270
182540
|
sandboxPort,
|
|
182271
182541
|
dashboardAppPort: options.dashboardAppPort ?? DEFAULT_PORTS4.DASHBOARD_APP,
|
|
@@ -182279,7 +182549,12 @@ function resolveOptions(options) {
|
|
|
182279
182549
|
seed: sandboxOptions.seed ?? true,
|
|
182280
182550
|
memoryOnly: sandboxOptions.memoryOnly ?? false,
|
|
182281
182551
|
databasePath: sandboxOptions.databasePath,
|
|
182282
|
-
|
|
182552
|
+
display: {
|
|
182553
|
+
hideBadge: displayOptions.hideBadge ?? false,
|
|
182554
|
+
badgePosition: displayOptions.badgePosition ?? "top-left",
|
|
182555
|
+
hideRelayPanel: displayOptions.hideRelayPanel ?? false,
|
|
182556
|
+
relayPanelPosition: displayOptions.relayPanelPosition ?? "bottom-left"
|
|
182557
|
+
},
|
|
182283
182558
|
timeback: options.timeback
|
|
182284
182559
|
};
|
|
182285
182560
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ViteDevServer } from 'vite';
|
|
2
|
-
import type { SandboxManager, TimebackPluginContext } from '../types';
|
|
2
|
+
import type { ResolvedDisplayOptions, SandboxManager, TimebackPluginContext } from '../types';
|
|
3
3
|
export interface ShellOptions {
|
|
4
|
-
|
|
4
|
+
display: ResolvedDisplayOptions;
|
|
5
5
|
timeback?: TimebackPluginContext;
|
|
6
6
|
}
|
|
7
|
+
export declare function generateLoaderHTML(sandboxUrl: string, gameSlug: string, gameId: string, options: ShellOptions, gameUrl?: string): string;
|
|
7
8
|
export declare function devServerMiddleware(server: ViteDevServer, sandbox: SandboxManager, gameUrl: string | undefined, options: ShellOptions): void;
|
package/dist/server/modes.d.ts
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
import type { PlaycademyMode } from '../types/options';
|
|
1
|
+
import type { PlaycademyMode, PlaycademyParentOptions } from '../types/options';
|
|
2
2
|
export type ShellMode = Exclude<PlaycademyMode, 'standalone'>;
|
|
3
|
+
/**
|
|
4
|
+
* Validates the `parent` plugin option for child mode. TypeScript users
|
|
5
|
+
* cannot get this wrong, but a plain-JS vite config can pass a hollow
|
|
6
|
+
* object; without this check the failure surfaces as an unrelated
|
|
7
|
+
* TypeError inside the child game, nowhere near the config that caused
|
|
8
|
+
* it.
|
|
9
|
+
*/
|
|
10
|
+
export declare function assertChildParentOptions(parent: PlaycademyParentOptions | undefined | null): void;
|
|
3
11
|
export declare function isShellMode(mode: PlaycademyMode): mode is ShellMode;
|
|
4
|
-
|
|
12
|
+
/**
|
|
13
|
+
* The `m` hotkey's cycle. `'child'` joins it only when a parent context is
|
|
14
|
+
* configured: cycling a game into child mode with no intent to deliver
|
|
15
|
+
* would boot it into a meaningless state.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getNextMode(mode: PlaycademyMode, includeChild: boolean): PlaycademyMode;
|
package/dist/server/state.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import type { ViteDevServer } from 'vite';
|
|
10
10
|
import type { DashboardAppManager } from '../lib/dashboard-app';
|
|
11
11
|
import type { GameBackendServerManager, PlatformRoleOverride, SandboxManager, TimebackRoleOverride } from '../types';
|
|
12
|
-
import type { PlaycademyMode } from '../types/options';
|
|
12
|
+
import type { PlaycademyMode, PlaycademyParentOptions } from '../types/options';
|
|
13
13
|
/**
|
|
14
14
|
* Module-level server references
|
|
15
15
|
*/
|
|
@@ -19,6 +19,7 @@ export declare const serverState: {
|
|
|
19
19
|
dashboardApp: DashboardAppManager | null;
|
|
20
20
|
viteServer: ViteDevServer | null;
|
|
21
21
|
currentMode: PlaycademyMode;
|
|
22
|
+
parentOptions: PlaycademyParentOptions | null;
|
|
22
23
|
timebackRoleOverride: TimebackRoleOverride | null;
|
|
23
24
|
platformRoleOverride: PlatformRoleOverride | null;
|
|
24
25
|
};
|
|
@@ -38,6 +39,14 @@ export declare function hasActiveServers(): boolean;
|
|
|
38
39
|
* Get current mode
|
|
39
40
|
*/
|
|
40
41
|
export declare function getCurrentMode(): PlaycademyMode;
|
|
42
|
+
/**
|
|
43
|
+
* Get the parent-game context for child mode (null unless configured)
|
|
44
|
+
*/
|
|
45
|
+
export declare function getParentOptions(): PlaycademyParentOptions | null;
|
|
46
|
+
/**
|
|
47
|
+
* Set the parent-game context for child mode
|
|
48
|
+
*/
|
|
49
|
+
export declare function setParentOptions(parent: PlaycademyParentOptions | null): void;
|
|
41
50
|
/**
|
|
42
51
|
* Set current mode
|
|
43
52
|
*/
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Type exports for the Playcademy Vite Plugin
|
|
3
3
|
*/
|
|
4
|
-
export type { PlaycademyExportOptions, PlaycademySandboxOptions, PlaycademyDisplayOptions, PlaycademyPluginOptions, PlaycademyTimebackOptions, PlaycademyMode, } from './options';
|
|
5
|
-
export type { BannerOptions, GameBackendDevServerOptions, GameBackendServerManager, HotkeyOptions, PlatformModeOptions, PlatformRoleOverride, PlaycademyOutputData, PluginContext, ProjectInfo, ResolvedPluginOptions, SandboxManager, StandaloneModeOptions, TimebackCourseConfig, TimebackPluginContext, TimebackRoleOverride, } from './internal';
|
|
4
|
+
export type { PlaycademyExportOptions, PlaycademySandboxOptions, PlaycademyDisplayOptions, PlaycademyPluginOptions, PlaycademyTimebackOptions, PlaycademyMode, PlaycademyParentOptions, ShellCorner, } from './options';
|
|
5
|
+
export type { BannerOptions, GameBackendDevServerOptions, GameBackendServerManager, HotkeyOptions, PlatformModeOptions, PlatformRoleOverride, PlaycademyOutputData, PluginContext, ProjectInfo, ResolvedDisplayOptions, ResolvedPluginOptions, SandboxManager, StandaloneModeOptions, TimebackCourseConfig, TimebackPluginContext, TimebackRoleOverride, } from './internal';
|
|
6
6
|
export { TIMEBACK_ROLES, PLATFORM_ROLES } from './internal';
|
package/dist/types/internal.d.ts
CHANGED
|
@@ -2,7 +2,16 @@
|
|
|
2
2
|
* Internal plugin state and context types
|
|
3
3
|
*/
|
|
4
4
|
import type { ResolvedConfig } from 'vite';
|
|
5
|
-
import type { PlaycademyMode, PlaycademyTimebackOptions } from './options';
|
|
5
|
+
import type { PlaycademyMode, PlaycademyParentOptions, PlaycademyTimebackOptions, ShellCorner } from './options';
|
|
6
|
+
/**
|
|
7
|
+
* Display options with every default applied.
|
|
8
|
+
*/
|
|
9
|
+
export interface ResolvedDisplayOptions {
|
|
10
|
+
hideBadge: boolean;
|
|
11
|
+
badgePosition: ShellCorner;
|
|
12
|
+
hideRelayPanel: boolean;
|
|
13
|
+
relayPanelPosition: ShellCorner;
|
|
14
|
+
}
|
|
6
15
|
/**
|
|
7
16
|
* TimeBack roles that can be cycled through in dev mode
|
|
8
17
|
*/
|
|
@@ -19,6 +28,7 @@ export type PlatformRoleOverride = (typeof PLATFORM_ROLES)[number];
|
|
|
19
28
|
export interface ResolvedPluginOptions {
|
|
20
29
|
configPath?: string;
|
|
21
30
|
mode: PlaycademyMode;
|
|
31
|
+
parent?: PlaycademyParentOptions;
|
|
22
32
|
gameBackendPort: number;
|
|
23
33
|
sandboxPort: number;
|
|
24
34
|
dashboardAppPort: number;
|
|
@@ -32,7 +42,7 @@ export interface ResolvedPluginOptions {
|
|
|
32
42
|
seed: boolean;
|
|
33
43
|
memoryOnly: boolean;
|
|
34
44
|
databasePath?: string;
|
|
35
|
-
|
|
45
|
+
display: ResolvedDisplayOptions;
|
|
36
46
|
timeback?: PlaycademyTimebackOptions | false;
|
|
37
47
|
}
|
|
38
48
|
/**
|
|
@@ -134,7 +144,7 @@ export interface PlatformModeOptions {
|
|
|
134
144
|
seed: boolean;
|
|
135
145
|
memoryOnly: boolean;
|
|
136
146
|
databasePath?: string;
|
|
137
|
-
|
|
147
|
+
display: ResolvedDisplayOptions;
|
|
138
148
|
gameBackendPort: number;
|
|
139
149
|
configPath?: string;
|
|
140
150
|
timeback?: PlaycademyTimebackOptions | false;
|
package/dist/types/options.d.ts
CHANGED
|
@@ -1,17 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Plugin configuration options
|
|
3
3
|
*/
|
|
4
|
+
import type { ParentGameContext, PlaycademyMode as SdkPlaycademyMode } from '@playcademy/sdk/types';
|
|
4
5
|
/**
|
|
5
6
|
* Plugin operation mode
|
|
6
7
|
*
|
|
7
8
|
* Controls how the Vite plugin operates during development:
|
|
8
9
|
* - `'platform'`: Full Playcademy platform experience with sandbox server, backend bundling, and shell wrapper (default)
|
|
9
10
|
* - `'demo'`: Shell-backed development mode that initializes the SDK with `mode: 'demo'`
|
|
11
|
+
* - `'child'`: Shell impersonates a parent game: INIT carries `mode: 'child'` and the
|
|
12
|
+
* `parent` block, and the shell displays what the game relays (requires the `parent` option)
|
|
10
13
|
* - `'standalone'`: Backend only, no sandbox or shell
|
|
11
14
|
*
|
|
15
|
+
* The union is the SDK's own `PlaycademyMode`, aliased so the two can
|
|
16
|
+
* never drift: whatever mode the plugin serves lands verbatim in the
|
|
17
|
+
* game's INIT payload.
|
|
18
|
+
*
|
|
12
19
|
* @default 'platform'
|
|
13
20
|
*/
|
|
14
|
-
export type PlaycademyMode =
|
|
21
|
+
export type PlaycademyMode = SdkPlaycademyMode;
|
|
22
|
+
/**
|
|
23
|
+
* Parent-game context the dev shell presents in `mode: 'child'`.
|
|
24
|
+
*
|
|
25
|
+
* In production a child game is launched by a parent game via
|
|
26
|
+
* `client.embed.launch()`; the dev shell plays that parent role locally so a
|
|
27
|
+
* child game can be developed without one. See the parent-child game
|
|
28
|
+
* embedding proposal (`docs/dev/timeback/` in the platform repo).
|
|
29
|
+
*
|
|
30
|
+
* Fields mirror the SDK's `ParentGameContext`, and each is typed from it
|
|
31
|
+
* directly. The block the dev shell actually sends is additionally checked
|
|
32
|
+
* against `ParentGameContext` where it is built (see the server middleware),
|
|
33
|
+
* so it cannot drift from what a real parent game would send.
|
|
34
|
+
*/
|
|
35
|
+
export interface PlaycademyParentOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Parent game id stamped into the INIT payload's `parent` block.
|
|
38
|
+
*
|
|
39
|
+
* @default 'dev-parent-game'
|
|
40
|
+
*/
|
|
41
|
+
gameId?: ParentGameContext['gameId'];
|
|
42
|
+
/**
|
|
43
|
+
* The `LaunchIntent` delivered to the game as `client.parent.intent`:
|
|
44
|
+
* which lesson to serve (`lessonId`, in the game's own vocabulary),
|
|
45
|
+
* and at which pedagogy stage (`eLevel`, the platform's E1-E4 taxonomy).
|
|
46
|
+
*/
|
|
47
|
+
intent: ParentGameContext['intent'];
|
|
48
|
+
}
|
|
15
49
|
/**
|
|
16
50
|
* Configuration for developing a game's dashboard app.
|
|
17
51
|
*
|
|
@@ -276,25 +310,43 @@ export interface PlaycademyTimebackOptions {
|
|
|
276
310
|
*/
|
|
277
311
|
courses?: Record<string, 'mock' | string | null | false>;
|
|
278
312
|
}
|
|
313
|
+
/**
|
|
314
|
+
* Screen corner for the dev shell's overlay elements.
|
|
315
|
+
*/
|
|
316
|
+
export type ShellCorner = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
279
317
|
/**
|
|
280
318
|
* Configuration options for the development shell wrapper
|
|
281
319
|
*
|
|
282
320
|
* The shell provides the platform UI during development, including the
|
|
283
|
-
* Playcademy badge and iframe wrapper used by
|
|
321
|
+
* Playcademy badge and iframe wrapper used by shell modes (`platform`,
|
|
322
|
+
* `demo`, and `child`).
|
|
284
323
|
*/
|
|
285
324
|
export interface PlaycademyDisplayOptions {
|
|
286
325
|
/**
|
|
287
|
-
* Hide the Playcademy badge
|
|
326
|
+
* Hide the Playcademy badge during development.
|
|
288
327
|
*
|
|
289
328
|
* @default false
|
|
290
|
-
* @example
|
|
291
|
-
* ```ts
|
|
292
|
-
* display: {
|
|
293
|
-
* hideBadge: true // Hide the badge
|
|
294
|
-
* }
|
|
295
|
-
* ```
|
|
296
329
|
*/
|
|
297
330
|
hideBadge?: boolean;
|
|
331
|
+
/**
|
|
332
|
+
* Corner the badge sits in.
|
|
333
|
+
*
|
|
334
|
+
* @default 'top-left'
|
|
335
|
+
*/
|
|
336
|
+
badgePosition?: ShellCorner;
|
|
337
|
+
/**
|
|
338
|
+
* Hide the child-mode relay panel (the live view of what the game
|
|
339
|
+
* relays to its parent: timing and the end-activity report).
|
|
340
|
+
*
|
|
341
|
+
* @default false
|
|
342
|
+
*/
|
|
343
|
+
hideRelayPanel?: boolean;
|
|
344
|
+
/**
|
|
345
|
+
* Corner the relay panel sits in.
|
|
346
|
+
*
|
|
347
|
+
* @default 'bottom-left'
|
|
348
|
+
*/
|
|
349
|
+
relayPanelPosition?: ShellCorner;
|
|
298
350
|
}
|
|
299
351
|
/**
|
|
300
352
|
* Main configuration options for the Playcademy Vite plugin
|
|
@@ -345,6 +397,7 @@ export interface PlaycademyPluginOptions {
|
|
|
345
397
|
*
|
|
346
398
|
* - `'platform'`: Full development experience with sandbox server and shell (recommended)
|
|
347
399
|
* - `'demo'`: Sandbox + shell, but initializes the SDK with `mode: 'demo'`
|
|
400
|
+
* - `'child'`: Sandbox + shell, but the shell plays a parent game (requires `parent`)
|
|
348
401
|
* - `'standalone'`: Backend bundling only, no platform features
|
|
349
402
|
*
|
|
350
403
|
* Most games should use `'platform'` mode; use `'demo'` to exercise
|
|
@@ -359,6 +412,20 @@ export interface PlaycademyPluginOptions {
|
|
|
359
412
|
* ```
|
|
360
413
|
*/
|
|
361
414
|
mode?: PlaycademyMode;
|
|
415
|
+
/**
|
|
416
|
+
* Parent-game context for `mode: 'child'`: what the dev shell's INIT
|
|
417
|
+
* carries in its `parent` block. Required when `mode` is `'child'`;
|
|
418
|
+
* also enables `'child'` in the `m` hotkey's mode cycle.
|
|
419
|
+
*
|
|
420
|
+
* @example
|
|
421
|
+
* ```ts
|
|
422
|
+
* {
|
|
423
|
+
* mode: 'child',
|
|
424
|
+
* parent: { intent: { lessonId: 'two-digit-add', eLevel: 'E2' } }
|
|
425
|
+
* }
|
|
426
|
+
* ```
|
|
427
|
+
*/
|
|
428
|
+
parent?: PlaycademyParentOptions;
|
|
362
429
|
/**
|
|
363
430
|
* Develop a dashboard app instead of a game.
|
|
364
431
|
*
|
|
@@ -458,7 +525,8 @@ export interface PlaycademyPluginOptions {
|
|
|
458
525
|
* ```ts
|
|
459
526
|
* {
|
|
460
527
|
* display: {
|
|
461
|
-
* hideBadge: true
|
|
528
|
+
* hideBadge: true,
|
|
529
|
+
* relayPanelPosition: 'bottom-right'
|
|
462
530
|
* }
|
|
463
531
|
* }
|
|
464
532
|
* ```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playcademy/vite-plugin",
|
|
3
|
-
"version": "1.2.1-beta.
|
|
3
|
+
"version": "1.2.1-beta.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -19,21 +19,21 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"archiver": "^7.0.1",
|
|
21
21
|
"picocolors": "^1.1.1",
|
|
22
|
-
"playcademy": "0.28.1-beta.
|
|
22
|
+
"playcademy": "0.28.1-beta.5"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@electric-sql/pglite": "^0.3.16",
|
|
26
26
|
"@inquirer/prompts": "^7.8.6",
|
|
27
27
|
"@playcademy/constants": "0.0.1",
|
|
28
|
-
"@playcademy/sandbox": "0.7.1-beta.
|
|
29
|
-
"@playcademy/sdk": "0.16.1-beta.
|
|
28
|
+
"@playcademy/sandbox": "0.7.1-beta.5",
|
|
29
|
+
"@playcademy/sdk": "0.16.1-beta.5",
|
|
30
30
|
"@playcademy/types": "0.0.1",
|
|
31
31
|
"@playcademy/utils": "0.0.1",
|
|
32
32
|
"@types/archiver": "^6.0.3",
|
|
33
33
|
"@types/bun": "1.3.5"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@playcademy/sdk": ">=0.
|
|
36
|
+
"@playcademy/sdk": ">=0.17.0",
|
|
37
37
|
"typescript": "^5 || ^6",
|
|
38
38
|
"vite": "^5 || ^6 || ^7 || ^8"
|
|
39
39
|
}
|