mirror of
https://github.com/game-ci/unity-builder.git
synced 2026-06-11 08:23:56 -07:00
feat(artifacts): complete generic artifact system with upload handlers, tests, and action integration (#798)
- Add ArtifactUploadHandler with support for github-artifacts, storage (rclone), and local copy upload targets, including large file chunking for GitHub Artifacts - Add 44 unit tests covering OutputTypeRegistry, OutputService, and ArtifactUploadHandler (config parsing, upload coordination, file collection) - Add 6 new action.yml inputs for artifact configuration - Add artifactManifestPath action output - Wire artifact collection and upload into index.ts post-build flow Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+572
@@ -34,10 +34,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
const core = __importStar(__nccwpck_require__(42186));
|
||||
const node_path_1 = __importDefault(__nccwpck_require__(49411));
|
||||
const model_1 = __nccwpck_require__(41359);
|
||||
const cli_1 = __nccwpck_require__(55651);
|
||||
const mac_builder_1 = __importDefault(__nccwpck_require__(39364));
|
||||
const platform_setup_1 = __importDefault(__nccwpck_require__(64423));
|
||||
const output_service_1 = __nccwpck_require__(18795);
|
||||
const output_type_registry_1 = __nccwpck_require__(58012);
|
||||
const artifact_upload_handler_1 = __nccwpck_require__(49063);
|
||||
async function runMain() {
|
||||
try {
|
||||
if (cli_1.Cli.InitCliMode()) {
|
||||
@@ -70,6 +74,44 @@ async function runMain() {
|
||||
await model_1.Output.setBuildVersion(buildParameters.buildVersion);
|
||||
await model_1.Output.setAndroidVersionCode(buildParameters.androidVersionCode);
|
||||
await model_1.Output.setEngineExitCode(exitCode);
|
||||
// Artifact collection and upload (runs on both success and failure)
|
||||
try {
|
||||
// Register custom output types if provided
|
||||
if (buildParameters.artifactCustomTypes) {
|
||||
try {
|
||||
const customTypes = JSON.parse(buildParameters.artifactCustomTypes);
|
||||
if (Array.isArray(customTypes)) {
|
||||
for (const ct of customTypes) {
|
||||
output_type_registry_1.OutputTypeRegistry.registerType({
|
||||
name: ct.name,
|
||||
defaultPath: ct.defaultPath || ct.pattern || `./${ct.name}/`,
|
||||
description: ct.description || `Custom output type: ${ct.name}`,
|
||||
builtIn: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (parseError) {
|
||||
core.warning(`Failed to parse artifactCustomTypes: ${parseError.message}`);
|
||||
}
|
||||
}
|
||||
// Collect outputs and generate manifest
|
||||
const manifestPath = node_path_1.default.join(buildParameters.projectPath, 'output-manifest.json');
|
||||
const manifest = await output_service_1.OutputService.collectOutputs(buildParameters.projectPath, buildParameters.buildGuid, buildParameters.artifactOutputTypes, manifestPath);
|
||||
core.setOutput('artifactManifestPath', manifestPath);
|
||||
// Upload artifacts
|
||||
const uploadConfig = artifact_upload_handler_1.ArtifactUploadHandler.parseConfig(buildParameters.artifactUploadTarget, buildParameters.artifactUploadPath || undefined, buildParameters.artifactCompression, buildParameters.artifactRetentionDays);
|
||||
const uploadResult = await artifact_upload_handler_1.ArtifactUploadHandler.uploadArtifacts(manifest, uploadConfig, buildParameters.projectPath);
|
||||
if (!uploadResult.success) {
|
||||
core.warning(`Artifact upload completed with errors: ${uploadResult.entries
|
||||
.filter((e) => !e.success)
|
||||
.map((e) => `${e.type}: ${e.error}`)
|
||||
.join('; ')}`);
|
||||
}
|
||||
}
|
||||
catch (artifactError) {
|
||||
core.warning(`Artifact collection/upload failed: ${artifactError.message}`);
|
||||
}
|
||||
if (exitCode !== 0) {
|
||||
core.setFailed(`Build failed with exit code ${exitCode}`);
|
||||
}
|
||||
@@ -375,6 +417,12 @@ class BuildParameters {
|
||||
cacheUnityInstallationOnMac: input_1.default.cacheUnityInstallationOnMac,
|
||||
unityHubVersionOnMac: input_1.default.unityHubVersionOnMac,
|
||||
dockerWorkspacePath: input_1.default.dockerWorkspacePath,
|
||||
artifactOutputTypes: input_1.default.artifactOutputTypes,
|
||||
artifactUploadTarget: input_1.default.artifactUploadTarget,
|
||||
artifactUploadPath: input_1.default.artifactUploadPath,
|
||||
artifactCompression: input_1.default.artifactCompression,
|
||||
artifactRetentionDays: input_1.default.artifactRetentionDays,
|
||||
artifactCustomTypes: input_1.default.artifactCustomTypes,
|
||||
};
|
||||
}
|
||||
static parseBuildFile(filename, platform, androidExportType) {
|
||||
@@ -1823,6 +1871,24 @@ class Input {
|
||||
static get containerRegistryImageVersion() {
|
||||
return Input.getInput('containerRegistryImageVersion') ?? '3';
|
||||
}
|
||||
static get artifactOutputTypes() {
|
||||
return Input.getInput('artifactOutputTypes') ?? 'build,logs,test-results';
|
||||
}
|
||||
static get artifactUploadTarget() {
|
||||
return Input.getInput('artifactUploadTarget') ?? 'github-artifacts';
|
||||
}
|
||||
static get artifactUploadPath() {
|
||||
return Input.getInput('artifactUploadPath') ?? '';
|
||||
}
|
||||
static get artifactCompression() {
|
||||
return Input.getInput('artifactCompression') ?? 'gzip';
|
||||
}
|
||||
static get artifactRetentionDays() {
|
||||
return Input.getInput('artifactRetentionDays') ?? '30';
|
||||
}
|
||||
static get artifactCustomTypes() {
|
||||
return Input.getInput('artifactCustomTypes') ?? '';
|
||||
}
|
||||
static get skipActivation() {
|
||||
return Input.getInput('skipActivation')?.toLowerCase() ?? 'false';
|
||||
}
|
||||
@@ -9632,6 +9698,504 @@ class ContainerHookService {
|
||||
exports.ContainerHookService = ContainerHookService;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 49063:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.ArtifactUploadHandler = void 0;
|
||||
const node_fs_1 = __importDefault(__nccwpck_require__(87561));
|
||||
const node_path_1 = __importDefault(__nccwpck_require__(49411));
|
||||
const exec_1 = __nccwpck_require__(71514);
|
||||
const orchestrator_logger_1 = __importDefault(__nccwpck_require__(32549));
|
||||
/**
|
||||
* GitHub Artifacts size limit per artifact (10 GB).
|
||||
* Files larger than this must be split.
|
||||
*/
|
||||
const GITHUB_ARTIFACT_SIZE_LIMIT = 10 * 1024 * 1024 * 1024;
|
||||
/**
|
||||
* Handles uploading build artifacts to various targets.
|
||||
*/
|
||||
class ArtifactUploadHandler {
|
||||
/**
|
||||
* Upload artifacts described by a manifest to the configured target.
|
||||
*/
|
||||
static async uploadArtifacts(manifest, config, projectPath) {
|
||||
const startTime = Date.now();
|
||||
const result = {
|
||||
success: true,
|
||||
entries: [],
|
||||
totalBytes: 0,
|
||||
durationMs: 0,
|
||||
};
|
||||
if (config.target === 'none') {
|
||||
orchestrator_logger_1.default.log('[ArtifactUpload] Upload target is "none", skipping upload');
|
||||
result.durationMs = Date.now() - startTime;
|
||||
return result;
|
||||
}
|
||||
if (manifest.outputs.length === 0) {
|
||||
orchestrator_logger_1.default.log('[ArtifactUpload] No outputs in manifest, nothing to upload');
|
||||
result.durationMs = Date.now() - startTime;
|
||||
return result;
|
||||
}
|
||||
orchestrator_logger_1.default.log(`[ArtifactUpload] Uploading ${manifest.outputs.length} output(s) to ${config.target}`);
|
||||
for (const entry of manifest.outputs) {
|
||||
const entryResult = await ArtifactUploadHandler.uploadEntry(entry, config, projectPath);
|
||||
result.entries.push(entryResult);
|
||||
result.totalBytes += entryResult.bytes;
|
||||
if (!entryResult.success) {
|
||||
result.success = false;
|
||||
}
|
||||
}
|
||||
result.durationMs = Date.now() - startTime;
|
||||
orchestrator_logger_1.default.log(`[ArtifactUpload] Upload complete: ${result.entries.filter((e) => e.success).length}/${result.entries.length} succeeded, ${result.totalBytes} bytes, ${result.durationMs}ms`);
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Upload a single output entry.
|
||||
*/
|
||||
static async uploadEntry(entry, config, projectPath) {
|
||||
const entryResult = {
|
||||
type: entry.type,
|
||||
path: entry.path,
|
||||
success: false,
|
||||
bytes: entry.size || 0,
|
||||
};
|
||||
const resolvedPath = node_path_1.default.resolve(projectPath, entry.path.replace('{platform}', process.env.BUILD_TARGET || 'Unknown'));
|
||||
if (!node_fs_1.default.existsSync(resolvedPath)) {
|
||||
entryResult.error = `Output path does not exist: ${resolvedPath}`;
|
||||
orchestrator_logger_1.default.logWarning(`[ArtifactUpload] ${entryResult.error}`);
|
||||
return entryResult;
|
||||
}
|
||||
try {
|
||||
switch (config.target) {
|
||||
case 'github-artifacts':
|
||||
await ArtifactUploadHandler.uploadToGitHubArtifacts(entry, resolvedPath, config);
|
||||
break;
|
||||
case 'storage':
|
||||
await ArtifactUploadHandler.uploadToStorage(entry, resolvedPath, config);
|
||||
break;
|
||||
case 'local':
|
||||
await ArtifactUploadHandler.uploadToLocal(entry, resolvedPath, config);
|
||||
break;
|
||||
}
|
||||
entryResult.success = true;
|
||||
orchestrator_logger_1.default.log(`[ArtifactUpload] Uploaded '${entry.type}' (${entryResult.bytes} bytes) to ${config.target}`);
|
||||
}
|
||||
catch (error) {
|
||||
entryResult.error = error.message || String(error);
|
||||
orchestrator_logger_1.default.logWarning(`[ArtifactUpload] Failed to upload '${entry.type}': ${entryResult.error}`);
|
||||
}
|
||||
return entryResult;
|
||||
}
|
||||
/**
|
||||
* Upload to GitHub Artifacts via @actions/artifact.
|
||||
* Handles large file splitting if artifacts exceed the size limit.
|
||||
*/
|
||||
static async uploadToGitHubArtifacts(entry, resolvedPath, config) {
|
||||
// Dynamically require @actions/artifact — it may not be available in all environments.
|
||||
// Using a variable to prevent TypeScript from resolving the module at compile time.
|
||||
let artifact;
|
||||
try {
|
||||
const artifactModule = '@actions/artifact';
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
artifact = __nccwpck_require__(89346);
|
||||
}
|
||||
catch {
|
||||
throw new Error('@actions/artifact package is not available. Install it to use github-artifacts upload target.');
|
||||
}
|
||||
const artifactClient = artifact.DefaultArtifactClient
|
||||
? new artifact.DefaultArtifactClient()
|
||||
: artifact.default
|
||||
? new artifact.default()
|
||||
: artifact;
|
||||
const files = ArtifactUploadHandler.collectFiles(resolvedPath);
|
||||
if (files.length === 0) {
|
||||
orchestrator_logger_1.default.logWarning(`[ArtifactUpload] No files found at ${resolvedPath} for '${entry.type}'`);
|
||||
return;
|
||||
}
|
||||
const totalSize = entry.size || 0;
|
||||
const artifactName = `unity-output-${entry.type}`;
|
||||
if (totalSize > GITHUB_ARTIFACT_SIZE_LIMIT) {
|
||||
orchestrator_logger_1.default.log(`[ArtifactUpload] Output '${entry.type}' exceeds GitHub Artifacts size limit (${totalSize} > ${GITHUB_ARTIFACT_SIZE_LIMIT}), splitting into chunks`);
|
||||
await ArtifactUploadHandler.uploadChunked(artifactClient, artifactName, files, resolvedPath, config);
|
||||
}
|
||||
else {
|
||||
const rootDirectory = node_fs_1.default.statSync(resolvedPath).isDirectory() ? resolvedPath : node_path_1.default.dirname(resolvedPath);
|
||||
if (typeof artifactClient.uploadArtifact === 'function') {
|
||||
await artifactClient.uploadArtifact(artifactName, files, rootDirectory, {
|
||||
retentionDays: config.retentionDays,
|
||||
compressionLevel: config.compression === 'none' ? 0 : 6,
|
||||
});
|
||||
}
|
||||
else {
|
||||
throw new Error('@actions/artifact client does not have uploadArtifact method. Ensure the package version is compatible.');
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Upload large artifacts in chunks to stay within GitHub size limits.
|
||||
*/
|
||||
static async uploadChunked(artifactClient, baseName, files, rootDirectory, config) {
|
||||
const chunkSize = GITHUB_ARTIFACT_SIZE_LIMIT;
|
||||
let currentChunkFiles = [];
|
||||
let currentChunkSize = 0;
|
||||
let chunkIndex = 0;
|
||||
for (const filePath of files) {
|
||||
const fileSize = node_fs_1.default.statSync(filePath).size;
|
||||
if (currentChunkSize + fileSize > chunkSize && currentChunkFiles.length > 0) {
|
||||
await ArtifactUploadHandler.uploadSingleChunk(artifactClient, `${baseName}-part${chunkIndex}`, currentChunkFiles, rootDirectory, config);
|
||||
chunkIndex++;
|
||||
currentChunkFiles = [];
|
||||
currentChunkSize = 0;
|
||||
}
|
||||
currentChunkFiles.push(filePath);
|
||||
currentChunkSize += fileSize;
|
||||
}
|
||||
if (currentChunkFiles.length > 0) {
|
||||
await ArtifactUploadHandler.uploadSingleChunk(artifactClient, chunkIndex > 0 ? `${baseName}-part${chunkIndex}` : baseName, currentChunkFiles, rootDirectory, config);
|
||||
}
|
||||
}
|
||||
static async uploadSingleChunk(artifactClient, name, files, rootDirectory, config) {
|
||||
orchestrator_logger_1.default.log(`[ArtifactUpload] Uploading chunk '${name}' with ${files.length} file(s)`);
|
||||
if (typeof artifactClient.uploadArtifact === 'function') {
|
||||
await artifactClient.uploadArtifact(name, files, rootDirectory, {
|
||||
retentionDays: config.retentionDays,
|
||||
compressionLevel: config.compression === 'none' ? 0 : 6,
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Upload to remote storage via rclone.
|
||||
*/
|
||||
static async uploadToStorage(entry, resolvedPath, config) {
|
||||
if (!config.destination) {
|
||||
throw new Error('Storage upload requires a destination URI in artifactUploadPath');
|
||||
}
|
||||
const destination = `${config.destination}/${entry.type}`;
|
||||
orchestrator_logger_1.default.log(`[ArtifactUpload] Uploading '${entry.type}' to storage: ${destination}`);
|
||||
const args = ['copy', resolvedPath, destination, '--progress'];
|
||||
if (config.compression !== 'none') {
|
||||
// rclone doesn't have built-in compression flags for copy;
|
||||
// compression is typically handled by the remote configuration.
|
||||
// Log as informational.
|
||||
orchestrator_logger_1.default.log(`[ArtifactUpload] Note: compression '${config.compression}' is configured at the remote level for rclone`);
|
||||
}
|
||||
await (0, exec_1.exec)('rclone', args);
|
||||
}
|
||||
/**
|
||||
* Upload to a local path (copy).
|
||||
*/
|
||||
static async uploadToLocal(entry, resolvedPath, config) {
|
||||
if (!config.destination) {
|
||||
throw new Error('Local upload requires a destination path in artifactUploadPath');
|
||||
}
|
||||
const destination = node_path_1.default.join(config.destination, entry.type);
|
||||
node_fs_1.default.mkdirSync(destination, { recursive: true });
|
||||
orchestrator_logger_1.default.log(`[ArtifactUpload] Copying '${entry.type}' to local path: ${destination}`);
|
||||
ArtifactUploadHandler.copyRecursive(resolvedPath, destination);
|
||||
}
|
||||
/**
|
||||
* Recursively copy files from source to destination.
|
||||
*/
|
||||
static copyRecursive(source, destination) {
|
||||
const stat = node_fs_1.default.statSync(source);
|
||||
if (stat.isDirectory()) {
|
||||
node_fs_1.default.mkdirSync(destination, { recursive: true });
|
||||
const entries = node_fs_1.default.readdirSync(source);
|
||||
for (const entry of entries) {
|
||||
ArtifactUploadHandler.copyRecursive(node_path_1.default.join(source, entry), node_path_1.default.join(destination, entry));
|
||||
}
|
||||
}
|
||||
else {
|
||||
node_fs_1.default.copyFileSync(source, destination);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Collect all files at a given path (recursively if directory).
|
||||
*/
|
||||
static collectFiles(targetPath) {
|
||||
const stat = node_fs_1.default.statSync(targetPath);
|
||||
if (!stat.isDirectory()) {
|
||||
return [targetPath];
|
||||
}
|
||||
const files = [];
|
||||
const entries = node_fs_1.default.readdirSync(targetPath, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const fullPath = node_path_1.default.join(targetPath, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
files.push(...ArtifactUploadHandler.collectFiles(fullPath));
|
||||
}
|
||||
else {
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
/**
|
||||
* Parse an ArtifactUploadConfig from action inputs.
|
||||
*/
|
||||
static parseConfig(target, destination, compression, retentionDays) {
|
||||
const validTargets = ['github-artifacts', 'storage', 'local', 'none'];
|
||||
const resolvedTarget = validTargets.includes(target)
|
||||
? target
|
||||
: 'github-artifacts';
|
||||
const validCompressions = ['none', 'gzip', 'lz4'];
|
||||
const resolvedCompression = validCompressions.includes(compression)
|
||||
? compression
|
||||
: 'gzip';
|
||||
const parsedRetention = Number.parseInt(retentionDays, 10);
|
||||
const resolvedRetention = Number.isNaN(parsedRetention) || parsedRetention <= 0 ? 30 : parsedRetention;
|
||||
return {
|
||||
target: resolvedTarget,
|
||||
destination: destination || undefined,
|
||||
compression: resolvedCompression,
|
||||
retentionDays: resolvedRetention,
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.ArtifactUploadHandler = ArtifactUploadHandler;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 18795:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.OutputService = void 0;
|
||||
const node_fs_1 = __importDefault(__nccwpck_require__(87561));
|
||||
const node_path_1 = __importDefault(__nccwpck_require__(49411));
|
||||
const orchestrator_logger_1 = __importDefault(__nccwpck_require__(32549));
|
||||
const output_type_registry_1 = __nccwpck_require__(58012);
|
||||
/**
|
||||
* Service for collecting, manifesting, and managing build outputs.
|
||||
*
|
||||
* After a build completes, this service scans declared output paths,
|
||||
* generates a structured manifest, and prepares outputs for post-processing.
|
||||
*/
|
||||
class OutputService {
|
||||
/**
|
||||
* Collect outputs from the workspace and generate a manifest.
|
||||
*
|
||||
* @param projectPath - Path to the Unity project root
|
||||
* @param buildGuid - Unique build identifier
|
||||
* @param outputTypesInput - Comma-separated output type names
|
||||
* @param manifestPath - Where to write the manifest JSON (optional)
|
||||
* @returns The generated output manifest
|
||||
*/
|
||||
static async collectOutputs(projectPath, buildGuid, outputTypesInput, manifestPath) {
|
||||
const types = output_type_registry_1.OutputTypeRegistry.parseOutputTypes(outputTypesInput);
|
||||
const manifest = {
|
||||
buildGuid,
|
||||
timestamp: new Date().toISOString(),
|
||||
outputs: [],
|
||||
};
|
||||
if (types.length === 0) {
|
||||
orchestrator_logger_1.default.log('[Output] No output types declared, skipping collection');
|
||||
return manifest;
|
||||
}
|
||||
orchestrator_logger_1.default.log(`[Output] Collecting ${types.length} output type(s): ${types.map((t) => t.name).join(', ')}`);
|
||||
for (const typeDef of types) {
|
||||
const outputPath = node_path_1.default.join(projectPath, typeDef.defaultPath.replace('{platform}', process.env.BUILD_TARGET || 'Unknown'));
|
||||
if (!node_fs_1.default.existsSync(outputPath)) {
|
||||
orchestrator_logger_1.default.log(`[Output] No output found for '${typeDef.name}' at ${outputPath}`);
|
||||
continue;
|
||||
}
|
||||
const entry = {
|
||||
type: typeDef.name,
|
||||
path: typeDef.defaultPath,
|
||||
};
|
||||
// Collect file listing for directory outputs
|
||||
try {
|
||||
const stat = node_fs_1.default.statSync(outputPath);
|
||||
if (stat.isDirectory()) {
|
||||
entry.files = node_fs_1.default.readdirSync(outputPath);
|
||||
entry.size = OutputService.getDirectorySize(outputPath);
|
||||
}
|
||||
else {
|
||||
entry.size = stat.size;
|
||||
}
|
||||
}
|
||||
catch {
|
||||
orchestrator_logger_1.default.logWarning(`[Output] Failed to stat output '${typeDef.name}' at ${outputPath}`);
|
||||
}
|
||||
manifest.outputs.push(entry);
|
||||
orchestrator_logger_1.default.log(`[Output] Collected '${typeDef.name}': ${entry.files?.length || 1} file(s), ${entry.size || 0} bytes`);
|
||||
}
|
||||
// Write manifest to disk
|
||||
if (manifestPath) {
|
||||
try {
|
||||
const manifestDir = node_path_1.default.dirname(manifestPath);
|
||||
node_fs_1.default.mkdirSync(manifestDir, { recursive: true });
|
||||
node_fs_1.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
|
||||
orchestrator_logger_1.default.log(`[Output] Manifest written to ${manifestPath}`);
|
||||
}
|
||||
catch (error) {
|
||||
orchestrator_logger_1.default.logWarning(`[Output] Failed to write manifest: ${error.message}`);
|
||||
}
|
||||
}
|
||||
return manifest;
|
||||
}
|
||||
/**
|
||||
* Calculate total size of a directory recursively.
|
||||
*/
|
||||
static getDirectorySize(dirPath) {
|
||||
let totalSize = 0;
|
||||
try {
|
||||
const entries = node_fs_1.default.readdirSync(dirPath, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const fullPath = node_path_1.default.join(dirPath, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
totalSize += OutputService.getDirectorySize(fullPath);
|
||||
}
|
||||
else {
|
||||
totalSize += node_fs_1.default.statSync(fullPath).size;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// Ignore errors in size calculation
|
||||
}
|
||||
return totalSize;
|
||||
}
|
||||
}
|
||||
exports.OutputService = OutputService;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 58012:
|
||||
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||
exports.OutputTypeRegistry = void 0;
|
||||
const orchestrator_logger_1 = __importDefault(__nccwpck_require__(32549));
|
||||
class OutputTypeRegistry {
|
||||
/**
|
||||
* Get a type definition by name. Checks custom types first, then built-in.
|
||||
*/
|
||||
static getType(name) {
|
||||
return OutputTypeRegistry.customTypes[name] || OutputTypeRegistry.builtInTypes[name];
|
||||
}
|
||||
/**
|
||||
* Get all registered types (built-in + custom).
|
||||
*/
|
||||
static getAllTypes() {
|
||||
return [
|
||||
...Object.values(OutputTypeRegistry.builtInTypes),
|
||||
...Object.values(OutputTypeRegistry.customTypes),
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Register a custom output type.
|
||||
*/
|
||||
static registerType(definition) {
|
||||
if (OutputTypeRegistry.builtInTypes[definition.name]) {
|
||||
orchestrator_logger_1.default.logWarning(`[OutputTypes] Cannot override built-in type '${definition.name}'`);
|
||||
return;
|
||||
}
|
||||
OutputTypeRegistry.customTypes[definition.name] = { ...definition, builtIn: false };
|
||||
orchestrator_logger_1.default.log(`[OutputTypes] Registered custom type '${definition.name}'`);
|
||||
}
|
||||
/**
|
||||
* Parse a comma-separated output types string into type definitions.
|
||||
* Unknown types are logged as warnings and skipped.
|
||||
*/
|
||||
static parseOutputTypes(outputTypesInput) {
|
||||
if (!outputTypesInput) {
|
||||
return [];
|
||||
}
|
||||
const names = outputTypesInput.split(',').map((s) => s.trim()).filter(Boolean);
|
||||
const types = [];
|
||||
for (const name of names) {
|
||||
const typeDef = OutputTypeRegistry.getType(name);
|
||||
if (typeDef) {
|
||||
types.push(typeDef);
|
||||
}
|
||||
else {
|
||||
orchestrator_logger_1.default.logWarning(`[OutputTypes] Unknown output type '${name}', skipping`);
|
||||
}
|
||||
}
|
||||
return types;
|
||||
}
|
||||
/**
|
||||
* Reset custom types (for testing).
|
||||
*/
|
||||
static resetCustomTypes() {
|
||||
OutputTypeRegistry.customTypes = {};
|
||||
}
|
||||
}
|
||||
exports.OutputTypeRegistry = OutputTypeRegistry;
|
||||
OutputTypeRegistry.builtInTypes = {
|
||||
build: {
|
||||
name: 'build',
|
||||
defaultPath: './Builds/{platform}/',
|
||||
description: 'Standard game build artifact',
|
||||
builtIn: true,
|
||||
},
|
||||
'test-results': {
|
||||
name: 'test-results',
|
||||
defaultPath: './TestResults/',
|
||||
description: 'NUnit/JUnit XML test results',
|
||||
builtIn: true,
|
||||
},
|
||||
'server-build': {
|
||||
name: 'server-build',
|
||||
defaultPath: './Builds/{platform}-server/',
|
||||
description: 'Dedicated server build artifact',
|
||||
builtIn: true,
|
||||
},
|
||||
'data-export': {
|
||||
name: 'data-export',
|
||||
defaultPath: './Exports/',
|
||||
description: 'Exported data files (CSV, JSON, binary)',
|
||||
builtIn: true,
|
||||
},
|
||||
images: {
|
||||
name: 'images',
|
||||
defaultPath: './Captures/',
|
||||
description: 'Screenshots, render captures, atlas previews',
|
||||
builtIn: true,
|
||||
},
|
||||
logs: {
|
||||
name: 'logs',
|
||||
defaultPath: './Logs/',
|
||||
description: 'Structured build and test logs',
|
||||
builtIn: true,
|
||||
},
|
||||
metrics: {
|
||||
name: 'metrics',
|
||||
defaultPath: './Metrics/',
|
||||
description: 'Build performance metrics and asset statistics',
|
||||
builtIn: true,
|
||||
},
|
||||
coverage: {
|
||||
name: 'coverage',
|
||||
defaultPath: './Coverage/',
|
||||
description: 'Code coverage reports',
|
||||
builtIn: true,
|
||||
},
|
||||
};
|
||||
OutputTypeRegistry.customTypes = {};
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 23451:
|
||||
@@ -358535,6 +359099,14 @@ try {
|
||||
} catch (er) {}
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 89346:
|
||||
/***/ ((module) => {
|
||||
|
||||
module.exports = eval("require")("@actions/artifact");
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ 71269:
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user