@playcademy/vite-plugin 1.2.1-beta.3 → 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 +312 -36
- 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: {
|
|
@@ -48521,6 +48546,7 @@ var init_d1 = __esm(() => {
|
|
|
48521
48546
|
var init_kv = () => {};
|
|
48522
48547
|
var RESERVED_SUBDOMAINS;
|
|
48523
48548
|
var init_hostname = __esm(() => {
|
|
48549
|
+
init_src();
|
|
48524
48550
|
RESERVED_SUBDOMAINS = new Set(["www", "hub", "cdn", "realtime", "admin", "docs", "api"]);
|
|
48525
48551
|
});
|
|
48526
48552
|
var init_mime = () => {};
|
|
@@ -50747,9 +50773,9 @@ var init_helpers3 = __esm(() => {
|
|
|
50747
50773
|
init_mime();
|
|
50748
50774
|
});
|
|
50749
50775
|
var init_provider = __esm(() => {
|
|
50750
|
-
init_src();
|
|
50751
50776
|
init_src();
|
|
50752
50777
|
init_core();
|
|
50778
|
+
init_hostname();
|
|
50753
50779
|
init_constants2();
|
|
50754
50780
|
init_assets_config();
|
|
50755
50781
|
init_bindings();
|
|
@@ -181513,10 +181539,24 @@ var shell_default2 = `<!doctype html>
|
|
|
181513
181539
|
height: 100vh;
|
|
181514
181540
|
overflow: hidden;
|
|
181515
181541
|
}
|
|
181516
|
-
|
|
181517
|
-
|
|
181542
|
+
.corner-top-left {
|
|
181543
|
+
top: 0.5rem;
|
|
181544
|
+
left: 0.5rem;
|
|
181545
|
+
}
|
|
181546
|
+
.corner-top-right {
|
|
181518
181547
|
top: 0.5rem;
|
|
181548
|
+
right: 0.5rem;
|
|
181549
|
+
}
|
|
181550
|
+
.corner-bottom-left {
|
|
181551
|
+
bottom: 0.5rem;
|
|
181519
181552
|
left: 0.5rem;
|
|
181553
|
+
}
|
|
181554
|
+
.corner-bottom-right {
|
|
181555
|
+
bottom: 0.5rem;
|
|
181556
|
+
right: 0.5rem;
|
|
181557
|
+
}
|
|
181558
|
+
#badge {
|
|
181559
|
+
position: fixed;
|
|
181520
181560
|
background: rgba(23, 23, 23, 0.9);
|
|
181521
181561
|
color: white;
|
|
181522
181562
|
padding: 0.25rem 0.5rem;
|
|
@@ -181531,6 +181571,28 @@ var shell_default2 = `<!doctype html>
|
|
|
181531
181571
|
#badge.offline {
|
|
181532
181572
|
background: rgba(211, 47, 47, 0.9);
|
|
181533
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
|
+
}
|
|
181534
181596
|
#frame {
|
|
181535
181597
|
position: absolute;
|
|
181536
181598
|
inset: 0;
|
|
@@ -181544,11 +181606,26 @@ var shell_default2 = `<!doctype html>
|
|
|
181544
181606
|
</style>
|
|
181545
181607
|
</head>
|
|
181546
181608
|
<body>
|
|
181547
|
-
<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>
|
|
181548
181615
|
<iframe id="frame" class="hidden"></iframe>
|
|
181549
181616
|
|
|
181550
181617
|
<script type="module">
|
|
181551
|
-
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
|
+
// ============================================================
|
|
181552
181629
|
|
|
181553
181630
|
const CONFIG = {
|
|
181554
181631
|
sandboxUrl: '{{SANDBOX_URL}}',
|
|
@@ -181556,27 +181633,149 @@ var shell_default2 = `<!doctype html>
|
|
|
181556
181633
|
gameToken: '{{GAME_TOKEN}}',
|
|
181557
181634
|
gameUrl: '{{GAME_URL}}' || undefined,
|
|
181558
181635
|
mode: '{{MODE}}',
|
|
181559
|
-
timebackJson:
|
|
181636
|
+
timebackJson: {{TIMEBACK_DATA}},
|
|
181637
|
+
parentJson: {{PARENT_DATA}},
|
|
181560
181638
|
hideBadge: '{{HIDE_BADGE}}' === 'true',
|
|
181639
|
+
hideRelayPanel: '{{HIDE_RELAY_PANEL}}' === 'true',
|
|
181561
181640
|
}
|
|
181562
181641
|
|
|
181563
|
-
|
|
181642
|
+
function parseJsonOrNull(raw) {
|
|
181564
181643
|
try {
|
|
181565
|
-
return JSON.parse(
|
|
181644
|
+
return JSON.parse(raw)
|
|
181566
181645
|
} catch {
|
|
181567
181646
|
return null
|
|
181568
181647
|
}
|
|
181569
|
-
}
|
|
181648
|
+
}
|
|
181570
181649
|
|
|
181571
|
-
const
|
|
181572
|
-
const
|
|
181650
|
+
const timeback = parseJsonOrNull(CONFIG.timebackJson)
|
|
181651
|
+
const parentContext = parseJsonOrNull(CONFIG.parentJson)
|
|
181573
181652
|
|
|
181574
|
-
if (CONFIG.
|
|
181575
|
-
|
|
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
|
+
)
|
|
181576
181657
|
}
|
|
181577
181658
|
|
|
181659
|
+
// ============================================================
|
|
181660
|
+
// DOM + logging
|
|
181661
|
+
// ============================================================
|
|
181662
|
+
|
|
181663
|
+
const badge = document.getElementById('badge')
|
|
181664
|
+
const frame = document.getElementById('frame')
|
|
181665
|
+
|
|
181578
181666
|
const log = (...args) => window.PLAYCADEMY_DEBUG && console.log('[DevShell]', ...args)
|
|
181579
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
|
+
|
|
181580
181779
|
async function checkSandbox() {
|
|
181581
181780
|
// Use the public /health endpoint as a pure liveness probe.
|
|
181582
181781
|
// We used to hit /api/users/me here, but that's \`requireNonAnonymous\`
|
|
@@ -181597,14 +181796,25 @@ var shell_default2 = `<!doctype html>
|
|
|
181597
181796
|
}
|
|
181598
181797
|
}
|
|
181599
181798
|
|
|
181600
|
-
|
|
181799
|
+
// ============================================================
|
|
181800
|
+
// INIT handshake (shell → game)
|
|
181801
|
+
// ============================================================
|
|
181802
|
+
|
|
181803
|
+
function initHandshake() {
|
|
181601
181804
|
const payload = {
|
|
181602
181805
|
baseUrl: CONFIG.sandboxUrl,
|
|
181603
181806
|
gameUrl: CONFIG.gameUrl,
|
|
181604
181807
|
gameId: CONFIG.gameId,
|
|
181605
181808
|
token: CONFIG.gameToken,
|
|
181606
181809
|
mode: CONFIG.mode,
|
|
181607
|
-
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,
|
|
181608
181818
|
}
|
|
181609
181819
|
|
|
181610
181820
|
let interval, timeout
|
|
@@ -181627,11 +181837,14 @@ var shell_default2 = `<!doctype html>
|
|
|
181627
181837
|
frame.classList.remove('hidden')
|
|
181628
181838
|
log('Frame loaded, starting handshake')
|
|
181629
181839
|
send()
|
|
181630
|
-
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.
|
|
181631
181844
|
timeout = setTimeout(() => {
|
|
181632
181845
|
stop()
|
|
181633
181846
|
log('Handshake timeout')
|
|
181634
|
-
},
|
|
181847
|
+
}, HANDSHAKE_MAX_DURATION_MS)
|
|
181635
181848
|
}
|
|
181636
181849
|
|
|
181637
181850
|
messaging.listen(MessageEvents.READY, () => {
|
|
@@ -181642,29 +181855,67 @@ var shell_default2 = `<!doctype html>
|
|
|
181642
181855
|
frame.src = '/'
|
|
181643
181856
|
}
|
|
181644
181857
|
|
|
181645
|
-
|
|
181646
|
-
|
|
181647
|
-
|
|
181648
|
-
|
|
181649
|
-
|
|
181650
|
-
|
|
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)
|
|
181651
181876
|
}
|
|
181877
|
+
|
|
181878
|
+
messaging.send(type, payload)
|
|
181652
181879
|
})
|
|
181653
181880
|
|
|
181654
|
-
|
|
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())
|
|
181655
181898
|
</script>
|
|
181656
181899
|
</body>
|
|
181657
181900
|
</html>
|
|
181658
181901
|
`;
|
|
181659
181902
|
|
|
181660
181903
|
// src/server/middleware.ts
|
|
181904
|
+
function jsonScriptLiteral(json6) {
|
|
181905
|
+
return JSON.stringify(json6).replace(/</g, String.raw`\u003c`);
|
|
181906
|
+
}
|
|
181661
181907
|
function generateLoaderHTML(sandboxUrl, gameSlug, gameId, options, gameUrl) {
|
|
181662
181908
|
const timebackJson = generateTimebackJson(options.timeback);
|
|
181663
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";
|
|
181664
181915
|
const tokenIdentity = mode === "demo" ? "anonymous" : getPlatformRoleOverride() ?? "player";
|
|
181665
181916
|
const gameToken = createSandboxGameToken(gameSlug, tokenIdentity);
|
|
181666
181917
|
const handshakeGameId = gameId || gameSlug;
|
|
181667
|
-
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);
|
|
181668
181919
|
}
|
|
181669
181920
|
function devServerMiddleware(server, sandbox, gameUrl, options) {
|
|
181670
181921
|
server.middlewares.use("/", (req, res, next) => {
|
|
@@ -181723,7 +181974,7 @@ async function recreateSandbox(options) {
|
|
|
181723
181974
|
if (sandbox.project && serverState.gameBackend) {
|
|
181724
181975
|
const gameUrl = `http://localhost:${serverState.gameBackend.port}`;
|
|
181725
181976
|
devServerMiddleware(viteServer, sandbox, gameUrl, {
|
|
181726
|
-
|
|
181977
|
+
display: platformModeOptions.display,
|
|
181727
181978
|
timeback: timebackDisabled ? undefined : {
|
|
181728
181979
|
baseCourses: sandbox.project.timebackCourses ?? [],
|
|
181729
181980
|
overrides: timebackOptions
|
|
@@ -182028,7 +182279,7 @@ async function configurePlatformMode(server, viteConfig, options) {
|
|
|
182028
182279
|
overrides: timebackOptions
|
|
182029
182280
|
};
|
|
182030
182281
|
devServerMiddleware(server, sandbox, gameUrl, {
|
|
182031
|
-
|
|
182282
|
+
display: options.display,
|
|
182032
182283
|
timeback: timebackContext
|
|
182033
182284
|
});
|
|
182034
182285
|
}
|
|
@@ -182080,13 +182331,21 @@ async function configureStandaloneMode(server, viteConfig, options) {
|
|
|
182080
182331
|
// src/server/hotkeys/toggle-mode.ts
|
|
182081
182332
|
async function toggleMode(options) {
|
|
182082
182333
|
const currentMode = getCurrentMode();
|
|
182083
|
-
|
|
182334
|
+
let newMode = getNextMode(currentMode, getParentOptions() !== null);
|
|
182084
182335
|
const viteServer = getViteServerRef();
|
|
182085
182336
|
const prefix2 = createLogPrefix("playcademy");
|
|
182086
182337
|
if (!viteServer) {
|
|
182087
182338
|
options.viteConfig.logger.error(`${prefix2} ${import_picocolors16.red("Cannot toggle mode: no Vite server reference")}`);
|
|
182088
182339
|
return;
|
|
182089
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
|
+
}
|
|
182090
182349
|
await cleanupServers();
|
|
182091
182350
|
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
182092
182351
|
setCurrentMode(newMode);
|
|
@@ -182103,7 +182362,7 @@ async function toggleMode(options) {
|
|
|
182103
182362
|
function toggleModeHotkey(options) {
|
|
182104
182363
|
return {
|
|
182105
182364
|
key: "m",
|
|
182106
|
-
description: `${import_picocolors16.cyan(import_picocolors16.bold("[playcademy]"))} cycle
|
|
182365
|
+
description: `${import_picocolors16.cyan(import_picocolors16.bold("[playcademy]"))} cycle dev mode`,
|
|
182107
182366
|
action: () => toggleMode(options)
|
|
182108
182367
|
};
|
|
182109
182368
|
}
|
|
@@ -182144,8 +182403,12 @@ async function configureServerHook(server, context) {
|
|
|
182144
182403
|
if (!context.viteConfig) {
|
|
182145
182404
|
throw new Error("[Playcademy] Vite config not resolved before configureServer");
|
|
182146
182405
|
}
|
|
182406
|
+
if (context.options.mode === "child") {
|
|
182407
|
+
assertChildParentOptions(context.options.parent);
|
|
182408
|
+
}
|
|
182147
182409
|
setViteServerRef(server);
|
|
182148
182410
|
setCurrentMode(context.options.mode);
|
|
182411
|
+
setParentOptions(context.options.parent ?? null);
|
|
182149
182412
|
setupProcessShutdownHandlers();
|
|
182150
182413
|
await registerAssetMiddleware(server, context.viteConfig.root, context.options.configPath);
|
|
182151
182414
|
if (hasActiveServers()) {
|
|
@@ -182163,7 +182426,7 @@ async function configureServerHook(server, context) {
|
|
|
182163
182426
|
seed: context.options.seed,
|
|
182164
182427
|
memoryOnly: context.options.memoryOnly,
|
|
182165
182428
|
databasePath: context.options.databasePath,
|
|
182166
|
-
|
|
182429
|
+
display: context.options.display,
|
|
182167
182430
|
gameBackendPort: context.options.gameBackendPort,
|
|
182168
182431
|
configPath: context.options.configPath,
|
|
182169
182432
|
timeback: context.options.timeback
|
|
@@ -182192,7 +182455,14 @@ function resolveShellSdkId(id) {
|
|
|
182192
182455
|
}
|
|
182193
182456
|
function loadShellSdk(id) {
|
|
182194
182457
|
if (id === SHELL_SDK_ID) {
|
|
182195
|
-
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
|
+
`);
|
|
182196
182466
|
}
|
|
182197
182467
|
}
|
|
182198
182468
|
|
|
@@ -182265,6 +182535,7 @@ function resolveOptions(options) {
|
|
|
182265
182535
|
return {
|
|
182266
182536
|
configPath: options.configPath,
|
|
182267
182537
|
mode: options.mode ?? "platform",
|
|
182538
|
+
parent: options.parent,
|
|
182268
182539
|
gameBackendPort: options.gameBackendPort ?? DEFAULT_PORTS4.GAME,
|
|
182269
182540
|
sandboxPort,
|
|
182270
182541
|
dashboardAppPort: options.dashboardAppPort ?? DEFAULT_PORTS4.DASHBOARD_APP,
|
|
@@ -182278,7 +182549,12 @@ function resolveOptions(options) {
|
|
|
182278
182549
|
seed: sandboxOptions.seed ?? true,
|
|
182279
182550
|
memoryOnly: sandboxOptions.memoryOnly ?? false,
|
|
182280
182551
|
databasePath: sandboxOptions.databasePath,
|
|
182281
|
-
|
|
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
|
+
},
|
|
182282
182558
|
timeback: options.timeback
|
|
182283
182559
|
};
|
|
182284
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
|
}
|