castle-web-cli 0.4.60 → 0.4.61
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/castle-host/host.d.ts +12 -1
- package/dist/castle-host/host.js +87 -3
- package/dist/ide.js +4 -0
- package/dist/serve.js +4 -1
- package/package.json +2 -2
|
@@ -26,6 +26,17 @@ export type GraphqlFetch = (
|
|
|
26
26
|
variables: Record<string, unknown>,
|
|
27
27
|
) => Promise<GraphqlResponse>;
|
|
28
28
|
|
|
29
|
+
export type PlatformHandler = (
|
|
30
|
+
command: unknown,
|
|
31
|
+
params: Record<string, unknown>,
|
|
32
|
+
ctx: HostContext,
|
|
33
|
+
) => Promise<unknown>;
|
|
34
|
+
|
|
35
|
+
export interface HostCapabilities {
|
|
36
|
+
graphqlFetch: GraphqlFetch;
|
|
37
|
+
platformHandler?: PlatformHandler;
|
|
38
|
+
}
|
|
39
|
+
|
|
29
40
|
export interface SerializedCommandError {
|
|
30
41
|
code: string;
|
|
31
42
|
message: string;
|
|
@@ -43,5 +54,5 @@ export function executeCommand(
|
|
|
43
54
|
ctx: HostContext,
|
|
44
55
|
command: unknown,
|
|
45
56
|
params: unknown,
|
|
46
|
-
|
|
57
|
+
capabilities: HostCapabilities,
|
|
47
58
|
): Promise<HostResult>;
|
package/dist/castle-host/host.js
CHANGED
|
@@ -24,11 +24,22 @@ const COMMAND_NAMES = [
|
|
|
24
24
|
"leaderboard.save",
|
|
25
25
|
"user.getCurrent",
|
|
26
26
|
"time.getServerTime",
|
|
27
|
+
"pass.has",
|
|
28
|
+
"pass.offer",
|
|
27
29
|
];
|
|
30
|
+
// Platform/capability commands: NOT serviced by graphqlFetch. They're dispatched
|
|
31
|
+
// to the host's optional platformHandler (mobile renders native UI; web shows an
|
|
32
|
+
// upsell). A host with no platformHandler returns the command's normalized
|
|
33
|
+
// "unavailable" outcome rather than an error — capability divergence is the
|
|
34
|
+
// host's concern, never a deck-facing gate.
|
|
35
|
+
const PLATFORM_COMMAND_NAMES = ["pass.offer"];
|
|
28
36
|
function isCommandName(value) {
|
|
29
37
|
return (typeof value === "string" &&
|
|
30
38
|
COMMAND_NAMES.includes(value));
|
|
31
39
|
}
|
|
40
|
+
function isPlatformCommand(command) {
|
|
41
|
+
return PLATFORM_COMMAND_NAMES.includes(command);
|
|
42
|
+
}
|
|
32
43
|
class HostCommandError extends Error {
|
|
33
44
|
code;
|
|
34
45
|
command;
|
|
@@ -41,7 +52,7 @@ class HostCommandError extends Error {
|
|
|
41
52
|
this.extensions = extensions;
|
|
42
53
|
}
|
|
43
54
|
}
|
|
44
|
-
export async function executeCommand(ctx, command, params,
|
|
55
|
+
export async function executeCommand(ctx, command, params, capabilities) {
|
|
45
56
|
if (!isCommandName(command)) {
|
|
46
57
|
return {
|
|
47
58
|
ok: false,
|
|
@@ -53,14 +64,18 @@ export async function executeCommand(ctx, command, params, graphqlFetch) {
|
|
|
53
64
|
};
|
|
54
65
|
}
|
|
55
66
|
try {
|
|
56
|
-
const data = await runCommand(ctx, command, (params ?? {}),
|
|
67
|
+
const data = await runCommand(ctx, command, (params ?? {}), capabilities);
|
|
57
68
|
return { ok: true, data };
|
|
58
69
|
}
|
|
59
70
|
catch (error) {
|
|
60
71
|
return { ok: false, error: toSerializedError(error, command) };
|
|
61
72
|
}
|
|
62
73
|
}
|
|
63
|
-
function runCommand(ctx, command, params,
|
|
74
|
+
function runCommand(ctx, command, params, caps) {
|
|
75
|
+
if (isPlatformCommand(command)) {
|
|
76
|
+
return runPlatformCommand(ctx, command, params, caps);
|
|
77
|
+
}
|
|
78
|
+
const gql = caps.graphqlFetch;
|
|
64
79
|
switch (command) {
|
|
65
80
|
case "deckStorage.load":
|
|
66
81
|
return deckStorageLoad(ctx, gql);
|
|
@@ -78,7 +93,68 @@ function runCommand(ctx, command, params, gql) {
|
|
|
78
93
|
return Promise.resolve(userGetCurrent(ctx));
|
|
79
94
|
case "time.getServerTime":
|
|
80
95
|
return timeGetServerTime(gql);
|
|
96
|
+
case "pass.has":
|
|
97
|
+
return passHas(ctx, params, gql);
|
|
98
|
+
// Platform commands are handled above; listed here to keep the switch
|
|
99
|
+
// exhaustive over CommandName.
|
|
100
|
+
case "pass.offer":
|
|
101
|
+
return runPlatformCommand(ctx, command, params, caps);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Dispatch a platform/capability command to the host's platformHandler. No
|
|
105
|
+
// handler → the command's normalized "unavailable" outcome (a SUCCESS, not an
|
|
106
|
+
// error: a deck on a host without this capability still gets one uniform
|
|
107
|
+
// result). The handler's return is normalized so a malformed outcome can't
|
|
108
|
+
// leak through to the deck.
|
|
109
|
+
async function runPlatformCommand(ctx, command, params, caps) {
|
|
110
|
+
switch (command) {
|
|
111
|
+
case "pass.offer":
|
|
112
|
+
return passesOffer(ctx, params, caps);
|
|
113
|
+
default:
|
|
114
|
+
return unavailableOutcome(command);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function passHas(ctx, params, gql) {
|
|
118
|
+
const deckId = requireDeckId(ctx, "pass.has");
|
|
119
|
+
const passId = asString(params.passId, "passId", "pass.has");
|
|
120
|
+
const data = await graphql(gql, PASSES_FOR_DECK_QUERY, { deckId }, "pass.has");
|
|
121
|
+
const match = (data.passesForDeck ?? []).find((p) => p?.passId === passId);
|
|
122
|
+
return { hasPass: match?.isActive === true };
|
|
123
|
+
}
|
|
124
|
+
async function passesOffer(ctx, params, caps) {
|
|
125
|
+
const passId = asString(params.passId, "passId", "pass.offer");
|
|
126
|
+
// Hosts without bricks support (dev CLI — no handler at all) get a normalized
|
|
127
|
+
// unavailable, never an error or a thrown MISSING_DECK_ID.
|
|
128
|
+
if (!caps.platformHandler)
|
|
129
|
+
return { status: "unavailable" };
|
|
130
|
+
// Real transaction path (mobile native sheet; web upsell): the pass belongs
|
|
131
|
+
// to a deck, so a trusted deckId is required before handing off.
|
|
132
|
+
const deckId = requireDeckId(ctx, "pass.offer");
|
|
133
|
+
const outcome = await caps.platformHandler("pass.offer", { passId, deckId }, ctx);
|
|
134
|
+
return normalizePassOutcome(outcome);
|
|
135
|
+
}
|
|
136
|
+
function normalizePassOutcome(value) {
|
|
137
|
+
const record = typeof value === "object" && value !== null
|
|
138
|
+
? value
|
|
139
|
+
: {};
|
|
140
|
+
const status = record.status;
|
|
141
|
+
const valid = [
|
|
142
|
+
"purchased",
|
|
143
|
+
"alreadyOwned",
|
|
144
|
+
"cancelled",
|
|
145
|
+
"unavailable",
|
|
146
|
+
];
|
|
147
|
+
if (typeof status === "string" && valid.includes(status)) {
|
|
148
|
+
return { status: status };
|
|
149
|
+
}
|
|
150
|
+
return { status: "cancelled" };
|
|
151
|
+
}
|
|
152
|
+
function unavailableOutcome(command) {
|
|
153
|
+
// Only passes exists today; keep this total over future platform commands.
|
|
154
|
+
if (command === "pass.offer") {
|
|
155
|
+
return { status: "unavailable" };
|
|
81
156
|
}
|
|
157
|
+
return { status: "unavailable" };
|
|
82
158
|
}
|
|
83
159
|
async function deckStorageLoad(ctx, gql) {
|
|
84
160
|
const deckId = requireDeckId(ctx, "deckStorage.load");
|
|
@@ -362,6 +438,14 @@ const SAVE_LEADERBOARD_MUTATION = `
|
|
|
362
438
|
)
|
|
363
439
|
}
|
|
364
440
|
`;
|
|
441
|
+
const PASSES_FOR_DECK_QUERY = `
|
|
442
|
+
query CastlePassesForDeck($deckId: ID!) {
|
|
443
|
+
passesForDeck(deckId: $deckId) {
|
|
444
|
+
passId
|
|
445
|
+
isActive
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
`;
|
|
365
449
|
const SERVER_TIME_QUERY = `
|
|
366
450
|
query CastleServerTime {
|
|
367
451
|
serverTime {
|
package/dist/ide.js
CHANGED
|
@@ -670,6 +670,9 @@ const IDE_STYLES = `
|
|
|
670
670
|
margin-top: 6px;
|
|
671
671
|
max-height: 65px;
|
|
672
672
|
overflow-y: auto;
|
|
673
|
+
/* Hide the scrollbar (Windows draws a persistent one; macOS auto-hides). */
|
|
674
|
+
scrollbar-width: none;
|
|
675
|
+
-ms-overflow-style: none;
|
|
673
676
|
font-size: 12px;
|
|
674
677
|
line-height: 1.5;
|
|
675
678
|
color: #57606a;
|
|
@@ -683,6 +686,7 @@ const IDE_STYLES = `
|
|
|
683
686
|
);
|
|
684
687
|
mask-image: linear-gradient(to bottom, transparent, #000000 5px, #000000 100%);
|
|
685
688
|
}
|
|
689
|
+
.task-feed::-webkit-scrollbar { width: 0; height: 0; }
|
|
686
690
|
.task-row { display: flex; align-items: center; gap: 8px; }
|
|
687
691
|
.task-title {
|
|
688
692
|
flex: 1 1 auto;
|
package/dist/serve.js
CHANGED
|
@@ -93,7 +93,10 @@ async function devHostContext(projectDir) {
|
|
|
93
93
|
}
|
|
94
94
|
async function handleCastleCommand(ws, projectDir, requestId, command, params) {
|
|
95
95
|
const ctx = await devHostContext(projectDir);
|
|
96
|
-
|
|
96
|
+
// The dev server services data commands via GraphQL but supplies no
|
|
97
|
+
// platformHandler, so platform/capability commands (e.g. passes.purchase)
|
|
98
|
+
// resolve to their normalized "unavailable" outcome.
|
|
99
|
+
const result = await executeCommand(ctx, command, params, { graphqlFetch: castleGraphql });
|
|
97
100
|
if (ws.readyState === WebSocket.OPEN) {
|
|
98
101
|
ws.send(JSON.stringify({ type: 'castle_command_response', castleSdk: 1, requestId, ...result }));
|
|
99
102
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "castle-web-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.61",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"castle-web": "./dist/index.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build": "tsc && cp -R src/castle-host dist/castle-host && rm -rf kits && cp -R ../kits kits",
|
|
9
|
+
"build": "tsc && rm -rf dist/castle-host && cp -R src/castle-host dist/castle-host && rm -rf kits && cp -R ../kits kits",
|
|
10
10
|
"dev": "tsc --watch",
|
|
11
11
|
"check": "eslint . && jscpd && tsc --noEmit"
|
|
12
12
|
},
|