@mpgd/cli 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpgd/cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Gunshi CLI for mpgd-kit starter generation and target workflow orchestration.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -51,14 +51,14 @@
51
51
  "typescript": "7.0.2",
52
52
  "@mpgd/adapter-ait": "0.4.2",
53
53
  "@mpgd/adapter-browser": "0.4.2",
54
- "@mpgd/adapter-capacitor": "0.4.2",
55
- "@mpgd/adapter-devvit": "0.6.0",
56
54
  "@mpgd/analytics": "0.3.5",
55
+ "@mpgd/adapter-capacitor": "0.4.2",
56
+ "@mpgd/adapter-devvit": "0.7.0",
57
57
  "@mpgd/bridge": "0.6.0",
58
- "@mpgd/game-services": "0.6.0",
59
58
  "@mpgd/catalog": "0.3.5",
60
- "@mpgd/i18n": "0.5.1",
59
+ "@mpgd/game-services": "0.7.0",
61
60
  "@mpgd/phaser-assets": "0.4.1",
61
+ "@mpgd/i18n": "0.5.1",
62
62
  "@mpgd/platform": "0.5.1",
63
63
  "@mpgd/target-config": "0.6.1"
64
64
  },
@@ -38,9 +38,8 @@ not required. Devvit-owned menu, scheduler, trigger, and form callbacks remain
38
38
  thin `/internal/...` routes and delegate to shared service functions. oRPC
39
39
  Publisher helpers can broadcast results after a task completes, but they do not
40
40
  replace the `devvit.json` scheduler endpoint and should not use process-local
41
- memory when delivery must cross instances. A thin `/api/mpgd/bridge` JSON route
42
- remains available for callers that explicitly configure the adapter's legacy
43
- `endpoint` option; new traffic uses oRPC by default.
41
+ memory when delivery must cross instances. The generated target exposes only
42
+ the oRPC bridge route; the former JSON compatibility route is not generated.
44
43
 
45
44
  The default post entry uses `index.html` for a lightweight inline preview. Its
46
45
  Play button requests the `game` entrypoint, which loads the separate
@@ -19,15 +19,15 @@
19
19
  "fix": "ttsc fix"
20
20
  },
21
21
  "dependencies": {
22
- "@devvit/web": "0.13.7",
22
+ "@devvit/web": "0.13.8",
23
23
  "@mpgd/adapter-devvit": "__MPGD_DEPENDENCY_VERSION_ADAPTER_DEVVIT__",
24
24
  "@mpgd/bridge": "__MPGD_DEPENDENCY_VERSION_BRIDGE__"
25
25
  },
26
26
  "devDependencies": {
27
- "@devvit/start": "0.13.7",
27
+ "@devvit/start": "0.13.8",
28
28
  "@ttsc/unplugin": "0.18.4",
29
29
  "@types/node": "^24.0.0",
30
- "devvit": "0.13.7",
30
+ "devvit": "0.13.8",
31
31
  "ttsc": "0.18.4",
32
32
  "typescript": "7.0.2",
33
33
  "vite": "^8.1.3"
@@ -3,7 +3,6 @@ import type { IncomingMessage, ServerResponse } from 'node:http';
3
3
  import { createServer, context, getServerPort, reddit, redis } from '@devvit/web/server';
4
4
  import type { UiResponse } from '@devvit/web/shared';
5
5
  import {
6
- assertBridgeRequest,
7
6
  createBridgeError,
8
7
  type BridgeRequest,
9
8
  type BridgeResponse,
@@ -17,8 +16,7 @@ import { createBridgeRpcNodeHandler } from '@mpgd/bridge/orpc/node';
17
16
  const redisKeyComponentPattern = /^[A-Za-z0-9:_-]{1,128}$/;
18
17
  const maxStorageKeyLength = 128;
19
18
  const maxEncodedStorageKeyLength = 384;
20
- const maxRequestBodySize = 1_000_000;
21
- const legacyBridgeEndpoint = '/api/mpgd/bridge';
19
+ const maxRequestBodySize = 1_048_576;
22
20
  const leaderboardUpdateMaxAttempts = 3;
23
21
  const leaderboardBackoffBaseMs = 25;
24
22
  const leaderboardLockTtlMs = 2_000;
@@ -59,11 +57,6 @@ async function handleHttpRequest(
59
57
  return;
60
58
  }
61
59
 
62
- if (request.method === 'POST' && requestPathname(request) === legacyBridgeEndpoint) {
63
- await handleLegacyBridgeRequest(request, response);
64
- return;
65
- }
66
-
67
60
  if (await bridgeRpcHandler(request, response)) {
68
61
  return;
69
62
  }
@@ -73,73 +66,6 @@ async function handleHttpRequest(
73
66
  });
74
67
  }
75
68
 
76
- async function handleLegacyBridgeRequest(
77
- request: IncomingMessage,
78
- response: ServerResponse,
79
- ): Promise<void> {
80
- let body: unknown;
81
-
82
- try {
83
- body = await readJsonRequestBody(request, maxRequestBodySize);
84
- } catch (error) {
85
- const responseError = describeLegacyBridgeBodyError(error);
86
-
87
- if (error instanceof RequestBodyTooLargeError) {
88
- discardOversizedRequestBody(request, response);
89
- }
90
-
91
- if (responseError.retryable) {
92
- console.error(`devvit legacy bridge body read failed: ${errorMessage(error)}`, error);
93
- }
94
-
95
- sendJson(
96
- response,
97
- responseError.status,
98
- createBridgeError(
99
- 'unknown',
100
- responseError.code,
101
- responseError.message,
102
- responseError.retryable,
103
- ),
104
- );
105
- return;
106
- }
107
-
108
- let bridgeRequest: BridgeRequest;
109
-
110
- try {
111
- bridgeRequest = assertBridgeRequest(body);
112
- } catch (error) {
113
- console.warn(`devvit legacy bridge validation failed: ${errorMessage(error)}`);
114
- sendJson(
115
- response,
116
- 400,
117
- createBridgeError(
118
- requestIdFromBody(body),
119
- 'INVALID_BRIDGE_REQUEST',
120
- 'Invalid bridge request.',
121
- ),
122
- );
123
- return;
124
- }
125
-
126
- try {
127
- sendJson(response, 200, await handleBridgeRequest(bridgeRequest));
128
- } catch (error) {
129
- console.error(`devvit legacy bridge request failed: ${errorMessage(error)}`, error);
130
- sendJson(
131
- response,
132
- 500,
133
- createBridgeError(
134
- bridgeRequest.id,
135
- 'DEVVIT_BRIDGE_INTERNAL_ERROR',
136
- 'Devvit bridge request failed.',
137
- true,
138
- ),
139
- );
140
- }
141
- }
142
-
143
69
  async function handleCreatePostMenu(response: ServerResponse): Promise<void> {
144
70
  const subredditName = currentSubredditName();
145
71
 
@@ -690,38 +616,6 @@ function requestPathname(request: IncomingMessage): string {
690
616
 
691
617
  class RequestBodyTooLargeError extends Error {}
692
618
 
693
- function describeLegacyBridgeBodyError(error: unknown): {
694
- readonly status: number;
695
- readonly code: string;
696
- readonly message: string;
697
- readonly retryable: boolean;
698
- } {
699
- if (error instanceof RequestBodyTooLargeError) {
700
- return {
701
- status: 413,
702
- code: 'BRIDGE_REQUEST_TOO_LARGE',
703
- message: 'Bridge request body is too large.',
704
- retryable: false,
705
- };
706
- }
707
-
708
- if (error instanceof SyntaxError) {
709
- return {
710
- status: 400,
711
- code: 'INVALID_BRIDGE_REQUEST',
712
- message: 'Bridge request body is not valid JSON.',
713
- retryable: false,
714
- };
715
- }
716
-
717
- return {
718
- status: 500,
719
- code: 'DEVVIT_BRIDGE_INTERNAL_ERROR',
720
- message: 'Bridge request body could not be read.',
721
- retryable: true,
722
- };
723
- }
724
-
725
619
  async function* readRequestBodyChunks(
726
620
  request: IncomingMessage,
727
621
  maxBodySize: number,
@@ -751,19 +645,6 @@ async function drainRequestBody(
751
645
  }
752
646
  }
753
647
 
754
- async function readJsonRequestBody(
755
- request: IncomingMessage,
756
- maxBodySize: number,
757
- ): Promise<unknown> {
758
- const chunks: Buffer[] = [];
759
-
760
- for await (const chunk of readRequestBodyChunks(request, maxBodySize)) {
761
- chunks.push(chunk);
762
- }
763
-
764
- return JSON.parse(Buffer.concat(chunks).toString('utf8'));
765
- }
766
-
767
648
  function assertContentLengthWithinLimit(
768
649
  request: IncomingMessage,
769
650
  maxBodySize: number,
@@ -787,15 +668,6 @@ function discardOversizedRequestBody(
787
668
  request.resume();
788
669
  }
789
670
 
790
- function requestIdFromBody(input: unknown): string {
791
- if (typeof input !== 'object' || input === null || !('id' in input)) {
792
- return 'unknown';
793
- }
794
-
795
- const id = (input as { readonly id?: unknown }).id;
796
- return typeof id === 'string' && id.length > 0 ? id : 'unknown';
797
- }
798
-
799
671
  function setResponseSecurityHeaders(response: ServerResponse): void {
800
672
  response.setHeader('cache-control', 'no-store');
801
673
  response.setHeader('content-security-policy', "default-src 'none'; frame-ancestors 'none'");
@@ -43,7 +43,6 @@
43
43
  },
44
44
  "reddit": {
45
45
  "kind": "devvit-web",
46
- "buildStrategy": "devvit-vite",
47
46
  "gameApp": ".",
48
47
  "wrapperApp": "apps/target-devvit",
49
48
  "adapter": "devvit",
@@ -51,12 +51,8 @@ export function createGameViteSharedConfig(
51
51
  alias: {
52
52
  ...createCatalogAliases({
53
53
  gameRoot: input.gameRoot,
54
- ...(input.productCatalogFile === undefined
55
- ? {}
56
- : { productCatalogFile: input.productCatalogFile }),
57
- ...(input.adPlacementsFile === undefined
58
- ? {}
59
- : { adPlacementsFile: input.adPlacementsFile }),
54
+ productCatalogFile: input.productCatalogFile,
55
+ adPlacementsFile: input.adPlacementsFile,
60
56
  }),
61
57
  '#mpgd-platform-gateway': resolve(input.gameRoot, buildGatewayModule),
62
58
  },
@@ -99,8 +95,8 @@ export function resolveBuildGatewayModule(input: {
99
95
 
100
96
  function createCatalogAliases(input: {
101
97
  readonly gameRoot: string;
102
- readonly productCatalogFile?: string;
103
- readonly adPlacementsFile?: string;
98
+ readonly productCatalogFile: string | undefined;
99
+ readonly adPlacementsFile: string | undefined;
104
100
  }): Record<string, string> {
105
101
  const productCatalogFile = readConfiguredPath(
106
102
  input.productCatalogFile ?? process.env.MPGD_PRODUCT_CATALOG_FILE,
@@ -209,9 +205,8 @@ function resolveCatalogBaseDir(path: string, pairedPath: string, gameRoot: strin
209
205
  }
210
206
  }
211
207
 
212
- console.warn(
208
+ throw new Error(
213
209
  `Could not locate catalog files (${path}, ${pairedPath}) in any expected directory; `
214
- + `falling back to game root ${gameRoot}.`,
210
+ + `checked: ${candidates.filter((candidate) => candidate !== undefined).join(', ')}.`,
215
211
  );
216
- return gameRoot;
217
212
  }