shipthis
Version:
ShipThis manages building and uploading your Godot games to the App Store and Google Play.
128 lines (124 loc) • 5.16 kB
JavaScript
import fs__default from 'node:fs';
import { useMutation } from '@tanstack/react-query';
import axios from 'axios';
import fg from 'fast-glob';
import { v4 } from 'uuid';
import { ZipFile } from 'yazl';
import { H as queryClient, k as getProject, v as DEFAULT_SHIPPED_FILES_GLOBS, w as DEFAULT_IGNORED_FILES_GLOBS, a0 as getNewUploadTicket, a1 as startJobsFromUpload } from './baseCommand-CTn3KGH3.js';
import { k as cacheKeys, s as getFileHash, t as getPlatformName } from './baseGameCommand-8VL7xe-O.js';
import { g as getCWDGitInfo } from './git-BpsfNFZ_.js';
import { jsx, Fragment } from 'react/jsx-runtime';
import 'ink';
import 'ink-spinner';
import 'react';
import 'crypto-js';
import 'luxon';
import 'socket.io-client';
import { u as useJobWatching } from './JobLogTail-Da8GuReK.js';
import 'fullscreen-ink';
import 'string-length';
import 'strip-ansi';
import 'open';
import '@inkjs/ui';
import 'node:path';
import 'marked';
import 'marked-terminal';
import { P as ProgressSpinner } from './ProgressSpinner-Um6ARKlk.js';
import 'qrcode';
async function ship({ command, log = () => {
}, shipFlags }) {
const commandFlags = command.getFlags();
const finalFlags = shipFlags || commandFlags;
const { verbose, useDemoCredentials } = finalFlags;
verbose && log("Fetching game config...");
const projectConfig = await command.getProjectConfig();
if (!projectConfig.project) throw new Error("No project found in project config");
const project = await getProject(projectConfig.project.id);
const projectUsesDemoCredentials = Boolean(project.details?.useDemoCredentials);
const isUsingDemoCredentials = useDemoCredentials ?? projectUsesDemoCredentials ?? false;
const hasConfiguredIos = Boolean(project.details?.iosBundleId);
const hasConfiguredAndroid = Boolean(project.details?.androidPackageName);
if (!isUsingDemoCredentials && !hasConfiguredAndroid && !hasConfiguredIos) {
throw new Error(
"No Android or iOS configuration found. Please run `shipthis game wizard android` or `shipthis game wizard ios` to configure your game."
);
}
verbose && log("Retrieving file globs...");
const shippedFilesGlobs = projectConfig.shippedFilesGlobs || DEFAULT_SHIPPED_FILES_GLOBS;
const ignoredFilesGlobs = projectConfig.ignoredFilesGlobs || DEFAULT_IGNORED_FILES_GLOBS;
verbose && log("Finding files to include in zip...");
const files = await fg(shippedFilesGlobs, { dot: true, ignore: ignoredFilesGlobs });
verbose && log(`Found ${files.length} files, adding to zip...`);
const zipFile = new ZipFile();
for (const file of files) {
zipFile.addFile(file, file);
}
const outputZipToFile = (zip, fileName) => new Promise((resolve) => {
const outputStream = fs__default.createWriteStream(fileName);
zip.outputStream.pipe(outputStream).on("close", () => resolve());
zip.end();
});
const tmpZipFile = `${process.cwd()}/shipthis-${v4()}.zip`;
log(`Creating zip file: ${tmpZipFile}`);
await outputZipToFile(zipFile, tmpZipFile);
verbose && log("Reading zip file buffer...");
const zipBuffer = fs__default.readFileSync(tmpZipFile);
const { size } = fs__default.statSync(tmpZipFile);
verbose && log("Requesting upload ticket...");
const uploadTicket = await getNewUploadTicket(projectConfig.project.id);
log("Uploading zip file...");
await axios.put(uploadTicket.url, zipBuffer, {
headers: {
"Content-Type": "application/zip",
"Content-length": size
}
});
verbose && log("Fetching Git info...");
const gitInfo = await getCWDGitInfo();
verbose && log("Computing file hash...");
const zipFileMd5 = await getFileHash(tmpZipFile);
const uploadDetails = {
...gitInfo,
zipFileMd5
};
verbose && log("Starting jobs from upload...");
const startJobsOptions = {
...uploadDetails,
platform: finalFlags.platform?.toUpperCase(),
skipPublish: finalFlags.skipPublish,
verbose: finalFlags.verbose,
useDemoCredentials: isUsingDemoCredentials,
gameEngineVersion: finalFlags.gameEngineVersion
};
const jobs = await startJobsFromUpload(uploadTicket.id, startJobsOptions);
verbose && log("Cleaning up temporary zip file...");
fs__default.unlinkSync(tmpZipFile);
verbose && log("Job submission complete.");
if (jobs.length === 0) {
throw new Error("No jobs were created. Please check your game configuration and try again.");
}
if (finalFlags?.follow) {
log("Waiting for job to start...");
}
return jobs;
}
const useShip = () => useMutation({
mutationFn: ship,
async onSuccess(data) {
queryClient.invalidateQueries({
queryKey: cacheKeys.jobs({ pageNumber: 0, projectId: data[0].project.id })
});
}
});
const JobProgress = (props) => {
const { progress } = useJobWatching({
isWatching: true,
jobId: props.job.id,
onComplete: props.onComplete,
onFailure: props.onFailure,
projectId: props.job.project.id
});
const label = `${getPlatformName(props.job.type)} build progress...`;
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(ProgressSpinner, { label, progress, spinnerType: "dots" }) });
};
export { JobProgress as J, useShip as u };