@mpgd/cli 0.30.1 → 0.32.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.
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Deterministic ZIP writer for pack delivery archives. The builder targets
3
+ * this limited format only: STORE and DEFLATE entries, no ZIP64, no split
4
+ * archives, no encryption, no directory or symlink entries, no comments.
5
+ * Fixed metadata: DOS timestamp 1980-01-01 00:00:00, UNIX permissions 0644,
6
+ * UTF-8 name flag, no extra fields. Given the same entries in the same order
7
+ * and the same Node/zlib build, the produced bytes are identical.
8
+ */
9
+ export interface DeterministicZipEntry {
10
+ /** Normalized relative entry path; validated by the pack format module. */
11
+ readonly path: string;
12
+ /** Uncompressed entry bytes. */
13
+ readonly data: Buffer;
14
+ readonly method: 'store' | 'deflate';
15
+ /** Precomputed stored bytes for the entry method, when the builder already
16
+ * produced them (for example while choosing the method). */
17
+ readonly stored?: Buffer | undefined;
18
+ }
19
+ export declare function createDeterministicZip(entries: readonly DeterministicZipEntry[]): Buffer;
@@ -0,0 +1,81 @@
1
+ import { crc32, deflateRawSync } from 'node:zlib';
2
+ const DOS_TIME = 0;
3
+ const DOS_DATE = 0x0021;
4
+ const VERSION_NEEDED = 20;
5
+ const VERSION_MADE_BY = 0x0300;
6
+ const UTF8_FLAG = 0x0800;
7
+ const EXTERNAL_ATTRIBUTES = (0o100644 << 16) >>> 0;
8
+ const MAX_ENTRIES = 0xffff;
9
+ const MAX_ENTRY_BYTES = 0xffffffff;
10
+ export function createDeterministicZip(entries) {
11
+ if (entries.length === 0 || entries.length > MAX_ENTRIES) {
12
+ throw new Error(`Deterministic ZIP requires 1..${MAX_ENTRIES} entries`);
13
+ }
14
+ const localChunks = [];
15
+ const centralChunks = [];
16
+ let offset = 0;
17
+ for (const entry of entries) {
18
+ const name = Buffer.from(entry.path, 'utf8');
19
+ if (name.length > MAX_ENTRIES) {
20
+ throw new Error(`Deterministic ZIP entry name exceeds 65535 bytes: ${entry.path}`);
21
+ }
22
+ if (entry.data.length > MAX_ENTRY_BYTES) {
23
+ throw new Error(`Deterministic ZIP entry exceeds 4 GiB: ${entry.path}`);
24
+ }
25
+ const compressed = entry.method === 'deflate'
26
+ ? (entry.stored ?? deflateRawSync(entry.data, { level: 9 }))
27
+ : entry.data;
28
+ if (compressed.length > MAX_ENTRY_BYTES) {
29
+ throw new Error(`Deterministic ZIP entry exceeds 4 GiB: ${entry.path}`);
30
+ }
31
+ const methodCode = entry.method === 'deflate' ? 8 : 0;
32
+ const digest = crc32(entry.data) >>> 0;
33
+ const local = Buffer.alloc(30);
34
+ local.writeUInt32LE(0x04034b50, 0);
35
+ local.writeUInt16LE(VERSION_NEEDED, 4);
36
+ local.writeUInt16LE(UTF8_FLAG, 6);
37
+ local.writeUInt16LE(methodCode, 8);
38
+ local.writeUInt16LE(DOS_TIME, 10);
39
+ local.writeUInt16LE(DOS_DATE, 12);
40
+ local.writeUInt32LE(digest, 14);
41
+ local.writeUInt32LE(compressed.length, 18);
42
+ local.writeUInt32LE(entry.data.length, 22);
43
+ local.writeUInt16LE(name.length, 26);
44
+ local.writeUInt16LE(0, 28);
45
+ localChunks.push(local, name, compressed);
46
+ const central = Buffer.alloc(46);
47
+ central.writeUInt32LE(0x02014b50, 0);
48
+ central.writeUInt16LE(VERSION_MADE_BY, 4);
49
+ central.writeUInt16LE(VERSION_NEEDED, 6);
50
+ central.writeUInt16LE(UTF8_FLAG, 8);
51
+ central.writeUInt16LE(methodCode, 10);
52
+ central.writeUInt16LE(DOS_TIME, 12);
53
+ central.writeUInt16LE(DOS_DATE, 14);
54
+ central.writeUInt32LE(digest, 16);
55
+ central.writeUInt32LE(compressed.length, 20);
56
+ central.writeUInt32LE(entry.data.length, 24);
57
+ central.writeUInt16LE(name.length, 28);
58
+ central.writeUInt16LE(0, 30);
59
+ central.writeUInt16LE(0, 32);
60
+ central.writeUInt16LE(0, 34);
61
+ central.writeUInt16LE(0, 36);
62
+ central.writeUInt32LE(EXTERNAL_ATTRIBUTES, 38);
63
+ central.writeUInt32LE(offset, 42);
64
+ centralChunks.push(central, name);
65
+ offset += local.length + name.length + compressed.length;
66
+ }
67
+ const centralDirectory = Buffer.concat(centralChunks);
68
+ if (offset > MAX_ENTRY_BYTES || centralDirectory.length > MAX_ENTRY_BYTES) {
69
+ throw new Error('Deterministic ZIP archive exceeds 4 GiB');
70
+ }
71
+ const end = Buffer.alloc(22);
72
+ end.writeUInt32LE(0x06054b50, 0);
73
+ end.writeUInt16LE(0, 4);
74
+ end.writeUInt16LE(0, 6);
75
+ end.writeUInt16LE(entries.length, 8);
76
+ end.writeUInt16LE(entries.length, 10);
77
+ end.writeUInt32LE(centralDirectory.length, 12);
78
+ end.writeUInt32LE(offset, 16);
79
+ end.writeUInt16LE(0, 20);
80
+ return Buffer.concat([...localChunks, centralDirectory, end]);
81
+ }
package/dist/bin.js CHANGED
@@ -1,3 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { readCliArgs, runMpgdCli } from './index.js';
2
+ import { mpgdCliNeedsForcedExit, readCliArgs, runMpgdCli } from './index.js';
3
3
  await runMpgdCli(readCliArgs());
4
+ if (mpgdCliNeedsForcedExit()) {
5
+ // Stalled threadpool filesystem work cannot be cancelled; only the
6
+ // standalone binary may terminate the process to outrun it.
7
+ process.exit(process.exitCode ?? 1);
8
+ }
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export { createMicrosoftStorePackageAcceptanceRuntime, microsoftStorePackageAcce
8
8
  export { createMicrosoftStorePackageGenerationRuntime, microsoftStorePackageGeneratorEndpoint, microsoftStorePackageGeneratorSourceRevision, microsoftStorePackageGenerationSchemaVersion, renderMicrosoftStorePackageGenerationMarkdown, runMicrosoftStorePackageGeneration, type CreateMicrosoftStorePackageGenerationRuntimeInput, type MicrosoftStoreAddressResolver, type MicrosoftStorePackageGenerationEvidence, type MicrosoftStorePackageGenerationRuntime, type MicrosoftStoreResolvedAddress, type RunMicrosoftStorePackageGenerationInput, } from './microsoft-store-package-generation.js';
9
9
  export { microsoftStoreSubmissionSchemaVersion, parseMicrosoftStoreSubmissionConfig, renderMicrosoftStoreSubmissionMarkdown, runMicrosoftStoreSubmissionPreflight, type MicrosoftStoreSubmissionConfig, type MicrosoftStoreSubmissionCommerce, type MicrosoftStoreSubmissionCommerceProduct, type MicrosoftStoreSubmissionEvidence, type RunMicrosoftStoreSubmissionPreflightInput, } from './microsoft-store-submission.js';
10
10
  export { assertProductionTargetReadiness, type ProductionTargetReadinessInput, } from './production-target-readiness.js';
11
+ export declare function mpgdCliNeedsForcedExit(): boolean;
11
12
  export declare function runMpgdCli(args: readonly string[]): Promise<void>;
12
13
  export declare function runCreateGameCli(args: readonly string[]): Promise<void>;
13
14
  export declare function readCliArgs(): readonly string[];
package/dist/index.js CHANGED
@@ -6,6 +6,8 @@ import completion from '@gunshi/plugin-completion';
6
6
  import i18n, { defineI18n } from '@gunshi/plugin-i18n';
7
7
  import resources from '@gunshi/resources';
8
8
  import { cli } from 'gunshi';
9
+ import { buildAssetPacks } from './asset-pack-build.js';
10
+ import { verifyAssetPackDelivery } from './asset-pack-verify.js';
9
11
  import { normalizeConfiguredBuildTargets, normalizeBuildTarget as normalizeConfiguredTargetName, supportedBuildTargets, } from './build-targets.js';
10
12
  import { escapeMarkdownInline as escapeEvidenceMarkdownInline, writeEvidenceReportFiles, } from './evidence-io.js';
11
13
  import { defaultGameAcceptanceCommandTimeoutMs, resolveGameAcceptanceReleaseManifestFile, runGameAcceptance, } from './game-acceptance.js';
@@ -85,11 +87,71 @@ const acceptanceStepIds = {
85
87
  targetSmoke: 'target-smoke',
86
88
  gameplayE2E: 'gameplay-e2e',
87
89
  };
90
+ /** Whether the last run reported a deadline failure that leaves
91
+ * uncancellable filesystem work pending: the standalone binary forces its
92
+ * exit (see bin.ts); in-process library callers handle it themselves. */
93
+ let forcedExitPending = false;
94
+ /** A blocked pipe must not prevent the standalone binary from reaching its
95
+ * forced-exit path after a deadline report. The callback may remain queued
96
+ * behind a stalled reader, so the drain is best effort and bounded. */
97
+ const CLI_OUTPUT_DRAIN_TIMEOUT_MS = 250;
98
+ const drainOutput = (stream) => new Promise((resolve) => {
99
+ let settled = false;
100
+ const finish = () => {
101
+ if (settled) {
102
+ return;
103
+ }
104
+ settled = true;
105
+ resolve();
106
+ };
107
+ const timer = setTimeout(finish, CLI_OUTPUT_DRAIN_TIMEOUT_MS);
108
+ try {
109
+ stream.write('', () => {
110
+ clearTimeout(timer);
111
+ finish();
112
+ });
113
+ }
114
+ catch {
115
+ clearTimeout(timer);
116
+ finish();
117
+ }
118
+ });
119
+ export function mpgdCliNeedsForcedExit() {
120
+ return forcedExitPending;
121
+ }
88
122
  export async function runMpgdCli(args) {
123
+ forcedExitPending = false;
89
124
  await cli([...args], entryCommand, {
90
125
  name: 'mpgd',
91
126
  version: cliVersion,
127
+ // Machine-readable mode contract: `assets verify-delivery --json`
128
+ // owns stdout entirely — exactly one JSON document, parseable with
129
+ // JSON.parse(stdout) and nothing else. The framework's banner is
130
+ // suppressed for that command, and framework-level argument errors
131
+ // (bad value types, missing required arguments — rejected before the
132
+ // command handler runs) move to stderr so stdout never carries a
133
+ // partial document. Every other command keeps the default rendering.
134
+ renderHeader: async (ctx) => {
135
+ const values = ctx.values;
136
+ if (ctx.name === 'verify-delivery' && values.json === true) {
137
+ return '';
138
+ }
139
+ const title = ctx.env.description || ctx.env.name || '';
140
+ return title
141
+ ? `${title} (${ctx.env.name || ''}${ctx.env.version ? ` v${ctx.env.version}` : ''})`
142
+ : title;
143
+ },
144
+ renderValidationErrors: async (ctx, error) => {
145
+ const messages = error.errors.map((entry) => String(entry.message)).join('\n');
146
+ const values = ctx.values;
147
+ if (ctx.name === 'verify-delivery' && values.json === true) {
148
+ process.stderr.write(`${messages}\n`);
149
+ return '';
150
+ }
151
+ return messages;
152
+ },
92
153
  subCommands: {
154
+ assets: assetsCommand,
93
155
  game: gameCommand,
94
156
  legal: legalCommand,
95
157
  target: targetCommand,
@@ -143,7 +205,7 @@ const entryCommand = defineI18n({
143
205
  ko: 'mpgd-kit 스타터와 타깃 워크플로우를 관리합니다.',
144
206
  }),
145
207
  run: () => {
146
- console.info('Use a sub-command: game, target, kit.');
208
+ console.info('Use a sub-command: assets, game, legal, target, kit.');
147
209
  console.info('Run "pnpm mpgd --help" for available commands.');
148
210
  },
149
211
  });
@@ -774,6 +836,226 @@ function readPositiveInteger(value, label) {
774
836
  }
775
837
  return parsed;
776
838
  }
839
+ function assetPackVerifyArgs() {
840
+ return {
841
+ manifest: {
842
+ type: 'string',
843
+ required: true,
844
+ description: 'Path to the delivery manifest JSON document.',
845
+ },
846
+ root: {
847
+ type: 'string',
848
+ required: true,
849
+ description: 'Deployment root the manifest artifact paths resolve against.',
850
+ },
851
+ json: {
852
+ type: 'boolean',
853
+ required: false,
854
+ description: 'Print a single machine-readable JSON report to stdout.',
855
+ },
856
+ 'max-object-bytes': {
857
+ type: 'number',
858
+ required: false,
859
+ description: 'Optional static-host limit: largest allowed deployed object in bytes.',
860
+ },
861
+ 'max-files': {
862
+ type: 'number',
863
+ required: false,
864
+ description: 'Optional static-host limit: total regular files allowed under the root.',
865
+ },
866
+ 'max-total-bytes': {
867
+ type: 'number',
868
+ required: false,
869
+ description: 'Optional static-host limit: total deployed bytes allowed under the root.',
870
+ },
871
+ 'max-archive-bytes': {
872
+ type: 'number',
873
+ required: false,
874
+ description: 'Optional cap: largest zip archive read for verification, in bytes.',
875
+ },
876
+ 'max-entry-bytes': {
877
+ type: 'number',
878
+ required: false,
879
+ description: 'Optional cap: largest expanded zip entry allowed, in bytes.',
880
+ },
881
+ 'max-expanded-bytes': {
882
+ type: 'number',
883
+ required: false,
884
+ description: 'Optional cap: total expanded bytes allowed per archive, in bytes.',
885
+ },
886
+ };
887
+ }
888
+ function assetPackBuildArgs() {
889
+ return {
890
+ config: {
891
+ type: 'string',
892
+ required: true,
893
+ description: 'Path to the asset pack build config JSON file.',
894
+ },
895
+ out: {
896
+ type: 'string',
897
+ required: true,
898
+ description: 'Output directory for delivery artifacts, outside the source root.',
899
+ },
900
+ };
901
+ }
902
+ const assetsCommand = defineI18n({
903
+ name: 'assets',
904
+ description: 'Build deterministic asset pack delivery artifacts.',
905
+ resource: commandResource({
906
+ en: 'Build deterministic asset pack delivery artifacts.',
907
+ ko: '결정적인 에셋 팩 전달 산출물을 빌드합니다.',
908
+ }),
909
+ subCommands: {
910
+ 'build-packs': defineI18n({
911
+ name: 'build-packs',
912
+ description: 'Build files or ZIP delivery artifacts for configured packs.',
913
+ resource: commandResource({
914
+ en: 'Build files or ZIP delivery artifacts for configured packs.',
915
+ ko: '설정된 팩의 files 또는 ZIP 전달 산출물을 빌드합니다.',
916
+ }, {
917
+ config: {
918
+ en: 'Path to the asset pack build config JSON file.',
919
+ ko: '에셋 팩 빌드 설정 JSON 파일 경로.',
920
+ },
921
+ out: {
922
+ en: 'Output directory for delivery artifacts, outside the source root.',
923
+ ko: '전달 산출물 출력 디렉터리. 소스 루트 밖이어야 합니다.',
924
+ },
925
+ }),
926
+ args: assetPackBuildArgs(),
927
+ run: (ctx) => {
928
+ const report = buildAssetPacks({
929
+ configPath: ctx.values.config,
930
+ outDir: ctx.values.out,
931
+ });
932
+ for (const output of report.outputs) {
933
+ console.info(`${output.action === 'written' ? 'wrote' : 'unchanged'} ${output.path} `
934
+ + `(${output.bytes} bytes, sha256:${output.sha256})`);
935
+ }
936
+ for (const archive of report.archives) {
937
+ console.info(`archive ${archive.path}: ${archive.entryCount} entries `
938
+ + `(${archive.storeEntries} store, ${archive.deflateEntries} deflate), `
939
+ + `sources ${archive.sourceBytes} bytes, archive ${archive.archiveBytes} bytes, `
940
+ + `sha256:${archive.sha256}`);
941
+ }
942
+ console.info(`Asset pack delivery manifest: ${report.manifestPath}`);
943
+ },
944
+ }),
945
+ 'verify-delivery': defineI18n({
946
+ name: 'verify-delivery',
947
+ description: 'Verify delivery artifacts read-only against the manifest.',
948
+ resource: commandResource({
949
+ en: 'Verify delivery artifacts read-only against the manifest.',
950
+ ko: '전달 산출물을 manifest 기준으로 읽기 전용으로 검증합니다.',
951
+ }, {
952
+ manifest: {
953
+ en: 'Path to the delivery manifest JSON document.',
954
+ ko: '전달 manifest JSON 문서 경로.',
955
+ },
956
+ root: {
957
+ en: 'Deployment root the manifest artifact paths resolve against.',
958
+ ko: 'manifest 산출물 경로의 기준이 되는 배포 루트 디렉터리.',
959
+ },
960
+ json: {
961
+ en: 'Print a single machine-readable JSON report to stdout.',
962
+ ko: '기계 판독 가능한 JSON 보고서 하나를 stdout에 출력합니다.',
963
+ },
964
+ 'max-object-bytes': {
965
+ en: 'Optional static-host limit: largest allowed deployed object in bytes.',
966
+ ko: '선택적 정적 호스트 한도: 허용되는 최대 배포 object 바이트 수.',
967
+ },
968
+ 'max-files': {
969
+ en: 'Optional static-host limit: total regular files allowed under the root.',
970
+ ko: '선택적 정적 호스트 한도: 루트 아래 허용되는 일반 파일 총 개수.',
971
+ },
972
+ 'max-total-bytes': {
973
+ en: 'Optional static-host limit: total deployed bytes allowed under the root.',
974
+ ko: '선택적 정적 호스트 한도: 루트 아래 허용되는 총 배포 바이트 수.',
975
+ },
976
+ 'max-archive-bytes': {
977
+ en: 'Optional cap: largest zip archive read for verification, in bytes.',
978
+ ko: '선택적 상한: 검증 시 읽는 zip 아카이브의 최대 바이트 수.',
979
+ },
980
+ 'max-entry-bytes': {
981
+ en: 'Optional cap: largest expanded zip entry allowed, in bytes.',
982
+ ko: '선택적 상한: 허용되는 확장(zip 해제) 엔트리 최대 바이트 수.',
983
+ },
984
+ 'max-expanded-bytes': {
985
+ en: 'Optional cap: total expanded bytes allowed per archive, in bytes.',
986
+ ko: '선택적 상한: 아카이브별 허용되는 총 확장 바이트 수.',
987
+ },
988
+ }),
989
+ args: assetPackVerifyArgs(),
990
+ run: async (ctx) => {
991
+ const hostLimits = {
992
+ ...(ctx.values['max-object-bytes'] === undefined
993
+ ? {}
994
+ : { maxObjectBytes: ctx.values['max-object-bytes'] }),
995
+ ...(ctx.values['max-files'] === undefined
996
+ ? {}
997
+ : { maxFiles: ctx.values['max-files'] }),
998
+ ...(ctx.values['max-total-bytes'] === undefined
999
+ ? {}
1000
+ : { maxTotalBytes: ctx.values['max-total-bytes'] }),
1001
+ };
1002
+ const report = await verifyAssetPackDelivery({
1003
+ manifestPath: ctx.values.manifest,
1004
+ root: ctx.values.root,
1005
+ ...(Object.keys(hostLimits).length > 0 ? { hostLimits } : {}),
1006
+ ...(ctx.values['max-archive-bytes'] === undefined
1007
+ ? {}
1008
+ : { maxArchiveBytes: ctx.values['max-archive-bytes'] }),
1009
+ ...(ctx.values['max-entry-bytes'] === undefined
1010
+ ? {}
1011
+ : { maxEntryBytes: ctx.values['max-entry-bytes'] }),
1012
+ ...(ctx.values['max-expanded-bytes'] === undefined
1013
+ ? {}
1014
+ : { maxExpandedBytes: ctx.values['max-expanded-bytes'] }),
1015
+ });
1016
+ if (ctx.values.json === true) {
1017
+ console.info(JSON.stringify(report, null, 2));
1018
+ }
1019
+ else {
1020
+ for (const archive of report.archives) {
1021
+ console.info(`verified archive ${archive.packId}: ${archive.entries} entries, ${archive.expandedBytes} expanded bytes`);
1022
+ }
1023
+ console.info(`manifest ${report.manifest.sha256.slice(0, 12)}… (${report.manifest.bytes} bytes, ${report.manifest.packs} packs)`);
1024
+ console.info(`referenced objects: ${report.referenced.files} files, ${report.referenced.bytes} bytes`);
1025
+ if (report.inventory !== undefined) {
1026
+ console.info(`root inventory: ${report.inventory.files} files, ${report.inventory.bytes} bytes${report.inventory.truncated ? ' (truncated)' : ''}`);
1027
+ }
1028
+ for (const failure of report.failures) {
1029
+ console.error(`[${failure.stage}/${failure.code}] ${failure.message}`);
1030
+ }
1031
+ if (report.ok) {
1032
+ console.info('Delivery verification passed.');
1033
+ }
1034
+ else {
1035
+ console.error('Delivery verification failed; see the failures above.');
1036
+ }
1037
+ }
1038
+ if (report.failures.some((failure) => failure.code === 'deadline')) {
1039
+ // Piped console output is asynchronous; drain both streams so a
1040
+ // large report is not truncated by the binary's forced exit.
1041
+ await Promise.all([drainOutput(process.stdout), drainOutput(process.stderr)]);
1042
+ // Stalled filesystem requests cannot be cancelled once the
1043
+ // deadline fires; the standalone binary forces its exit so a
1044
+ // pending threadpool call cannot keep the process (and its
1045
+ // leaked handle) alive past the reported budget. In-process
1046
+ // callers of runMpgdCli observe mpgdCliNeedsForcedExit instead.
1047
+ forcedExitPending = true;
1048
+ }
1049
+ if (!report.ok) {
1050
+ process.exitCode = 1;
1051
+ }
1052
+ },
1053
+ }),
1054
+ },
1055
+ run: () => {
1056
+ console.info('Use "mpgd assets build-packs" or "mpgd assets verify-delivery".');
1057
+ },
1058
+ });
777
1059
  const legalCommand = defineI18n({
778
1060
  name: 'legal',
779
1061
  description: 'Build static legal/support pages for store and release evidence.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpgd/cli",
3
- "version": "0.30.1",
3
+ "version": "0.32.0",
4
4
  "description": "Gunshi CLI for mpgd-kit starter generation and target workflow orchestration.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -46,25 +46,25 @@
46
46
  "entities": "^8.0.0",
47
47
  "esbuild": "^0.28.1",
48
48
  "gunshi": "^0.35.1",
49
- "undici": "^7.28.0"
49
+ "undici": "^7.28.0",
50
+ "@mpgd/phaser-assets": "0.6.0"
50
51
  },
51
52
  "devDependencies": {
52
53
  "@types/node": "^24.0.0",
53
54
  "ttsc": "0.18.4",
54
55
  "typescript": "7.0.2",
55
- "@mpgd/adapter-capacitor": "0.4.10",
56
- "@mpgd/adapter-ait": "0.12.1",
57
56
  "@mpgd/adapter-browser": "0.7.3",
57
+ "@mpgd/adapter-capacitor": "0.4.10",
58
58
  "@mpgd/adapter-devvit": "0.9.8",
59
+ "@mpgd/adapter-ait": "0.12.1",
59
60
  "@mpgd/adapter-verse8": "0.3.6",
60
- "@mpgd/analytics": "0.3.12",
61
- "@mpgd/catalog": "0.7.2",
62
- "@mpgd/game-services": "0.15.1",
63
61
  "@mpgd/bridge": "0.9.0",
62
+ "@mpgd/catalog": "0.7.2",
63
+ "@mpgd/i18n": "0.6.1",
64
64
  "@mpgd/platform": "0.12.0",
65
+ "@mpgd/game-services": "0.15.1",
65
66
  "@mpgd/target-config": "0.15.0",
66
- "@mpgd/phaser-assets": "0.4.1",
67
- "@mpgd/i18n": "0.6.1"
67
+ "@mpgd/analytics": "0.3.12"
68
68
  },
69
69
  "main": "./dist/index.js",
70
70
  "types": "./dist/index.d.ts",