@mpgd/cli 0.54.0 → 0.55.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/dist/devvit-sdk-check.d.ts +14 -0
- package/dist/devvit-sdk-check.js +112 -0
- package/dist/index.js +74 -22
- package/dist/kit-upgrade.js +103 -4
- package/dist/legal-site-output.d.ts +12 -0
- package/dist/legal-site-output.js +226 -0
- package/dist/native-build-info.json +2 -2
- package/dist/native-build-target.js +101 -18
- package/package.json +17 -17
- package/templates/phaser-game/apps/target-devvit/package.json +10 -9
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface DevvitSdkVersion {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
readonly declared: string;
|
|
4
|
+
readonly installed: string;
|
|
5
|
+
readonly latest: string;
|
|
6
|
+
readonly source?: 'adapter';
|
|
7
|
+
}
|
|
8
|
+
export interface CheckDevvitSdkOptions {
|
|
9
|
+
/** Test seam. Production looks up the npm `latest` dist-tag. */
|
|
10
|
+
readonly lookupLatest?: (name: string) => Promise<string>;
|
|
11
|
+
}
|
|
12
|
+
export declare function assertLatestDevvitSdk(entries: readonly DevvitSdkVersion[]): void;
|
|
13
|
+
/** Read-only pre-upload gate. Never install a new SDK between verified build and upload. */
|
|
14
|
+
export declare function checkDevvitSdk(project: string, options?: CheckDevvitSdkOptions): Promise<readonly DevvitSdkVersion[]>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { execFile as execFileCallback } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { readFile, realpath } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
const execFile = promisify(execFileCallback);
|
|
6
|
+
const stable = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/u;
|
|
7
|
+
function isFirstPartyDevvitPackage(name) {
|
|
8
|
+
return name === 'devvit' || name.startsWith('@devvit/');
|
|
9
|
+
}
|
|
10
|
+
function devvitPackageNames(dependencies) {
|
|
11
|
+
return Object.keys(dependencies).filter(isFirstPartyDevvitPackage);
|
|
12
|
+
}
|
|
13
|
+
function record(value) {
|
|
14
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
15
|
+
throw new Error('Invalid package metadata.');
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
async function npmLatestStable(name, cwd) {
|
|
20
|
+
const { stdout } = await execFile(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['view', `${name}@latest`, 'version', '--json', '--registry=https://registry.npmjs.org'], {
|
|
21
|
+
cwd,
|
|
22
|
+
encoding: 'utf8',
|
|
23
|
+
timeout: 20_000,
|
|
24
|
+
maxBuffer: 16_384,
|
|
25
|
+
shell: process.platform === 'win32',
|
|
26
|
+
});
|
|
27
|
+
return requireStableVersion(name, JSON.parse(stdout));
|
|
28
|
+
}
|
|
29
|
+
function requireStableVersion(name, latest) {
|
|
30
|
+
if (typeof latest !== 'string' || !stable.test(latest)) {
|
|
31
|
+
throw new Error('Registry did not return a stable Devvit version: ' + name);
|
|
32
|
+
}
|
|
33
|
+
return latest;
|
|
34
|
+
}
|
|
35
|
+
export function assertLatestDevvitSdk(entries) {
|
|
36
|
+
if (!entries.some((e) => e.name === 'devvit' || e.name === '@devvit/cli') || !entries.some((e) => e.name === '@devvit/web' || e.name === '@devvit/public-api')) {
|
|
37
|
+
throw new Error('Devvit upload needs both an installed CLI and SDK in this project.');
|
|
38
|
+
}
|
|
39
|
+
for (const entry of entries) {
|
|
40
|
+
if (!stable.test(entry.latest) || (entry.source !== 'adapter' && entry.declared !== entry.latest) || entry.installed !== entry.latest) {
|
|
41
|
+
throw new Error(`${entry.name}: declared=${entry.declared}, installed=${entry.installed}, latest=${entry.latest}. Update exact SDK/CLI pins and lockfile, rebuild and test, then retry upload.`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Read-only pre-upload gate. Never install a new SDK between verified build and upload. */
|
|
46
|
+
export async function checkDevvitSdk(project, options = {}) {
|
|
47
|
+
const manifestPath = path.resolve(project, 'package.json');
|
|
48
|
+
const manifest = record(JSON.parse(await readFile(manifestPath, 'utf8')));
|
|
49
|
+
const dependencies = {
|
|
50
|
+
...(manifest.dependencies === undefined ? {} : record(manifest.dependencies)),
|
|
51
|
+
...(manifest.devDependencies === undefined ? {} : record(manifest.devDependencies)),
|
|
52
|
+
};
|
|
53
|
+
async function installedPackage(from, name) {
|
|
54
|
+
let directory = from;
|
|
55
|
+
for (;;) {
|
|
56
|
+
const file = path.join(directory, 'node_modules', name, 'package.json');
|
|
57
|
+
try {
|
|
58
|
+
const metadata = record(JSON.parse(await readFile(file, 'utf8')));
|
|
59
|
+
if (metadata.name !== name || typeof metadata.version !== 'string') {
|
|
60
|
+
throw new Error('Invalid installed package: ' + name);
|
|
61
|
+
}
|
|
62
|
+
return { metadata, directory: path.dirname(await realpath(file)) };
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (!(error instanceof Error) || !('code' in error) || error.code !== 'ENOENT') {
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const parent = path.dirname(directory);
|
|
70
|
+
if (parent === directory) {
|
|
71
|
+
throw new Error('Cannot resolve installed Devvit package: ' + name);
|
|
72
|
+
}
|
|
73
|
+
directory = parent;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const targetDirectory = path.dirname(manifestPath);
|
|
77
|
+
const subjects = devvitPackageNames(dependencies).map((name) => ({
|
|
78
|
+
name,
|
|
79
|
+
declared: dependencies[name],
|
|
80
|
+
directory: targetDirectory,
|
|
81
|
+
transitive: false,
|
|
82
|
+
}));
|
|
83
|
+
if (Object.hasOwn(dependencies, '@mpgd/adapter-devvit')) {
|
|
84
|
+
const adapter = await installedPackage(targetDirectory, '@mpgd/adapter-devvit');
|
|
85
|
+
const adapterDependencies = record(adapter.metadata.dependencies);
|
|
86
|
+
for (const name of devvitPackageNames(adapterDependencies)) {
|
|
87
|
+
subjects.push({
|
|
88
|
+
name,
|
|
89
|
+
declared: adapterDependencies[name],
|
|
90
|
+
directory: adapter.directory,
|
|
91
|
+
transitive: true,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const entries = await Promise.all(subjects.map(async ({ name, declared, directory, transitive }) => {
|
|
96
|
+
if (typeof declared !== 'string') {
|
|
97
|
+
throw new Error('Invalid Devvit dependency: ' + name);
|
|
98
|
+
}
|
|
99
|
+
const resolved = await installedPackage(directory, name);
|
|
100
|
+
const installed = resolved.metadata.version;
|
|
101
|
+
if (typeof installed !== 'string') {
|
|
102
|
+
throw new Error('Missing installed Devvit version: ' + name);
|
|
103
|
+
}
|
|
104
|
+
const latest = options.lookupLatest
|
|
105
|
+
? requireStableVersion(name, await options.lookupLatest(name))
|
|
106
|
+
: await npmLatestStable(name, path.dirname(manifestPath));
|
|
107
|
+
// A consumer may use a reviewed exact override until the adapter release catches up.
|
|
108
|
+
return { name, declared, installed, latest, ...(transitive ? { source: 'adapter' } : {}) };
|
|
109
|
+
}));
|
|
110
|
+
assertLatestDevvitSdk(entries);
|
|
111
|
+
return entries;
|
|
112
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { buildAssetPacks } from './asset-pack-build.js';
|
|
|
12
12
|
import { verifyAssetPackDelivery } from './asset-pack-verify.js';
|
|
13
13
|
import { buildAssetPackTarget } from './asset-pack-target.js';
|
|
14
14
|
import { publishAssetPacks } from './asset-pack-publish.js';
|
|
15
|
+
import { assertLegalProjectPath, publishLegalSiteFiles } from './legal-site-output.js';
|
|
15
16
|
import { assertAssetPackTargetPolicy } from '@mpgd/target-config/asset-packs';
|
|
16
17
|
import { materializeCapacitorShellStarter, planCapacitorShellStarter, } from './capacitor-shell-starter.js';
|
|
17
18
|
import { doctorNativeDeployment, initializeDeployConfig, parseDeployTargets, planNativeDeployment, readNativeDeploymentPlan, writeNativeDeploymentPlan, } from './deploy-planning.js';
|
|
@@ -26,6 +27,7 @@ import { runMicrosoftStorePackageGeneration } from './microsoft-store-package-ge
|
|
|
26
27
|
import { defaultMpgdKitPath, initializeMicrosoftStoreStarter, listMicrosoftStoreInitializerTemplateFiles, prepareBaseGameTemplateFile, resolveMicrosoftStoreAdapterDependencyVersion, } from './microsoft-store-starter.js';
|
|
27
28
|
import { applyNotificationsStarter, notificationsProviderId, readGameCreateProviders, } from './notifications-starter.js';
|
|
28
29
|
import { applyKitUpgrade, planKitUpgrade } from './kit-upgrade.js';
|
|
30
|
+
import { checkDevvitSdk } from './devvit-sdk-check.js';
|
|
29
31
|
import { applyToolingMigration, planToolingMigration } from './tooling-migration.js';
|
|
30
32
|
import { runMicrosoftStoreSubmissionPreflight } from './microsoft-store-submission.js';
|
|
31
33
|
import { defaultOfflinePlaytestArtifactDir, defaultOfflinePlaytestMaximumBytes, defaultOfflinePlaytestOutputDir, runOfflinePlaytestPackaging, } from './offline-playtest.js';
|
|
@@ -1818,7 +1820,10 @@ const targetCommand = defineI18n({
|
|
|
1818
1820
|
action: 'build',
|
|
1819
1821
|
target,
|
|
1820
1822
|
profile,
|
|
1821
|
-
env: createTargetCommandEnv(ctx.values, {
|
|
1823
|
+
env: createTargetCommandEnv(ctx.values, {
|
|
1824
|
+
nativeBuildTarget: target,
|
|
1825
|
+
requireSourceRevision: true,
|
|
1826
|
+
}),
|
|
1822
1827
|
...(variant === undefined ? {} : { variant }),
|
|
1823
1828
|
});
|
|
1824
1829
|
},
|
|
@@ -2448,7 +2453,7 @@ const targetCommand = defineI18n({
|
|
|
2448
2453
|
run: (ctx) => {
|
|
2449
2454
|
const aitVariant = readOptionalString(ctx.values['ait-variant']);
|
|
2450
2455
|
const iosVariant = readOptionalString(ctx.values['ios-variant']);
|
|
2451
|
-
const env = createTargetCommandEnv(ctx.values);
|
|
2456
|
+
const env = createTargetCommandEnv(ctx.values, { requireSourceRevision: true });
|
|
2452
2457
|
runTargetMatrix({
|
|
2453
2458
|
action: 'build',
|
|
2454
2459
|
targets: parseTargetList(readOptionalString(ctx.values.targets), env),
|
|
@@ -2521,6 +2526,18 @@ const kitCommand = defineI18n({
|
|
|
2521
2526
|
ko: '현재 mpgd-kit 체크아웃을 점검합니다.',
|
|
2522
2527
|
}),
|
|
2523
2528
|
subCommands: {
|
|
2529
|
+
'devvit-check': defineI18n({
|
|
2530
|
+
name: 'devvit-check',
|
|
2531
|
+
description: 'Require exact installed latest stable Devvit SDK and CLI before upload.',
|
|
2532
|
+
resource: commandResource({ en: 'Check latest stable Devvit SDK before upload.', ko: '업로드 전 Devvit 최신 안정 SDK를 확인합니다.' }),
|
|
2533
|
+
args: { project: { type: 'string', default: '.', description: 'Devvit target directory.' } },
|
|
2534
|
+
run: async (ctx) => {
|
|
2535
|
+
const entries = await checkDevvitSdk(readOptionalString(ctx.values.project) ?? '.');
|
|
2536
|
+
for (const entry of entries) {
|
|
2537
|
+
console.info(entry.name + '@' + entry.installed + ' (latest stable)');
|
|
2538
|
+
}
|
|
2539
|
+
},
|
|
2540
|
+
}),
|
|
2524
2541
|
upgrade: defineI18n({
|
|
2525
2542
|
name: 'upgrade',
|
|
2526
2543
|
description: 'Plan or apply the latest published Kit package versions for a game.',
|
|
@@ -2774,9 +2791,13 @@ function resolveLegalCommandOptions(values) {
|
|
|
2774
2791
|
function buildLegalSite(input) {
|
|
2775
2792
|
const legalDir = path.resolve(process.cwd(), input.legalDir);
|
|
2776
2793
|
const outDir = path.resolve(process.cwd(), input.outDir);
|
|
2794
|
+
assertLegalProjectPath('legal-dir', legalDir);
|
|
2795
|
+
assertLegalProjectPath('out-dir', outDir);
|
|
2777
2796
|
const outputs = createLegalSiteOutputs(legalDir, outDir);
|
|
2778
2797
|
if (input.check) {
|
|
2798
|
+
assertLegalProjectPath('out-dir', outDir);
|
|
2779
2799
|
const stale = outputs.filter((output) => {
|
|
2800
|
+
assertLegalProjectPath('out-dir', output.file);
|
|
2780
2801
|
if (!existsSync(output.file)) {
|
|
2781
2802
|
return true;
|
|
2782
2803
|
}
|
|
@@ -2792,29 +2813,24 @@ function buildLegalSite(input) {
|
|
|
2792
2813
|
outDir,
|
|
2793
2814
|
};
|
|
2794
2815
|
}
|
|
2795
|
-
|
|
2796
|
-
mkdirSync(path.dirname(output.file), { recursive: true });
|
|
2797
|
-
writeFileSync(output.file, output.content);
|
|
2798
|
-
}
|
|
2816
|
+
publishLegalSiteFiles(outDir, outputs);
|
|
2799
2817
|
return {
|
|
2800
2818
|
outDir,
|
|
2801
2819
|
};
|
|
2802
2820
|
}
|
|
2803
2821
|
function createLegalSiteOutputs(legalDir, outDir) {
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
throw new Error(`legal-dir must be inside the project root: ${legalDir}`);
|
|
2808
|
-
}
|
|
2809
|
-
if (relativeOutDir.startsWith('..') || path.isAbsolute(relativeOutDir)) {
|
|
2810
|
-
throw new Error(`out-dir must be inside the project root: ${outDir}`);
|
|
2811
|
-
}
|
|
2812
|
-
const sourceRoot = toTemplatePath(relativeLegalDir || '.');
|
|
2822
|
+
assertLegalProjectPath('legal-dir', legalDir);
|
|
2823
|
+
assertLegalProjectPath('out-dir', outDir);
|
|
2824
|
+
const sourceRoot = toTemplatePath(path.relative(process.cwd(), legalDir) || '.');
|
|
2813
2825
|
const outputs = legalPageSlugs.map((slug) => {
|
|
2814
2826
|
const sourceFile = path.join(legalDir, `${slug}.html`);
|
|
2827
|
+
// existsSync and readFileSync follow a page symlink. Check the file
|
|
2828
|
+
// itself, not only the legal directory, immediately before reading it.
|
|
2829
|
+
assertLegalProjectPath('legal-dir', sourceFile);
|
|
2815
2830
|
if (!existsSync(sourceFile)) {
|
|
2816
2831
|
throw new Error(`Missing legal page source: ${sourceFile}`);
|
|
2817
2832
|
}
|
|
2833
|
+
assertLegalProjectPath('legal-dir', sourceFile);
|
|
2818
2834
|
return {
|
|
2819
2835
|
file: path.join(outDir, slug, 'index.html'),
|
|
2820
2836
|
content: normalizeLegalHtml(readFileSync(sourceFile, 'utf8'), sourceFile),
|
|
@@ -3347,7 +3363,11 @@ function createTargetCommandEnv(values, options = {}) {
|
|
|
3347
3363
|
});
|
|
3348
3364
|
const env = {
|
|
3349
3365
|
...process.env,
|
|
3350
|
-
|
|
3366
|
+
// Release provenance is recorded only by build commands. Smoke, doctor,
|
|
3367
|
+
// icons, and submission preflight must still run on an exported game tree.
|
|
3368
|
+
...createGameOwnedReleaseEnv(gameRoot, process.env, {
|
|
3369
|
+
requireSourceRevision: options.requireSourceRevision === true,
|
|
3370
|
+
}),
|
|
3351
3371
|
MPGD_PLATFORM_TARGETS_FILE: preparedTargetsFile,
|
|
3352
3372
|
...(targetConfigExtensionsFile === undefined
|
|
3353
3373
|
? {}
|
|
@@ -3450,10 +3470,9 @@ function resolveTargetConfigExtensionsFile(gameRoot, configuredFile) {
|
|
|
3450
3470
|
const gameOwnedFile = path.join(gameRoot, 'mpgd.target-config.json');
|
|
3451
3471
|
return existsSync(gameOwnedFile) ? gameOwnedFile : undefined;
|
|
3452
3472
|
}
|
|
3453
|
-
function createGameOwnedReleaseEnv(gameRoot, baseEnv) {
|
|
3473
|
+
function createGameOwnedReleaseEnv(gameRoot, baseEnv, options) {
|
|
3454
3474
|
const monetizationFiles = resolveGameOwnedMonetizationFiles(gameRoot, baseEnv);
|
|
3455
|
-
const sourceGitSha =
|
|
3456
|
-
?? readSourceGitSha(gameRoot);
|
|
3475
|
+
const sourceGitSha = resolveCommandSourceGitSha(gameRoot, baseEnv, options.requireSourceRevision);
|
|
3457
3476
|
const appVersion = readOptionalString(baseEnv.APP_VERSION)
|
|
3458
3477
|
?? readGamePackageVersion(gameRoot);
|
|
3459
3478
|
const notificationTopicsFile = readConfiguredPath(baseEnv.MPGD_NOTIFICATION_TOPICS_FILE);
|
|
@@ -3464,7 +3483,7 @@ function createGameOwnedReleaseEnv(gameRoot, baseEnv) {
|
|
|
3464
3483
|
...(notificationTopicsFile === undefined
|
|
3465
3484
|
? {}
|
|
3466
3485
|
: { MPGD_NOTIFICATION_TOPICS_FILE: path.resolve(gameRoot, notificationTopicsFile) }),
|
|
3467
|
-
MPGD_SOURCE_GIT_SHA: sourceGitSha,
|
|
3486
|
+
...(sourceGitSha === undefined ? {} : { MPGD_SOURCE_GIT_SHA: sourceGitSha }),
|
|
3468
3487
|
...(appVersion === undefined ? {} : { APP_VERSION: appVersion }),
|
|
3469
3488
|
};
|
|
3470
3489
|
}
|
|
@@ -3499,15 +3518,48 @@ function readConfiguredPath(value) {
|
|
|
3499
3518
|
const normalized = value?.trim();
|
|
3500
3519
|
return normalized === undefined || normalized.length === 0 ? undefined : normalized;
|
|
3501
3520
|
}
|
|
3521
|
+
function resolveCommandSourceGitSha(gameRoot, baseEnv, requireSourceRevision) {
|
|
3522
|
+
const configuredSourceGitSha = readOptionalString(baseEnv.MPGD_SOURCE_GIT_SHA);
|
|
3523
|
+
if (requireSourceRevision) {
|
|
3524
|
+
return configuredSourceGitSha === undefined
|
|
3525
|
+
? readSourceGitSha(gameRoot)
|
|
3526
|
+
: requireSourceGitSha(configuredSourceGitSha);
|
|
3527
|
+
}
|
|
3528
|
+
if (configuredSourceGitSha !== undefined) {
|
|
3529
|
+
return isSourceGitSha(configuredSourceGitSha) ? configuredSourceGitSha : undefined;
|
|
3530
|
+
}
|
|
3531
|
+
const detectedSourceGitSha = readGitHead(gameRoot);
|
|
3532
|
+
return detectedSourceGitSha !== undefined && isSourceGitSha(detectedSourceGitSha)
|
|
3533
|
+
? detectedSourceGitSha
|
|
3534
|
+
: undefined;
|
|
3535
|
+
}
|
|
3502
3536
|
function readSourceGitSha(gameRoot) {
|
|
3537
|
+
const head = readGitHead(gameRoot);
|
|
3538
|
+
if (head === undefined) {
|
|
3539
|
+
throw new Error(`Failed to resolve the game source Git revision for ${gameRoot}. `
|
|
3540
|
+
+ 'Set MPGD_SOURCE_GIT_SHA to its lowercase 40-character commit SHA.');
|
|
3541
|
+
}
|
|
3542
|
+
return requireSourceGitSha(head);
|
|
3543
|
+
}
|
|
3544
|
+
function readGitHead(gameRoot) {
|
|
3503
3545
|
const result = spawnSync('git', ['-C', gameRoot, 'rev-parse', 'HEAD'], {
|
|
3504
3546
|
encoding: 'utf8',
|
|
3505
3547
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
3506
3548
|
});
|
|
3507
3549
|
if (result.error !== undefined || result.status !== 0) {
|
|
3508
|
-
return
|
|
3550
|
+
return undefined;
|
|
3551
|
+
}
|
|
3552
|
+
const head = result.stdout.trim();
|
|
3553
|
+
return head.length === 0 ? undefined : head;
|
|
3554
|
+
}
|
|
3555
|
+
function requireSourceGitSha(value) {
|
|
3556
|
+
if (!isSourceGitSha(value)) {
|
|
3557
|
+
throw new Error('MPGD_SOURCE_GIT_SHA must be a lowercase 40-character game source SHA.');
|
|
3509
3558
|
}
|
|
3510
|
-
return
|
|
3559
|
+
return value;
|
|
3560
|
+
}
|
|
3561
|
+
function isSourceGitSha(value) {
|
|
3562
|
+
return /^[0-9a-f]{40}$/u.test(value);
|
|
3511
3563
|
}
|
|
3512
3564
|
function readGamePackageVersion(gameRoot) {
|
|
3513
3565
|
const packageFile = path.join(gameRoot, 'package.json');
|
package/dist/kit-upgrade.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFile as execFileCallback, spawnSync } from 'node:child_process';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
|
-
import { existsSync, lstatSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
+
import { existsSync, lstatSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import { compare, intersects, minVersion, satisfies, valid, validRange } from 'semver';
|
|
@@ -154,6 +154,9 @@ export function applyKitUpgrade(plan, runLockfileUpdate = updatePnpmLockfile) {
|
|
|
154
154
|
...plan.manifestDigests,
|
|
155
155
|
...plan.lockfileDigests,
|
|
156
156
|
})) {
|
|
157
|
+
// Re-check links before the digest read. readFileSync follows a symlink,
|
|
158
|
+
// so a same-bytes swap would otherwise look unchanged.
|
|
159
|
+
assertOwnedUpgradePath(upgradeWriteAnchor(plan, file), file);
|
|
157
160
|
const current = readFileSync(file, 'utf8');
|
|
158
161
|
if (digest(current) !== expected) {
|
|
159
162
|
throw new Error('File changed after upgrade planning: ' + file);
|
|
@@ -176,9 +179,11 @@ export function applyKitUpgrade(plan, runLockfileUpdate = updatePnpmLockfile) {
|
|
|
176
179
|
section[update.packageName] = update.next;
|
|
177
180
|
parsed[update.section] = section;
|
|
178
181
|
}
|
|
179
|
-
|
|
182
|
+
writeUpgradeFile(plan, manifest, stringifyLike(source, parsed));
|
|
180
183
|
}
|
|
181
184
|
for (const root of plan.workspaceRoots) {
|
|
185
|
+
const lockfile = path.join(root, 'pnpm-lock.yaml');
|
|
186
|
+
assertOwnedUpgradePath(upgradeWriteAnchor(plan, lockfile), lockfile);
|
|
182
187
|
runLockfileUpdate(root);
|
|
183
188
|
}
|
|
184
189
|
}
|
|
@@ -186,7 +191,7 @@ export function applyKitUpgrade(plan, runLockfileUpdate = updatePnpmLockfile) {
|
|
|
186
191
|
const restoreFailures = [];
|
|
187
192
|
for (const [file, original] of snapshots) {
|
|
188
193
|
try {
|
|
189
|
-
|
|
194
|
+
writeUpgradeFile(plan, file, original);
|
|
190
195
|
}
|
|
191
196
|
catch (restoreError) {
|
|
192
197
|
restoreFailures.push(file + ': ' + formatError(restoreError));
|
|
@@ -473,6 +478,100 @@ function stringifyLike(original, value) {
|
|
|
473
478
|
return JSON.stringify(value, null, indent).replaceAll('\n', newline)
|
|
474
479
|
+ (original.endsWith('\n') ? newline : '');
|
|
475
480
|
}
|
|
481
|
+
function upgradeWriteAnchor(plan, file) {
|
|
482
|
+
const resolved = path.resolve(file);
|
|
483
|
+
let anchor = plan.gameRoot;
|
|
484
|
+
while (!inside(anchor, resolved)) {
|
|
485
|
+
const parent = path.dirname(anchor);
|
|
486
|
+
if (parent === anchor) {
|
|
487
|
+
throw new Error('Refusing to write outside the planned upgrade directory: ' + file);
|
|
488
|
+
}
|
|
489
|
+
anchor = parent;
|
|
490
|
+
}
|
|
491
|
+
// In-game files used to return plan.gameRoot before this check. A game
|
|
492
|
+
// directory or parent swapped for a symlink after planning then skipped
|
|
493
|
+
// lstat/realpath, and assertOwnedUpgradePath only inspects children.
|
|
494
|
+
let anchorStat;
|
|
495
|
+
try {
|
|
496
|
+
anchorStat = lstatSync(anchor);
|
|
497
|
+
}
|
|
498
|
+
catch (error) {
|
|
499
|
+
throw new Error('Refusing to write through a symbolic link: ' + anchor, { cause: error });
|
|
500
|
+
}
|
|
501
|
+
if (anchorStat.isSymbolicLink()) {
|
|
502
|
+
throw new Error('Refusing to write through a symbolic link: ' + anchor);
|
|
503
|
+
}
|
|
504
|
+
const anchorReal = realpathSync(anchor);
|
|
505
|
+
// Compare with the game root captured at planning time. A fresh realpath of
|
|
506
|
+
// both sides would follow a parent swapped for a symlink and look consistent.
|
|
507
|
+
if (!inside(anchorReal, plan.gameRoot)
|
|
508
|
+
&& path.resolve(anchorReal) !== path.resolve(plan.gameRoot)) {
|
|
509
|
+
throw new Error('Refusing to write through a symbolic link: ' + anchor);
|
|
510
|
+
}
|
|
511
|
+
return anchor;
|
|
512
|
+
}
|
|
513
|
+
function assertOwnedUpgradePath(anchor, file) {
|
|
514
|
+
const resolved = path.resolve(file);
|
|
515
|
+
if (!inside(anchor, resolved)) {
|
|
516
|
+
throw new Error('Refusing to write outside the planned upgrade directory: ' + file);
|
|
517
|
+
}
|
|
518
|
+
const relative = path.relative(anchor, resolved);
|
|
519
|
+
const parts = relative.split(path.sep).filter((part) => part !== '' && part !== '.');
|
|
520
|
+
let current = anchor;
|
|
521
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
522
|
+
const part = parts[index];
|
|
523
|
+
if (part === undefined) {
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
current = path.join(current, part);
|
|
527
|
+
const isFinal = index === parts.length - 1;
|
|
528
|
+
let stat;
|
|
529
|
+
try {
|
|
530
|
+
stat = lstatSync(current);
|
|
531
|
+
}
|
|
532
|
+
catch (error) {
|
|
533
|
+
if (isErrno(error, 'ENOENT') && isFinal) {
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
throw new Error('Upgrade file disappeared before write: ' + file, { cause: error });
|
|
537
|
+
}
|
|
538
|
+
if (stat.isSymbolicLink()) {
|
|
539
|
+
throw new Error('Refusing to write through a symbolic link: ' + current);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
if (!inside(realpathSync(anchor), realpathSync(resolved))) {
|
|
543
|
+
throw new Error('Refusing to write through a symbolic link: ' + file);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
function writeUpgradeFile(plan, file, source) {
|
|
547
|
+
const anchor = upgradeWriteAnchor(plan, file);
|
|
548
|
+
assertOwnedUpgradePath(anchor, file);
|
|
549
|
+
const temporary = `${file}.mpgd-${randomUUID()}.tmp`;
|
|
550
|
+
try {
|
|
551
|
+
let mode = 0o644;
|
|
552
|
+
try {
|
|
553
|
+
const stat = lstatSync(file);
|
|
554
|
+
if (stat.isSymbolicLink()) {
|
|
555
|
+
throw new Error('Refusing to write through a symbolic link: ' + file);
|
|
556
|
+
}
|
|
557
|
+
mode = stat.mode & 0o777;
|
|
558
|
+
}
|
|
559
|
+
catch (error) {
|
|
560
|
+
if (!isErrno(error, 'ENOENT')) {
|
|
561
|
+
throw error;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
writeFileSync(temporary, source, { flag: 'wx', mode });
|
|
565
|
+
assertOwnedUpgradePath(anchor, file);
|
|
566
|
+
renameSync(temporary, file);
|
|
567
|
+
}
|
|
568
|
+
finally {
|
|
569
|
+
rmSync(temporary, { force: true });
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
function isErrno(error, code) {
|
|
573
|
+
return error !== null && typeof error === 'object' && 'code' in error && error.code === code;
|
|
574
|
+
}
|
|
476
575
|
function inside(root, candidate) {
|
|
477
576
|
const relative = path.relative(root, candidate);
|
|
478
577
|
return relative === '' || (!relative.startsWith('..' + path.sep) && relative !== '..'
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface LegalSiteFile {
|
|
2
|
+
readonly file: string;
|
|
3
|
+
readonly content: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Legal-site paths are contained only after the nearest existing ancestor is
|
|
7
|
+
* canonicalized. A lexical `path.relative` check treats a symlinked `artifacts`
|
|
8
|
+
* directory as inside the project while later writes follow the link.
|
|
9
|
+
*/
|
|
10
|
+
export declare function assertLegalProjectPath(label: string, resolved: string): void;
|
|
11
|
+
/** Stage the site beside the destination, then publish with rename. */
|
|
12
|
+
export declare function publishLegalSiteFiles(outDir: string, outputs: readonly LegalSiteFile[]): void;
|
|
@@ -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
|
+
}
|
|
@@ -6990,9 +6990,6 @@ var releaseBackoffMs = 60 * secondMs;
|
|
|
6990
6990
|
// node_modules/.cache/mpgd-native-build/adapters/devvit/src/server/redis-notification-store.js
|
|
6991
6991
|
var defaultPurgeLimit = Number.POSITIVE_INFINITY;
|
|
6992
6992
|
|
|
6993
|
-
// node_modules/.cache/mpgd-native-build/adapters/devvit/src/notifications-contract.js
|
|
6994
|
-
var devvitHostCodeCharacters = "[A-Za-z0-9_.:-]{1,128}";
|
|
6995
|
-
|
|
6996
6993
|
// node_modules/.cache/mpgd-native-build/adapters/devvit/src/server/notifications-delivery.js
|
|
6997
6994
|
var devvitNotificationProviderCodes = Object.freeze({
|
|
6998
6995
|
deviceRecipient: "DEVVIT_DEVICE_RECIPIENT",
|
|
@@ -7023,13 +7020,17 @@ var devvitNotificationSchedulerTasks = Object.freeze({
|
|
|
7023
7020
|
})
|
|
7024
7021
|
});
|
|
7025
7022
|
|
|
7023
|
+
// node_modules/.cache/mpgd-native-build/packages/bridge/src/notifications.js
|
|
7024
|
+
var hostCodeCharacters = "[A-Za-z0-9_.:-]{1,128}";
|
|
7025
|
+
var hostCodeValue = new RegExp(`^${hostCodeCharacters}$`, "u");
|
|
7026
|
+
var hostCodeSuffix = new RegExp(`\\(host code (${hostCodeCharacters})\\)$`, "u");
|
|
7027
|
+
|
|
7026
7028
|
// node_modules/.cache/mpgd-native-build/adapters/devvit/src/server/notifications-bridge.js
|
|
7027
7029
|
var devvitNotificationHostCodes = Object.freeze({
|
|
7028
7030
|
optInRefused: "DEVVIT_OPT_IN_REFUSED",
|
|
7029
7031
|
optOutRefused: "DEVVIT_OPT_OUT_REFUSED",
|
|
7030
7032
|
pluginFailed: "DEVVIT_NOTIFICATIONS_PLUGIN_FAILED"
|
|
7031
7033
|
});
|
|
7032
|
-
var hostCodeValue = new RegExp(`^${devvitHostCodeCharacters}$`, "u");
|
|
7033
7034
|
|
|
7034
7035
|
// node_modules/.cache/mpgd-native-build/packages/cli/src/target-name.js
|
|
7035
7036
|
var deploymentTargetNamePattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
@@ -9502,9 +9503,21 @@ var assertReleaseManifestStructure = /* @__PURE__ */ (() => {
|
|
|
9502
9503
|
};
|
|
9503
9504
|
})();
|
|
9504
9505
|
var fullGitShaPattern = /^[0-9a-f]{40}$/u;
|
|
9506
|
+
var sha256DigestPattern = /^[0-9a-f]{64}$/u;
|
|
9505
9507
|
function assertReleaseManifest(input) {
|
|
9506
9508
|
const manifest = assertReleaseManifestStructure(input);
|
|
9509
|
+
if (!fullGitShaPattern.test(manifest.gitSha)) {
|
|
9510
|
+
throw new TypeError("Release manifest gitSha must be a lowercase 40-character SHA.");
|
|
9511
|
+
}
|
|
9512
|
+
if (!fullGitShaPattern.test(manifest.kitGitSha)) {
|
|
9513
|
+
throw new TypeError("Release manifest kitGitSha must be a lowercase 40-character SHA.");
|
|
9514
|
+
}
|
|
9507
9515
|
for (const [target2, entry] of Object.entries(manifest.targets)) {
|
|
9516
|
+
assertSha256Digest(entry.effectiveConfig.digest, `Release manifest ${target2} effectiveConfig.digest`);
|
|
9517
|
+
assertSha256Digest(entry.iconManifest.digest, `Release manifest ${target2} iconManifest.digest`);
|
|
9518
|
+
assertSha256Digest(entry.iconManifest.sourceSha256, `Release manifest ${target2} iconManifest.sourceSha256`);
|
|
9519
|
+
assertSha256Digest(entry.iconManifest.sharedConfigSha256, `Release manifest ${target2} iconManifest.sharedConfigSha256`);
|
|
9520
|
+
assertSha256Digest(entry.iconManifest.renderConfigSha256, `Release manifest ${target2} iconManifest.renderConfigSha256`);
|
|
9508
9521
|
const delivery = entry.nativeDelivery;
|
|
9509
9522
|
if (delivery === void 0) {
|
|
9510
9523
|
continue;
|
|
@@ -9519,9 +9532,6 @@ function assertReleaseManifest(input) {
|
|
|
9519
9532
|
throw new TypeError(`Release manifest production native delivery is unsigned: ${target2}.`);
|
|
9520
9533
|
}
|
|
9521
9534
|
}
|
|
9522
|
-
if (!fullGitShaPattern.test(manifest.kitGitSha)) {
|
|
9523
|
-
throw new TypeError("Release manifest kitGitSha must be a lowercase 40-character SHA.");
|
|
9524
|
-
}
|
|
9525
9535
|
if (manifest.releaseIdentity !== void 0) {
|
|
9526
9536
|
const releaseIdentity = createMpgdReleaseIdentity({
|
|
9527
9537
|
gameVersion: manifest.releaseIdentity.gameVersion,
|
|
@@ -9543,6 +9553,11 @@ function assertReleaseManifest(input) {
|
|
|
9543
9553
|
}
|
|
9544
9554
|
return manifest;
|
|
9545
9555
|
}
|
|
9556
|
+
function assertSha256Digest(value, label) {
|
|
9557
|
+
if (!sha256DigestPattern.test(value)) {
|
|
9558
|
+
throw new TypeError(`${label} must be a lowercase 64-character SHA-256 digest.`);
|
|
9559
|
+
}
|
|
9560
|
+
}
|
|
9546
9561
|
|
|
9547
9562
|
// node_modules/.cache/mpgd-native-build/tools/target/release-manifest-lock.js
|
|
9548
9563
|
import { randomUUID } from "node:crypto";
|
|
@@ -9641,7 +9656,7 @@ function canRecoverLock(lockFile) {
|
|
|
9641
9656
|
// node_modules/.cache/mpgd-native-build/tools/target/generate-release-manifest.js
|
|
9642
9657
|
var __typia_transform__accessExpressionAsString6 = _accessExpressionAsString_110._accessExpressionAsString;
|
|
9643
9658
|
var assertProductCatalog = /* @__PURE__ */ (() => {
|
|
9644
|
-
const _ae0 = '({ readonly type: "currency"; readonly currency: string & MinLength<1>; readonly amount:
|
|
9659
|
+
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>; })';
|
|
9645
9660
|
const _ao0 = (input, _path, _exceptionable = true) => ("string" === typeof input.version || _assertGuard_19._assertGuard(_exceptionable, {
|
|
9646
9661
|
method: "typia.createAssert",
|
|
9647
9662
|
path: _path + ".version",
|
|
@@ -9739,10 +9754,25 @@ var assertProductCatalog = /* @__PURE__ */ (() => {
|
|
|
9739
9754
|
path: _path + ".currency",
|
|
9740
9755
|
expected: "(string & MinLength<1>)",
|
|
9741
9756
|
value: input.currency
|
|
9742
|
-
}, _errorFactory)) && ("number" === typeof input.amount || _assertGuard_19._assertGuard(_exceptionable, {
|
|
9757
|
+
}, _errorFactory)) && ("number" === typeof input.amount && (1 <= input.amount || _assertGuard_19._assertGuard(_exceptionable, {
|
|
9743
9758
|
method: "typia.createAssert",
|
|
9744
9759
|
path: _path + ".amount",
|
|
9745
|
-
expected: "number",
|
|
9760
|
+
expected: "number & Minimum<1>",
|
|
9761
|
+
value: input.amount
|
|
9762
|
+
}, _errorFactory)) && (input.amount <= 9007199254740991 || _assertGuard_19._assertGuard(_exceptionable, {
|
|
9763
|
+
method: "typia.createAssert",
|
|
9764
|
+
path: _path + ".amount",
|
|
9765
|
+
expected: "number & Maximum<9007199254740991>",
|
|
9766
|
+
value: input.amount
|
|
9767
|
+
}, _errorFactory)) && (input.amount % 1 === 0 || _assertGuard_19._assertGuard(_exceptionable, {
|
|
9768
|
+
method: "typia.createAssert",
|
|
9769
|
+
path: _path + ".amount",
|
|
9770
|
+
expected: "number & MultipleOf<1>",
|
|
9771
|
+
value: input.amount
|
|
9772
|
+
}, _errorFactory)) || _assertGuard_19._assertGuard(_exceptionable, {
|
|
9773
|
+
method: "typia.createAssert",
|
|
9774
|
+
path: _path + ".amount",
|
|
9775
|
+
expected: "(number & Minimum<1> & Maximum<9007199254740991> & MultipleOf<1>)",
|
|
9746
9776
|
value: input.amount
|
|
9747
9777
|
}, _errorFactory));
|
|
9748
9778
|
const _ao3 = (input, _path, _exceptionable = true) => ("entitlement" === input.type || _assertGuard_19._assertGuard(_exceptionable, {
|
|
@@ -9815,7 +9845,7 @@ var assertProductCatalog = /* @__PURE__ */ (() => {
|
|
|
9815
9845
|
})();
|
|
9816
9846
|
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)));
|
|
9817
9847
|
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));
|
|
9818
|
-
const _io2 = (input) => "currency" === input.type && ("string" === typeof input.currency && 1 <= input.currency.length) && "number" === typeof input.amount;
|
|
9848
|
+
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));
|
|
9819
9849
|
const _io3 = (input) => "entitlement" === input.type && "string" === typeof input.entitlement;
|
|
9820
9850
|
const _io4 = (input) => "resource" === input.type && ("string" === typeof input.resource && 1 <= input.resource.length) && ("number" === typeof input.amount && (0 < input.amount && input.amount <= 17976931348623157e292));
|
|
9821
9851
|
const _io5 = (input) => Object.keys(input).every((key) => {
|
|
@@ -9855,16 +9885,31 @@ var assertProductCatalog = /* @__PURE__ */ (() => {
|
|
|
9855
9885
|
};
|
|
9856
9886
|
})();
|
|
9857
9887
|
var assertAdPlacements = /* @__PURE__ */ (() => {
|
|
9858
|
-
const _ip0 = (input) => "number" === typeof input["amount"];
|
|
9888
|
+
const _ip0 = (input) => "number" === typeof input["amount"] && (1 <= input["amount"] && input["amount"] <= 9007199254740991 && input["amount"] % 1 === 0);
|
|
9859
9889
|
const _ae0 = '(AdPlacementEntryBase & { readonly type: "banner" | "interstitial"; readonly reward?: never; } | AdPlacementEntryBase & { readonly type: "rewarded"; readonly reward?: AdReward; })';
|
|
9860
|
-
const _ae1 = '(undefined | { readonly type: "continue"; readonly amount:
|
|
9861
|
-
const _ap0 = (input, _path, _exceptionable = true) => "number" === typeof input["amount"] || _assertGuard_19._assertGuard(_exceptionable, {
|
|
9890
|
+
const _ae1 = '(undefined | { readonly type: "continue"; readonly amount: PositiveLedgerAmount; } | { readonly type: "currency"; readonly amount: PositiveLedgerAmount; readonly currency: string & MinLength<1>; })';
|
|
9891
|
+
const _ap0 = (input, _path, _exceptionable = true) => "number" === typeof input["amount"] && (1 <= input["amount"] || _assertGuard_19._assertGuard(_exceptionable, {
|
|
9862
9892
|
method: "typia.createAssert",
|
|
9863
9893
|
path: _path + ".amount",
|
|
9864
|
-
expected: "number",
|
|
9894
|
+
expected: "number & Minimum<1>",
|
|
9895
|
+
value: input["amount"]
|
|
9896
|
+
}, _errorFactory)) && (input["amount"] <= 9007199254740991 || _assertGuard_19._assertGuard(_exceptionable, {
|
|
9897
|
+
method: "typia.createAssert",
|
|
9898
|
+
path: _path + ".amount",
|
|
9899
|
+
expected: "number & Maximum<9007199254740991>",
|
|
9900
|
+
value: input["amount"]
|
|
9901
|
+
}, _errorFactory)) && (input["amount"] % 1 === 0 || _assertGuard_19._assertGuard(_exceptionable, {
|
|
9902
|
+
method: "typia.createAssert",
|
|
9903
|
+
path: _path + ".amount",
|
|
9904
|
+
expected: "number & MultipleOf<1>",
|
|
9905
|
+
value: input["amount"]
|
|
9906
|
+
}, _errorFactory)) || _assertGuard_19._assertGuard(_exceptionable, {
|
|
9907
|
+
method: "typia.createAssert",
|
|
9908
|
+
path: _path + ".amount",
|
|
9909
|
+
expected: "(number & Minimum<1> & Maximum<9007199254740991> & MultipleOf<1>)",
|
|
9865
9910
|
value: input["amount"]
|
|
9866
9911
|
}, _errorFactory);
|
|
9867
|
-
const _ae2 = '({ readonly type: "continue"; readonly amount:
|
|
9912
|
+
const _ae2 = '({ readonly type: "continue"; readonly amount: PositiveLedgerAmount; } | { readonly type: "currency"; readonly amount: PositiveLedgerAmount; readonly currency: string & MinLength<1>; })';
|
|
9868
9913
|
const _ao0 = (input, _path, _exceptionable = true) => ("string" === typeof input.version || _assertGuard_19._assertGuard(_exceptionable, {
|
|
9869
9914
|
method: "typia.createAssert",
|
|
9870
9915
|
path: _path + ".version",
|
|
@@ -10373,9 +10418,47 @@ function readSdkMajor(envValue, metadataValue) {
|
|
|
10373
10418
|
function getSourceGitSha(kitGitSha) {
|
|
10374
10419
|
const configuredGitSha = readOptionalString2(process.env.MPGD_SOURCE_GIT_SHA);
|
|
10375
10420
|
if (configuredGitSha !== void 0) {
|
|
10376
|
-
return configuredGitSha;
|
|
10421
|
+
return requireFullGitSha(configuredGitSha, "MPGD_SOURCE_GIT_SHA");
|
|
10377
10422
|
}
|
|
10378
|
-
|
|
10423
|
+
if (isInRepoKitBuild()) {
|
|
10424
|
+
return inRepoKitSourceGitSha(kitGitSha);
|
|
10425
|
+
}
|
|
10426
|
+
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.");
|
|
10427
|
+
}
|
|
10428
|
+
function inRepoKitSourceGitSha(kitGitSha) {
|
|
10429
|
+
return requireFullGitSha(kitGitSha, "in-repo kit source Git SHA");
|
|
10430
|
+
}
|
|
10431
|
+
function isInRepoKitBuild() {
|
|
10432
|
+
if (process.env.MPGD_NATIVE_PACKAGED_BUILD === "1") {
|
|
10433
|
+
return false;
|
|
10434
|
+
}
|
|
10435
|
+
const targetsFile = readOptionalString2(process.env.MPGD_PLATFORM_TARGETS_FILE);
|
|
10436
|
+
if (targetsFile === void 0) {
|
|
10437
|
+
return true;
|
|
10438
|
+
}
|
|
10439
|
+
return isInsideDirectory(resolveKitRoot(), targetsFile);
|
|
10440
|
+
}
|
|
10441
|
+
function isInsideDirectory(root, candidate) {
|
|
10442
|
+
let resolvedRoot;
|
|
10443
|
+
try {
|
|
10444
|
+
resolvedRoot = realpathSync6(root);
|
|
10445
|
+
} catch {
|
|
10446
|
+
return false;
|
|
10447
|
+
}
|
|
10448
|
+
const resolvedCandidate = resolve12(candidate);
|
|
10449
|
+
let comparable = resolvedCandidate;
|
|
10450
|
+
try {
|
|
10451
|
+
comparable = realpathSync6(resolvedCandidate);
|
|
10452
|
+
} catch {
|
|
10453
|
+
}
|
|
10454
|
+
const rel = relative9(resolvedRoot, comparable);
|
|
10455
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute8(rel);
|
|
10456
|
+
}
|
|
10457
|
+
function requireFullGitSha(value, label) {
|
|
10458
|
+
if (!/^[0-9a-f]{40}$/u.test(value)) {
|
|
10459
|
+
throw new Error(`${label} must be a lowercase 40-character SHA.`);
|
|
10460
|
+
}
|
|
10461
|
+
return value;
|
|
10379
10462
|
}
|
|
10380
10463
|
function resolveKitGitSha() {
|
|
10381
10464
|
if (process.env.MPGD_NATIVE_PACKAGED_BUILD === "1") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpgd/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.55.0",
|
|
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.
|
|
74
|
-
"@mpgd/
|
|
75
|
-
"@mpgd/
|
|
76
|
-
"@mpgd/
|
|
73
|
+
"@mpgd/catalog": "0.10.7",
|
|
74
|
+
"@mpgd/target-config": "0.23.7",
|
|
75
|
+
"@mpgd/platform": "0.27.2",
|
|
76
|
+
"@mpgd/phaser-assets": "0.9.1"
|
|
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.
|
|
85
|
-
"@mpgd/adapter-browser": "0.11.
|
|
86
|
-
"@mpgd/adapter-
|
|
87
|
-
"@mpgd/adapter-
|
|
88
|
-
"@mpgd/
|
|
89
|
-
"@mpgd/
|
|
90
|
-
"@mpgd/
|
|
91
|
-
"@mpgd/
|
|
92
|
-
"@mpgd/capacitor-storekit": "0.3.
|
|
93
|
-
"@mpgd/game-
|
|
94
|
-
"@mpgd/
|
|
95
|
-
"@mpgd/
|
|
84
|
+
"@mpgd/adapter-ait": "0.20.3",
|
|
85
|
+
"@mpgd/adapter-browser": "0.11.7",
|
|
86
|
+
"@mpgd/adapter-capacitor": "0.12.3",
|
|
87
|
+
"@mpgd/adapter-devvit": "0.13.5",
|
|
88
|
+
"@mpgd/adapter-verse8": "0.7.7",
|
|
89
|
+
"@mpgd/analytics": "0.4.16",
|
|
90
|
+
"@mpgd/capacitor-play-billing": "0.3.7",
|
|
91
|
+
"@mpgd/bridge": "0.15.0",
|
|
92
|
+
"@mpgd/capacitor-storekit": "0.3.7",
|
|
93
|
+
"@mpgd/game-runtime": "0.8.0",
|
|
94
|
+
"@mpgd/game-services": "0.27.2",
|
|
95
|
+
"@mpgd/i18n": "0.6.21",
|
|
96
96
|
"@mpgd/lite-shell": "0.1.0"
|
|
97
97
|
},
|
|
98
98
|
"main": "./dist/index.js",
|
|
@@ -6,29 +6,30 @@
|
|
|
6
6
|
"scripts": {
|
|
7
7
|
"prepare:icon": "pnpm --dir ../.. icons:generate:devvit",
|
|
8
8
|
"prepare:icon:production": "pnpm --dir ../.. icons:generate:devvit:production",
|
|
9
|
-
"init": "pnpm run prepare:icon:production && devvit upload --config devvit.json",
|
|
10
|
-
"init:copy-paste": "pnpm run prepare:icon:production && devvit upload --copy-paste --config devvit.json",
|
|
11
|
-
"dev": "pnpm run prepare:icon && devvit playtest --config devvit.json",
|
|
9
|
+
"init": "pnpm run check:sdk && pnpm run prepare:icon:production && devvit upload --config devvit.json",
|
|
10
|
+
"init:copy-paste": "pnpm run check:sdk && pnpm run prepare:icon:production && devvit upload --copy-paste --config devvit.json",
|
|
11
|
+
"dev": "pnpm run check:sdk && pnpm run prepare:icon && devvit playtest --config devvit.json",
|
|
12
12
|
"build": "pnpm --dir ../.. build:devvit",
|
|
13
13
|
"whoami": "devvit whoami",
|
|
14
|
-
"upload": "pnpm run prepare:icon:production && devvit upload --config devvit.json",
|
|
15
|
-
"publish": "pnpm run prepare:icon:production && devvit publish --config devvit.json",
|
|
14
|
+
"upload": "pnpm run check:sdk && pnpm run prepare:icon:production && devvit upload --config devvit.json",
|
|
15
|
+
"publish": "pnpm run check:sdk && pnpm run prepare:icon:production && devvit publish --config devvit.json",
|
|
16
16
|
"check": "ttsc --noEmit -p tsconfig.json",
|
|
17
17
|
"lint": "ttsc --noEmit -p tsconfig.json",
|
|
18
18
|
"format": "ttsc format",
|
|
19
|
-
"fix": "ttsc fix"
|
|
19
|
+
"fix": "ttsc fix",
|
|
20
|
+
"check:sdk": "pnpm exec mpgd kit devvit-check --project ."
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|
|
22
|
-
"@devvit/web": "0.
|
|
23
|
+
"@devvit/web": "0.15.0",
|
|
23
24
|
"@mpgd/adapter-devvit": "__MPGD_DEPENDENCY_VERSION_ADAPTER_DEVVIT__",
|
|
24
25
|
"@mpgd/bridge": "__MPGD_DEPENDENCY_VERSION_BRIDGE__",
|
|
25
26
|
"@mpgd/catalog": "__MPGD_DEPENDENCY_VERSION_CATALOG__"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
|
-
"@devvit/start": "0.
|
|
29
|
+
"@devvit/start": "0.15.0",
|
|
29
30
|
"@ttsc/unplugin": "0.32.1",
|
|
30
31
|
"@types/node": "^24.0.0",
|
|
31
|
-
"devvit": "0.
|
|
32
|
+
"devvit": "0.15.0",
|
|
32
33
|
"ttsc": "0.32.1",
|
|
33
34
|
"typescript": "7.0.2",
|
|
34
35
|
"vite": "7.3.6"
|