@mpgd/cli 0.53.0 → 0.54.1

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.
@@ -0,0 +1,226 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { lstatSync, mkdirSync, realpathSync, renameSync, rmSync, writeFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ /**
5
+ * Legal-site paths are contained only after the nearest existing ancestor is
6
+ * canonicalized. A lexical `path.relative` check treats a symlinked `artifacts`
7
+ * directory as inside the project while later writes follow the link.
8
+ */
9
+ export function assertLegalProjectPath(label, resolved) {
10
+ const lexicalRoot = process.cwd();
11
+ const lexical = path.relative(lexicalRoot, resolved);
12
+ if (lexical.startsWith('..') || path.isAbsolute(lexical)) {
13
+ throw new Error(`${label} must be inside the project root: ${resolved}`);
14
+ }
15
+ assertNoLegalSymlink(lexicalRoot, resolved, label);
16
+ const canonicalRoot = realpathSync(lexicalRoot);
17
+ const canonical = realpathBestEffort(resolved);
18
+ const canonicalRelative = path.relative(canonicalRoot, canonical);
19
+ if (canonicalRelative.startsWith('..') || path.isAbsolute(canonicalRelative)) {
20
+ throw new Error(`${label} must be inside the project root: ${resolved}`);
21
+ }
22
+ }
23
+ /** Stage the site beside the destination, then publish with rename. */
24
+ export function publishLegalSiteFiles(outDir, outputs) {
25
+ assertLegalProjectPath('out-dir', outDir);
26
+ // A temp dir on the project root is a different filesystem when out-dir is a
27
+ // mount or sits under a bind-mounted artifacts directory. Rename then fails
28
+ // with EXDEV. Stage on the destination filesystem instead.
29
+ const staging = path.join(legalStagingParent(outDir), `.mpgd-legal-${randomUUID()}`);
30
+ let publishedDirectory = false;
31
+ try {
32
+ for (const output of outputs) {
33
+ const relative = path.relative(outDir, output.file);
34
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
35
+ throw new Error(`Legal site output escapes the output directory: ${output.file}`);
36
+ }
37
+ const staged = path.join(staging, relative);
38
+ mkdirSync(path.dirname(staged), { recursive: true });
39
+ writeFileSync(staged, output.content, { flag: 'wx' });
40
+ }
41
+ assertLegalProjectPath('out-dir', outDir);
42
+ let existing;
43
+ try {
44
+ existing = lstatSync(outDir);
45
+ }
46
+ catch (error) {
47
+ if (!isErrno(error, 'ENOENT')) {
48
+ throw error;
49
+ }
50
+ }
51
+ if (existing?.isSymbolicLink() === true) {
52
+ throw new Error(`Refusing to write legal site output through a symbolic link: ${outDir}`);
53
+ }
54
+ if (existing === undefined) {
55
+ ensureLegalDirectory(path.dirname(outDir));
56
+ assertLegalProjectPath('out-dir', outDir);
57
+ renameSync(staging, outDir);
58
+ publishedDirectory = true;
59
+ return;
60
+ }
61
+ if (!existing.isDirectory()) {
62
+ throw new Error(`Legal site output path is not a directory: ${outDir}`);
63
+ }
64
+ for (const output of outputs) {
65
+ assertNoLegalSymlink(process.cwd(), output.file, 'out-dir');
66
+ ensureLegalDirectory(path.dirname(output.file));
67
+ assertNoLegalSymlink(process.cwd(), output.file, 'out-dir');
68
+ renameSync(path.join(staging, path.relative(outDir, output.file)), output.file);
69
+ }
70
+ }
71
+ finally {
72
+ if (!publishedDirectory) {
73
+ rmSync(staging, { recursive: true, force: true });
74
+ }
75
+ }
76
+ }
77
+ /**
78
+ * Parent for the staging directory. An existing out-dir may itself be a mount,
79
+ * so stage inside it. A missing out-dir is created on its nearest existing
80
+ * ancestor, which is the filesystem the later rename must stay on.
81
+ */
82
+ function legalStagingParent(outDir) {
83
+ const existing = lstatIfPresent(outDir);
84
+ if (existing !== undefined) {
85
+ if (existing.isSymbolicLink()) {
86
+ throw new Error(`Refusing to write legal site output through a symbolic link: ${outDir}`);
87
+ }
88
+ if (!existing.isDirectory()) {
89
+ throw new Error(`Legal site output path is not a directory: ${outDir}`);
90
+ }
91
+ return outDir;
92
+ }
93
+ let current = path.dirname(outDir);
94
+ for (;;) {
95
+ const stat = lstatIfPresent(current);
96
+ if (stat !== undefined) {
97
+ if (stat.isSymbolicLink()) {
98
+ throw new Error(`Refusing to write legal site output through a symbolic link: ${current}`);
99
+ }
100
+ if (!stat.isDirectory()) {
101
+ throw new Error(`Legal site output path is not a directory: ${current}`);
102
+ }
103
+ return current;
104
+ }
105
+ const parent = path.dirname(current);
106
+ if (parent === current) {
107
+ throw new Error(`out-dir must be inside the project root: ${outDir}`);
108
+ }
109
+ current = parent;
110
+ }
111
+ }
112
+ function lstatIfPresent(target) {
113
+ try {
114
+ return lstatSync(target);
115
+ }
116
+ catch (error) {
117
+ if (isErrno(error, 'ENOENT')) {
118
+ return undefined;
119
+ }
120
+ throw error;
121
+ }
122
+ }
123
+ /** Resolve symlinks for the longest existing ancestor, keeping the remainder. */
124
+ function realpathBestEffort(target) {
125
+ // lstat-based existence stops the walk at dangling symlinks, so realpath
126
+ // fails closed on them instead of lexically skipping the link.
127
+ const exists = (candidate) => {
128
+ try {
129
+ lstatSync(candidate);
130
+ return true;
131
+ }
132
+ catch {
133
+ return false;
134
+ }
135
+ };
136
+ let existing = target;
137
+ while (!exists(existing)) {
138
+ const parent = path.dirname(existing);
139
+ if (parent === existing) {
140
+ return existing;
141
+ }
142
+ existing = parent;
143
+ }
144
+ let real;
145
+ try {
146
+ real = realpathSync(existing);
147
+ }
148
+ catch (error) {
149
+ throw new Error(`Legal site path resolves through a broken symbolic link: ${target}`, {
150
+ cause: error,
151
+ });
152
+ }
153
+ return existing === target ? real : path.resolve(real, path.relative(existing, target));
154
+ }
155
+ function assertNoLegalSymlink(root, target, label) {
156
+ const relative = path.relative(root, target);
157
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
158
+ throw new Error(`${label} must be inside the project root: ${target}`);
159
+ }
160
+ const verb = label === 'legal-dir' ? 'read legal sources' : 'write legal site output';
161
+ let current = root;
162
+ for (const part of relative.split(path.sep)) {
163
+ if (part === '' || part === '.') {
164
+ continue;
165
+ }
166
+ current = path.join(current, part);
167
+ let stat;
168
+ try {
169
+ stat = lstatSync(current);
170
+ }
171
+ catch (error) {
172
+ if (isErrno(error, 'ENOENT')) {
173
+ return;
174
+ }
175
+ throw error;
176
+ }
177
+ if (stat.isSymbolicLink()) {
178
+ throw new Error(`Refusing to ${verb} through a symbolic link: ${current}`);
179
+ }
180
+ }
181
+ }
182
+ function ensureLegalDirectory(directory) {
183
+ const root = process.cwd();
184
+ const relative = path.relative(root, directory);
185
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
186
+ throw new Error(`out-dir must be inside the project root: ${directory}`);
187
+ }
188
+ if (relative === '') {
189
+ return;
190
+ }
191
+ let current = root;
192
+ for (const part of relative.split(path.sep)) {
193
+ if (part === '' || part === '.') {
194
+ continue;
195
+ }
196
+ current = path.join(current, part);
197
+ let stat;
198
+ try {
199
+ stat = lstatSync(current);
200
+ }
201
+ catch (error) {
202
+ if (!isErrno(error, 'ENOENT')) {
203
+ throw error;
204
+ }
205
+ try {
206
+ mkdirSync(current);
207
+ continue;
208
+ }
209
+ catch (mkdirError) {
210
+ if (!isErrno(mkdirError, 'EEXIST')) {
211
+ throw mkdirError;
212
+ }
213
+ stat = lstatSync(current);
214
+ }
215
+ }
216
+ if (stat.isSymbolicLink()) {
217
+ throw new Error(`Refusing to write legal site output through a symbolic link: ${current}`);
218
+ }
219
+ if (!stat.isDirectory()) {
220
+ throw new Error(`Legal site output path is not a directory: ${current}`);
221
+ }
222
+ }
223
+ }
224
+ function isErrno(error, code) {
225
+ return error !== null && typeof error === 'object' && 'code' in error && error.code === code;
226
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "kitGitSha": "78bcaad6a3e53131be44f749c27b9377a2fd96dc",
2
+ "kitGitSha": "ed8837541cc3c5737c21638a596ba2dc121112da",
3
3
  "kitDirty": false,
4
- "packageVersion": "0.53.0"
4
+ "packageVersion": "0.54.1"
5
5
  }
@@ -6899,6 +6899,72 @@ var timestampPattern = new RegExp("^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2})
6899
6899
  var fcmAddressKinds = Object.freeze(["fcm-token"]);
6900
6900
  var fcmReservedDataKeys = Object.freeze(["topic", "route"]);
6901
6901
  var tokenRefreshMarginMs = 5 * 60 * 1e3;
6902
+ var sha256K = new Uint32Array([
6903
+ 1116352408,
6904
+ 1899447441,
6905
+ 3049323471,
6906
+ 3921009573,
6907
+ 961987163,
6908
+ 1508970993,
6909
+ 2453635748,
6910
+ 2870763221,
6911
+ 3624381080,
6912
+ 310598401,
6913
+ 607225278,
6914
+ 1426881987,
6915
+ 1925078388,
6916
+ 2162078206,
6917
+ 2614888103,
6918
+ 3248222580,
6919
+ 3835390401,
6920
+ 4022224774,
6921
+ 264347078,
6922
+ 604807628,
6923
+ 770255983,
6924
+ 1249150122,
6925
+ 1555081692,
6926
+ 1996064986,
6927
+ 2554220882,
6928
+ 2821834349,
6929
+ 2952996808,
6930
+ 3210313671,
6931
+ 3336571891,
6932
+ 3584528711,
6933
+ 113926993,
6934
+ 338241895,
6935
+ 666307205,
6936
+ 773529912,
6937
+ 1294757372,
6938
+ 1396182291,
6939
+ 1695183700,
6940
+ 1986661051,
6941
+ 2177026350,
6942
+ 2456956037,
6943
+ 2730485921,
6944
+ 2820302411,
6945
+ 3259730800,
6946
+ 3345764771,
6947
+ 3516065817,
6948
+ 3600352804,
6949
+ 4094571909,
6950
+ 275423344,
6951
+ 430227734,
6952
+ 506948616,
6953
+ 659060556,
6954
+ 883997877,
6955
+ 958139571,
6956
+ 1322822218,
6957
+ 1537002063,
6958
+ 1747873779,
6959
+ 1955562222,
6960
+ 2024104815,
6961
+ 2227730452,
6962
+ 2361852424,
6963
+ 2428436474,
6964
+ 2756734187,
6965
+ 3204031479,
6966
+ 3329325298
6967
+ ]);
6902
6968
 
6903
6969
  // node_modules/.cache/mpgd-native-build/packages/game-services/src/notifications/registry.js
6904
6970
  var notificationHistoryRetentionDays = 30;
@@ -9436,9 +9502,21 @@ var assertReleaseManifestStructure = /* @__PURE__ */ (() => {
9436
9502
  };
9437
9503
  })();
9438
9504
  var fullGitShaPattern = /^[0-9a-f]{40}$/u;
9505
+ var sha256DigestPattern = /^[0-9a-f]{64}$/u;
9439
9506
  function assertReleaseManifest(input) {
9440
9507
  const manifest = assertReleaseManifestStructure(input);
9508
+ if (!fullGitShaPattern.test(manifest.gitSha)) {
9509
+ throw new TypeError("Release manifest gitSha must be a lowercase 40-character SHA.");
9510
+ }
9511
+ if (!fullGitShaPattern.test(manifest.kitGitSha)) {
9512
+ throw new TypeError("Release manifest kitGitSha must be a lowercase 40-character SHA.");
9513
+ }
9441
9514
  for (const [target2, entry] of Object.entries(manifest.targets)) {
9515
+ assertSha256Digest(entry.effectiveConfig.digest, `Release manifest ${target2} effectiveConfig.digest`);
9516
+ assertSha256Digest(entry.iconManifest.digest, `Release manifest ${target2} iconManifest.digest`);
9517
+ assertSha256Digest(entry.iconManifest.sourceSha256, `Release manifest ${target2} iconManifest.sourceSha256`);
9518
+ assertSha256Digest(entry.iconManifest.sharedConfigSha256, `Release manifest ${target2} iconManifest.sharedConfigSha256`);
9519
+ assertSha256Digest(entry.iconManifest.renderConfigSha256, `Release manifest ${target2} iconManifest.renderConfigSha256`);
9442
9520
  const delivery = entry.nativeDelivery;
9443
9521
  if (delivery === void 0) {
9444
9522
  continue;
@@ -9453,9 +9531,6 @@ function assertReleaseManifest(input) {
9453
9531
  throw new TypeError(`Release manifest production native delivery is unsigned: ${target2}.`);
9454
9532
  }
9455
9533
  }
9456
- if (!fullGitShaPattern.test(manifest.kitGitSha)) {
9457
- throw new TypeError("Release manifest kitGitSha must be a lowercase 40-character SHA.");
9458
- }
9459
9534
  if (manifest.releaseIdentity !== void 0) {
9460
9535
  const releaseIdentity = createMpgdReleaseIdentity({
9461
9536
  gameVersion: manifest.releaseIdentity.gameVersion,
@@ -9477,6 +9552,11 @@ function assertReleaseManifest(input) {
9477
9552
  }
9478
9553
  return manifest;
9479
9554
  }
9555
+ function assertSha256Digest(value, label) {
9556
+ if (!sha256DigestPattern.test(value)) {
9557
+ throw new TypeError(`${label} must be a lowercase 64-character SHA-256 digest.`);
9558
+ }
9559
+ }
9480
9560
 
9481
9561
  // node_modules/.cache/mpgd-native-build/tools/target/release-manifest-lock.js
9482
9562
  import { randomUUID } from "node:crypto";
@@ -9575,7 +9655,7 @@ function canRecoverLock(lockFile) {
9575
9655
  // node_modules/.cache/mpgd-native-build/tools/target/generate-release-manifest.js
9576
9656
  var __typia_transform__accessExpressionAsString6 = _accessExpressionAsString_110._accessExpressionAsString;
9577
9657
  var assertProductCatalog = /* @__PURE__ */ (() => {
9578
- const _ae0 = '({ readonly type: "currency"; readonly currency: string & MinLength<1>; readonly amount: number; } | { readonly type: "entitlement"; readonly entitlement: string; } | { readonly type: "resource"; readonly resource: string & MinLength<1>; readonly amount: number & ExclusiveMinimum<0> & Maximum<1.7976931348623157e+308>; })';
9658
+ const _ae0 = '({ readonly type: "currency"; readonly currency: string & MinLength<1>; readonly amount: PositiveLedgerAmount; } | { readonly type: "entitlement"; readonly entitlement: string; } | { readonly type: "resource"; readonly resource: string & MinLength<1>; readonly amount: number & ExclusiveMinimum<0> & Maximum<1.7976931348623157e+308>; })';
9579
9659
  const _ao0 = (input, _path, _exceptionable = true) => ("string" === typeof input.version || _assertGuard_19._assertGuard(_exceptionable, {
9580
9660
  method: "typia.createAssert",
9581
9661
  path: _path + ".version",
@@ -9673,10 +9753,25 @@ var assertProductCatalog = /* @__PURE__ */ (() => {
9673
9753
  path: _path + ".currency",
9674
9754
  expected: "(string & MinLength<1>)",
9675
9755
  value: input.currency
9676
- }, _errorFactory)) && ("number" === typeof input.amount || _assertGuard_19._assertGuard(_exceptionable, {
9756
+ }, _errorFactory)) && ("number" === typeof input.amount && (1 <= input.amount || _assertGuard_19._assertGuard(_exceptionable, {
9677
9757
  method: "typia.createAssert",
9678
9758
  path: _path + ".amount",
9679
- expected: "number",
9759
+ expected: "number & Minimum<1>",
9760
+ value: input.amount
9761
+ }, _errorFactory)) && (input.amount <= 9007199254740991 || _assertGuard_19._assertGuard(_exceptionable, {
9762
+ method: "typia.createAssert",
9763
+ path: _path + ".amount",
9764
+ expected: "number & Maximum<9007199254740991>",
9765
+ value: input.amount
9766
+ }, _errorFactory)) && (input.amount % 1 === 0 || _assertGuard_19._assertGuard(_exceptionable, {
9767
+ method: "typia.createAssert",
9768
+ path: _path + ".amount",
9769
+ expected: "number & MultipleOf<1>",
9770
+ value: input.amount
9771
+ }, _errorFactory)) || _assertGuard_19._assertGuard(_exceptionable, {
9772
+ method: "typia.createAssert",
9773
+ path: _path + ".amount",
9774
+ expected: "(number & Minimum<1> & Maximum<9007199254740991> & MultipleOf<1>)",
9680
9775
  value: input.amount
9681
9776
  }, _errorFactory));
9682
9777
  const _ao3 = (input, _path, _exceptionable = true) => ("entitlement" === input.type || _assertGuard_19._assertGuard(_exceptionable, {
@@ -9749,7 +9844,7 @@ var assertProductCatalog = /* @__PURE__ */ (() => {
9749
9844
  })();
9750
9845
  const _io0 = (input) => "string" === typeof input.version && (void 0 === input.currencies || Array.isArray(input.currencies) && (1 <= input.currencies.length && input.currencies.every((elem) => "string" === typeof elem && 1 <= elem.length))) && (Array.isArray(input.products) && input.products.every((elem) => "object" === typeof elem && null !== elem && _io1(elem)));
9751
9846
  const _io1 = (input) => "string" === typeof input.id && ("consumable" === input.type || "non_consumable" === input.type || "subscription" === input.type) && ("object" === typeof input.grant && null !== input.grant && _iu0(input.grant)) && ("object" === typeof input.platformProductIds && null !== input.platformProductIds && false === Array.isArray(input.platformProductIds) && _io5(input.platformProductIds));
9752
- const _io2 = (input) => "currency" === input.type && ("string" === typeof input.currency && 1 <= input.currency.length) && "number" === typeof input.amount;
9847
+ const _io2 = (input) => "currency" === input.type && ("string" === typeof input.currency && 1 <= input.currency.length) && ("number" === typeof input.amount && (1 <= input.amount && input.amount <= 9007199254740991 && input.amount % 1 === 0));
9753
9848
  const _io3 = (input) => "entitlement" === input.type && "string" === typeof input.entitlement;
9754
9849
  const _io4 = (input) => "resource" === input.type && ("string" === typeof input.resource && 1 <= input.resource.length) && ("number" === typeof input.amount && (0 < input.amount && input.amount <= 17976931348623157e292));
9755
9850
  const _io5 = (input) => Object.keys(input).every((key) => {
@@ -9789,16 +9884,31 @@ var assertProductCatalog = /* @__PURE__ */ (() => {
9789
9884
  };
9790
9885
  })();
9791
9886
  var assertAdPlacements = /* @__PURE__ */ (() => {
9792
- const _ip0 = (input) => "number" === typeof input["amount"];
9887
+ const _ip0 = (input) => "number" === typeof input["amount"] && (1 <= input["amount"] && input["amount"] <= 9007199254740991 && input["amount"] % 1 === 0);
9793
9888
  const _ae0 = '(AdPlacementEntryBase & { readonly type: "banner" | "interstitial"; readonly reward?: never; } | AdPlacementEntryBase & { readonly type: "rewarded"; readonly reward?: AdReward; })';
9794
- const _ae1 = '(undefined | { readonly type: "continue"; readonly amount: number; } | { readonly type: "currency"; readonly amount: number; readonly currency: string & MinLength<1>; })';
9795
- const _ap0 = (input, _path, _exceptionable = true) => "number" === typeof input["amount"] || _assertGuard_19._assertGuard(_exceptionable, {
9889
+ const _ae1 = '(undefined | { readonly type: "continue"; readonly amount: PositiveLedgerAmount; } | { readonly type: "currency"; readonly amount: PositiveLedgerAmount; readonly currency: string & MinLength<1>; })';
9890
+ const _ap0 = (input, _path, _exceptionable = true) => "number" === typeof input["amount"] && (1 <= input["amount"] || _assertGuard_19._assertGuard(_exceptionable, {
9796
9891
  method: "typia.createAssert",
9797
9892
  path: _path + ".amount",
9798
- expected: "number",
9893
+ expected: "number & Minimum<1>",
9894
+ value: input["amount"]
9895
+ }, _errorFactory)) && (input["amount"] <= 9007199254740991 || _assertGuard_19._assertGuard(_exceptionable, {
9896
+ method: "typia.createAssert",
9897
+ path: _path + ".amount",
9898
+ expected: "number & Maximum<9007199254740991>",
9899
+ value: input["amount"]
9900
+ }, _errorFactory)) && (input["amount"] % 1 === 0 || _assertGuard_19._assertGuard(_exceptionable, {
9901
+ method: "typia.createAssert",
9902
+ path: _path + ".amount",
9903
+ expected: "number & MultipleOf<1>",
9904
+ value: input["amount"]
9905
+ }, _errorFactory)) || _assertGuard_19._assertGuard(_exceptionable, {
9906
+ method: "typia.createAssert",
9907
+ path: _path + ".amount",
9908
+ expected: "(number & Minimum<1> & Maximum<9007199254740991> & MultipleOf<1>)",
9799
9909
  value: input["amount"]
9800
9910
  }, _errorFactory);
9801
- const _ae2 = '({ readonly type: "continue"; readonly amount: number; } | { readonly type: "currency"; readonly amount: number; readonly currency: string & MinLength<1>; })';
9911
+ const _ae2 = '({ readonly type: "continue"; readonly amount: PositiveLedgerAmount; } | { readonly type: "currency"; readonly amount: PositiveLedgerAmount; readonly currency: string & MinLength<1>; })';
9802
9912
  const _ao0 = (input, _path, _exceptionable = true) => ("string" === typeof input.version || _assertGuard_19._assertGuard(_exceptionable, {
9803
9913
  method: "typia.createAssert",
9804
9914
  path: _path + ".version",
@@ -10307,9 +10417,47 @@ function readSdkMajor(envValue, metadataValue) {
10307
10417
  function getSourceGitSha(kitGitSha) {
10308
10418
  const configuredGitSha = readOptionalString2(process.env.MPGD_SOURCE_GIT_SHA);
10309
10419
  if (configuredGitSha !== void 0) {
10310
- return configuredGitSha;
10420
+ return requireFullGitSha(configuredGitSha, "MPGD_SOURCE_GIT_SHA");
10311
10421
  }
10312
- return kitGitSha;
10422
+ if (isInRepoKitBuild()) {
10423
+ return inRepoKitSourceGitSha(kitGitSha);
10424
+ }
10425
+ throw new Error("MPGD_SOURCE_GIT_SHA is required for a downstream game build and must be a lowercase 40-character SHA. Refusing to record the kit revision as the game revision.");
10426
+ }
10427
+ function inRepoKitSourceGitSha(kitGitSha) {
10428
+ return requireFullGitSha(kitGitSha, "in-repo kit source Git SHA");
10429
+ }
10430
+ function isInRepoKitBuild() {
10431
+ if (process.env.MPGD_NATIVE_PACKAGED_BUILD === "1") {
10432
+ return false;
10433
+ }
10434
+ const targetsFile = readOptionalString2(process.env.MPGD_PLATFORM_TARGETS_FILE);
10435
+ if (targetsFile === void 0) {
10436
+ return true;
10437
+ }
10438
+ return isInsideDirectory(resolveKitRoot(), targetsFile);
10439
+ }
10440
+ function isInsideDirectory(root, candidate) {
10441
+ let resolvedRoot;
10442
+ try {
10443
+ resolvedRoot = realpathSync6(root);
10444
+ } catch {
10445
+ return false;
10446
+ }
10447
+ const resolvedCandidate = resolve12(candidate);
10448
+ let comparable = resolvedCandidate;
10449
+ try {
10450
+ comparable = realpathSync6(resolvedCandidate);
10451
+ } catch {
10452
+ }
10453
+ const rel = relative9(resolvedRoot, comparable);
10454
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute8(rel);
10455
+ }
10456
+ function requireFullGitSha(value, label) {
10457
+ if (!/^[0-9a-f]{40}$/u.test(value)) {
10458
+ throw new Error(`${label} must be a lowercase 40-character SHA.`);
10459
+ }
10460
+ return value;
10313
10461
  }
10314
10462
  function resolveKitGitSha() {
10315
10463
  if (process.env.MPGD_NATIVE_PACKAGED_BUILD === "1") {
@@ -14739,6 +14887,7 @@ var notificationsCapacitorGateways = Object.freeze({
14739
14887
  var notificationsCapacitorGatewayModules = Object.freeze([
14740
14888
  "src/platform/capacitorNotifications.ts",
14741
14889
  "src/platform/notifications.ts",
14890
+ "src/platform/notificationBackend.ts",
14742
14891
  "src/platform/notificationSettings.ts",
14743
14892
  "src/platform/notificationOpens.ts"
14744
14893
  ]);
@@ -14749,6 +14898,9 @@ var notificationsCapacitorSupportFiles = Object.freeze([
14749
14898
  "tools/validate-notifications.ts",
14750
14899
  "docs/NOTIFICATIONS.md"
14751
14900
  ]);
14901
+ var notificationsCapacitorTemplateSupportFiles = Object.freeze([
14902
+ "src/platform/serverSession.ts"
14903
+ ]);
14752
14904
 
14753
14905
  // node_modules/.cache/mpgd-native-build/packages/cli/src/native-shell-identity.js
14754
14906
  function assertAndroidSettingsAppProject(source) {
@@ -58,6 +58,12 @@ export declare const notificationsCapacitorGatewayModules: readonly string[];
58
58
  * catalog files they read, the validator, and the game's notifications doc.
59
59
  */
60
60
  export declare const notificationsCapacitorSupportFiles: readonly string[];
61
+ /**
62
+ * Files of the base game template the notifications modules import, which a
63
+ * game created by an earlier CLI release may not have: written from the
64
+ * template when missing, kept when the game has one.
65
+ */
66
+ export declare const notificationsCapacitorTemplateSupportFiles: readonly string[];
61
67
  /** Whether a Capacitor gateway already registers a notifications provider. */
62
68
  export declare function capacitorGatewayRegistersNotifications(source: string): boolean;
63
69
  /**
@@ -460,6 +460,7 @@ export const notificationsCapacitorGateways = Object.freeze({
460
460
  export const notificationsCapacitorGatewayModules = Object.freeze([
461
461
  'src/platform/capacitorNotifications.ts',
462
462
  'src/platform/notifications.ts',
463
+ 'src/platform/notificationBackend.ts',
463
464
  'src/platform/notificationSettings.ts',
464
465
  'src/platform/notificationOpens.ts',
465
466
  ]);
@@ -475,6 +476,14 @@ export const notificationsCapacitorSupportFiles = Object.freeze([
475
476
  'tools/validate-notifications.ts',
476
477
  'docs/NOTIFICATIONS.md',
477
478
  ]);
479
+ /**
480
+ * Files of the base game template the notifications modules import, which a
481
+ * game created by an earlier CLI release may not have: written from the
482
+ * template when missing, kept when the game has one.
483
+ */
484
+ export const notificationsCapacitorTemplateSupportFiles = Object.freeze([
485
+ 'src/platform/serverSession.ts',
486
+ ]);
478
487
  /** Whether a Capacitor gateway already registers a notifications provider. */
479
488
  export function capacitorGatewayRegistersNotifications(source) {
480
489
  // One pass over strings and comments: neither registers anything, and a
@@ -3125,7 +3125,7 @@ function assembleOfflineHtml(html, bundledEntry, entryAttributes) {
3125
3125
  || output.indexOf(offlineEntryPlaceholder) !== output.lastIndexOf(offlineEntryPlaceholder)) {
3126
3126
  throw new Error('Offline playtest could not preserve the module entry position.');
3127
3127
  }
3128
- output = output.replace(offlineEntryPlaceholder, inlineScript);
3128
+ output = output.replace(offlineEntryPlaceholder, () => inlineScript);
3129
3129
  if (findActiveHtmlStartTag(output, 'body') !== undefined) {
3130
3130
  return `${output.trim()}\n`;
3131
3131
  }
@@ -399,7 +399,7 @@ function migrateKnownRunner(source, template) {
399
399
  if (defaults.length !== 1) {
400
400
  return undefined;
401
401
  }
402
- const rendered = template.replace("'__DEFAULT_KIT_PATH__'", JSON.stringify(defaults[0]));
402
+ const rendered = template.replace("'__DEFAULT_KIT_PATH__'", () => JSON.stringify(defaults[0]));
403
403
  const canonical = (value) => JSON.stringify(value, (key, item) => ['start', 'end', 'raw'].includes(key) ? undefined : item);
404
404
  return canonical(ast) === canonical(parse(rendered, { ecmaVersion: 'latest', sourceType: 'module' }))
405
405
  ? rendered
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpgd/cli",
3
- "version": "0.53.0",
3
+ "version": "0.54.1",
4
4
  "description": "Gunshi CLI for mpgd-kit starter generation and target workflow orchestration.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -70,10 +70,10 @@
70
70
  "parse5": "7.3.0",
71
71
  "postcss": "8.5.28",
72
72
  "postcss-value-parser": "4.2.0",
73
- "@mpgd/catalog": "0.10.4",
74
- "@mpgd/platform": "0.26.0",
75
- "@mpgd/target-config": "0.23.4",
76
- "@mpgd/phaser-assets": "0.9.1"
73
+ "@mpgd/catalog": "0.10.6",
74
+ "@mpgd/platform": "0.27.1",
75
+ "@mpgd/phaser-assets": "0.9.1",
76
+ "@mpgd/target-config": "0.23.6"
77
77
  },
78
78
  "devDependencies": {
79
79
  "@types/node": "^24.0.0",
@@ -81,18 +81,18 @@
81
81
  "@types/yauzl": "3.4.0",
82
82
  "ttsc": "0.32.1",
83
83
  "typescript": "7.0.2",
84
- "@mpgd/adapter-ait": "0.20.0",
85
- "@mpgd/adapter-browser": "0.11.4",
86
- "@mpgd/adapter-capacitor": "0.12.0",
87
- "@mpgd/adapter-devvit": "0.13.2",
88
- "@mpgd/adapter-verse8": "0.7.4",
89
- "@mpgd/analytics": "0.4.13",
90
- "@mpgd/bridge": "0.14.1",
91
- "@mpgd/capacitor-storekit": "0.3.4",
92
- "@mpgd/game-services": "0.26.0",
93
- "@mpgd/capacitor-play-billing": "0.3.4",
94
- "@mpgd/i18n": "0.6.18",
95
- "@mpgd/game-runtime": "0.7.4",
84
+ "@mpgd/adapter-browser": "0.11.6",
85
+ "@mpgd/adapter-capacitor": "0.12.2",
86
+ "@mpgd/adapter-ait": "0.20.2",
87
+ "@mpgd/adapter-verse8": "0.7.6",
88
+ "@mpgd/bridge": "0.14.3",
89
+ "@mpgd/analytics": "0.4.15",
90
+ "@mpgd/adapter-devvit": "0.13.4",
91
+ "@mpgd/capacitor-play-billing": "0.3.6",
92
+ "@mpgd/capacitor-storekit": "0.3.6",
93
+ "@mpgd/game-runtime": "0.7.6",
94
+ "@mpgd/game-services": "0.27.1",
95
+ "@mpgd/i18n": "0.6.20",
96
96
  "@mpgd/lite-shell": "0.1.0"
97
97
  },
98
98
  "main": "./dist/index.js",
@@ -57,13 +57,42 @@ left for you.
57
57
 
58
58
  Consent records (reminders, 광고성 정보 수신동의, night consent), endpoint
59
59
  registration, server reminders, and consent notices go through the game
60
- backend. The kit does not ship that runtime's backend client yet, so
61
- `createStarterNotificationBackend` in `src/platform/notifications.ts` returns
62
- `undefined` and the settings screen asks only for the OS or host permission
63
- (and withdraws a Devvit opt-in the game rendered). Once your backend serves
64
- the notification routes, return its `NotificationRuntime` there with
65
- `listConsent`, which reads the signed-in player's consent records (the
66
- game-services Worker's `GET /consent`). The settings screen shows each
60
+ backend. `createStarterNotificationBackend` in
61
+ `src/platform/notifications.ts` builds the notifications runtime
62
+ (`@mpgd/platform/notifications-runtime`) over the game-services Worker's
63
+ notification routes (`@mpgd/game-services/notifications/client`) when the
64
+ game has both:
65
+
66
+ - a game-services URL, `VITE_MPGD_GAME_SERVICES_URL`, the one the commerce
67
+ client uses (a public https URL in production); and
68
+ - a server-backed session, which the game sets with
69
+ `setStarterServerSession` in `src/platform/serverSession.ts` at the top of
70
+ `src/main.ts`, before `bootstrap()`: the signed-in `{ playerId, sessionId }`
71
+ (`undefined` until the player signs in), a sign-in subscription, and the
72
+ session's `Authorization` header. The commerce client then sends the same
73
+ header. `sessionId` must be the stable session-family id: the `sessionId` your
74
+ identity service answers to the Worker's `authenticateNotificationSession`
75
+ for this session's header, the same across token refreshes (see the kit's
76
+ `docs/CLOUDFLARE_WORKER_DEPLOY.md`, "Sessions"). Do not pass a per-token
77
+ or rotating id: a `GuestSessionCoordinator` view's `sessionId` may rotate
78
+ on refresh, so it is not that id.
79
+
80
+ Without either, it returns `undefined` and the settings screen asks only
81
+ for the OS or host permission (and withdraws a Devvit opt-in the game
82
+ rendered). With them, the runtime registers the device for the signed-in
83
+ player, keeps its registration state in the platform storage, and
84
+ `listConsent` reads the player's consent records (the Worker's
85
+ `GET /consent`). The runtime also loads the Worker's registry entry for
86
+ its target (`GET /providers`) and turns notifications into a configuration
87
+ error when the provider disagrees with it; a Worker it cannot reach is
88
+ logged and the runtime runs without that check.
89
+
90
+ Reddit never gets this backend, even with a URL and a session: a Reddit
91
+ game's notifications backend is its Devvit server, which keeps the opt-in,
92
+ the records, and the sends, and the Worker serves no `reddit` target. The
93
+ Worker serves the Android, iOS, and Apps in Toss providers.
94
+
95
+ The settings screen shows each
67
96
  switch from the latest record, read every time it opens and after every
68
97
  change, so a choice made on another device shows. A tap reads the records
69
98
  again and writes only what changes. It keeps nothing on the device, and