export const WORKS_APP_ID_PATTERN = /^[a-z0-9][a-z0-9-]{2,79}$/; export type WorksPublishFailure = { title: string; reason: string; nextStep: string; }; const PUBLISH_FAILURES: Record = { PREVIEW_REQUIRED: { title: '请先打开项目预览', reason: '提交前检查需要当前项目正在 Makelore 内置浏览器中运行。', nextStep: '先在内置浏览器中打开当前项目的本地预览,确认页面可用后再提交。', }, PUBLISH_PREFLIGHT_LOAD_FAILED: { title: '作品主页无法打开', reason: 'Makelore 使用内置浏览器检查时,主页面没有成功加载。', nextStep: '请让开发助手检查构建输出中的 index.html 和资源路径。', }, PUBLISH_PREFLIGHT_RUNTIME_ERROR: { title: '作品打开时发生错误', reason: 'Makelore 的内置浏览器检测到页面运行错误。', nextStep: '请让开发助手用项目预览修复运行错误后重新提交。', }, PUBLISH_PREFLIGHT_BLANK: { title: '作品打开后没有内容', reason: '桌面或移动视口下没有检测到可见页面内容。', nextStep: '请让开发助手检查首屏渲染、页面尺寸和移动端布局。', }, PUBLISH_PREFLIGHT_TIMEOUT: { title: '作品打开检查超时', reason: '内置浏览器没有在规定时间内完成页面检查。', nextStep: '请让开发助手检查首屏资源大小和启动逻辑。', }, PUBLISH_PREFLIGHT_UNAVAILABLE: { title: '暂时无法完成本地浏览器检查', reason: 'Makelore 的内置浏览器当前不可用。', nextStep: '重新启动 Makelore 后再提交,无需安装额外浏览器。', }, AUTH_REQUIRED: { title: '需要重新登录', reason: '当前登录状态已失效,无法提交作品。', nextStep: '重新登录 Makelore 后再提交。', }, PROJECT_NOT_FOUND: { title: '找不到项目文件夹', reason: 'Makelore 无法读取当前项目。', nextStep: '重新打开项目,确认文件夹仍然存在后再提交。', }, PROJECT_FILE_MISSING: { title: '项目文件不完整', reason: '项目模板中的发布文件被删除或损坏。', nextStep: '请让开发助手修复当前项目模板后重试。', }, PROJECT_TYPE_UNPUBLISHABLE: { title: '这个项目没有配置发布方式', reason: '自定义项目只创建工作空间,不会自动生成平台发布模板。', nextStep: '如需一键提交,请新建小游戏或小程序项目。', }, PROJECT_FILE_INVALID: { title: '项目配置无法读取', reason: 'package.json 或 package-lock.json 格式不正确。', nextStep: '请让开发助手修复项目配置文件后重试。', }, VITE_NOT_DECLARED: { title: '项目还不是可发布的 Vite 工程', reason: 'package.json 中没有找到 Vite 依赖。', nextStep: '请让开发助手补齐 Vite 配置并确认本地可以启动。', }, PACKAGE_MANAGER_UNSUPPORTED: { title: '项目缺少 npm 锁文件', reason: '当前自动提交只支持带 package-lock.json 的 npm 项目。', nextStep: '请让开发助手使用 npm 生成并提交 package-lock.json。', }, LOCKFILE_UNSUPPORTED: { title: 'npm 锁文件版本过旧', reason: '当前 package-lock.json 不能用于安全构建。', nextStep: '请让开发助手使用新版 npm 重新生成 package-lock.json。', }, INDEX_HTML_EMPTY: { title: '项目入口是空的', reason: '根目录的 index.html 没有可用内容。', nextStep: '请让开发助手修复页面入口,并确认本地可以打开。', }, PROJECT_TOO_LARGE: { title: '项目文件太大', reason: '源码总大小超过平台允许范围。', nextStep: '删除不需要的大文件和生成文件后重新提交。', }, ARCHIVE_TOO_LARGE: { title: '项目压缩包太大', reason: '安全打包后的文件仍超过平台限制。', nextStep: '移除不需要的图片、音视频或其他大文件后重试。', }, PROJECT_TOO_MANY_FILES: { title: '项目文件太多', reason: '源码文件数量超过平台允许范围。', nextStep: '清理缓存、生成文件和不再使用的素材后重试。', }, PROJECT_SYMLINK_UNSUPPORTED: { title: '项目包含不安全的链接文件', reason: '自动提交不会跟随符号链接读取其他位置的文件。', nextStep: '将需要的文件复制到项目内,并移除符号链接后重试。', }, PROJECT_FILE_UNSUPPORTED: { title: '项目包含不支持的文件', reason: '部分文件类型无法安全打包。', nextStep: '请让开发助手移除特殊文件后重试。', }, PROJECT_PATH_UNSUPPORTED: { title: '项目包含不支持的文件名', reason: '部分路径无法在云端安全解压。', nextStep: '请让开发助手重命名异常文件或文件夹后重试。', }, PROJECT_CHANGED_DURING_PACKAGING: { title: '项目正在发生变化', reason: '打包时有文件仍在写入,无法保证提交内容完整。', nextStep: '等待开发助手或其他程序保存完成后重新提交。', }, PROJECT_SUBMISSION_CONFLICT: { title: '这个作品已有版本在处理中', reason: '平台正在构建或审核上一版,暂时不能重复提交。', nextStep: '等待当前版本完成审核或被驳回后再提交。', }, PROJECT_CREATE_REJECTED: { title: '作品信息没有通过检查', reason: '平台没有接受自动生成的作品信息。', nextStep: '请联系运营人员检查作品信息。', }, PROJECT_OWNERSHIP_UNCONFIRMED: { title: '作品标识已被占用', reason: '自动生成的作品标识不属于当前账号。', nextStep: '请联系运营人员处理作品归属。', }, SOURCE_PACKAGE_REJECTED: { title: '项目没有通过平台检查', reason: '云端发现项目包不符合安全构建要求。', nextStep: '请让开发助手检查依赖、入口文件和敏感文件后重试。', }, PUBLISH_FORBIDDEN: { title: '当前账号不能发布这个作品', reason: '平台没有确认当前账号的作品权限。', nextStep: '确认登录了正确账号;如果仍然失败,请联系运营人员。', }, WORKS_SQUARE_UNAVAILABLE: { title: '发布服务暂时不可用', reason: '平台当前没有完成这次提交。', nextStep: '请稍后重新提交;自动重试不会创建重复版本。', }, BUILD_STATUS_UNAVAILABLE: { title: '暂时无法读取构建进度', reason: '作品已经提交,但 Makelore 暂时没有拿到最新状态。', nextStep: '稍后重新打开项目查看,不需要重复提交。', }, BUILD_STATUS_TIMEOUT: { title: '构建时间比平时更长', reason: '作品仍可能在云端处理中。', nextStep: '稍后重新打开项目查看,不需要重复提交。', }, DEPENDENCY_PREFETCH_FAILED: { title: '暂时无法下载项目依赖', reason: '云端没有成功准备 package-lock.json 中的依赖。', nextStep: '请检查 package-lock.json 是否已提交,并确认依赖名称和版本有效后重试。', }, DEPENDENCY_PREFETCH_TIMEOUT: { title: '下载项目依赖超时', reason: '云端准备依赖的时间超过限制。', nextStep: '稍后重试;如果持续失败,请让开发助手精简依赖。', }, BUILD_COMMAND_FAILED: { title: '项目没有构建成功', reason: '云端执行项目构建时发现代码或配置错误。', nextStep: '请让开发助手运行 npm run build,修复错误后提交新版本。', }, BUILD_TIMEOUT: { title: '项目构建超时', reason: '云端构建时间超过平台限制。', nextStep: '请让开发助手减少构建步骤或过大的依赖后重试。', }, BUILD_SANDBOX_UNAVAILABLE: { title: '云端构建环境暂时不可用', reason: '平台暂时无法启动安全构建环境。', nextStep: '稍后重新提交,无需修改项目。', }, BUILD_PIPELINE_MISMATCH: { title: '项目发布方式不匹配', reason: '当前提交不是平台支持的 Makelore 静态项目格式。', nextStep: '请从当前项目重新点击一键提交,不要手工修改发布包。', }, BUILD_PIPELINE_UNSUPPORTED: { title: '这个项目暂不支持自动发布', reason: '平台没有找到适合当前项目类型的安全构建方式。', nextStep: '请联系运营人员确认项目类型。', }, VERSION_NOT_FOUND: { title: '平台没有找到本次提交', reason: '构建服务无法读取刚刚上传的版本。', nextStep: '请重新提交;如果持续失败,请联系运营人员。', }, SOURCE_DIGEST_MISMATCH: { title: '上传内容校验失败', reason: '平台收到的项目内容与提交时不一致。', nextStep: '重新提交一次;如果持续失败,请联系运营人员。', }, OUTPUT_MISSING: { title: '没有生成可运行页面', reason: '构建完成后没有找到发布所需的页面文件。', nextStep: '请让开发助手检查 Vite 输出目录和 index.html。', }, OUTPUT_INVALID: { title: '生成的页面无法发布', reason: '构建输出缺少必要文件或包含不安全内容。', nextStep: '请让开发助手检查 Vite 构建输出后重试。', }, OUTPUT_LIMIT_EXCEEDED: { title: '生成的页面文件太大', reason: '构建结果超过平台允许范围。', nextStep: '请让开发助手压缩素材、拆分资源或删除无用输出。', }, RELEASE_STORE_FAILED: { title: '平台暂时无法保存发布文件', reason: '作品已经构建,但发布存储当前不可用。', nextStep: '稍后重新提交,无需修改项目。', }, BUILD_STALE: { title: '构建任务已中断', reason: '云端构建任务长时间没有继续运行。', nextStep: '重新提交一次;如果持续发生,请联系运营人员。', }, BROWSER_SMOKE_FAILED: { title: '自动打开作品时发现问题', reason: '云端浏览器检测到了白屏、运行错误或资源加载失败。', nextStep: '请让开发助手运行 npm run build,并在浏览器中检查构建结果后重试。', }, BROWSER_SMOKE_TIMEOUT: { title: '自动打开作品超时', reason: '作品在规定时间内没有完成加载。', nextStep: '请让开发助手检查首屏资源大小和启动逻辑,优化后重新提交。', }, BROWSER_SMOKE_UNAVAILABLE: { title: '自动验收环境暂时不可用', reason: '平台暂时无法启动用于检查作品的安全浏览器。', nextStep: '稍后重新提交,无需修改项目。', }, BUILD_CANCELLED: { title: '构建已取消', reason: '平台没有继续处理这个版本。', nextStep: '确认没有其他版本正在处理后重新提交。', }, BUILD_FAILED: { title: '项目没有构建成功', reason: '云端未能生成可审核的作品。', nextStep: '请让开发助手先确认 npm run build 成功,再提交新版本。', }, }; export function describeWorksPublishFailure( code: string | null, statusCode?: number, ): WorksPublishFailure { const knownCode = code?.trim().toUpperCase(); if (knownCode && PUBLISH_FAILURES[knownCode]) { return PUBLISH_FAILURES[knownCode]; } if (statusCode === 401) return PUBLISH_FAILURES.AUTH_REQUIRED; if (statusCode === 403) return PUBLISH_FAILURES.PUBLISH_FORBIDDEN; if (statusCode === 409) return PUBLISH_FAILURES.PROJECT_SUBMISSION_CONFLICT; if (statusCode === 413) return PUBLISH_FAILURES.ARCHIVE_TOO_LARGE; if (statusCode !== undefined && statusCode >= 500) { return PUBLISH_FAILURES.WORKS_SQUARE_UNAVAILABLE; } if (statusCode === 400 || statusCode === 422) { return PUBLISH_FAILURES.SOURCE_PACKAGE_REJECTED; } return { title: '提交没有完成', reason: 'Makelore 没有收到可确认的提交结果。', nextStep: '请稍后重新提交;如果持续失败,请联系运营人员。', }; } export function createDefaultWorksAppId(project: { id: string; name?: string }): string { const normalize = (value: string) => value .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, ''); const fromId = normalize(project.id); const fallback = `makelore-${fromId || 'project'}`.slice(0, 80).replace(/-+$/g, ''); return WORKS_APP_ID_PATTERN.test(fallback) ? fallback : 'makelore-project'; }