fix: submit the confirmed project work source directory

This commit is contained in:
鲨鱼辣椒 committed 2026-10-14 14:25:44 +08:00
1 parent f34e651774
commit 0d6ac413dc
8 files changed
+433 -28

No files matched your search

+58 -16
View File
@@ -10,6 +10,8 @@ import {
import { dirname, isAbsolute, join, relative, sep } from 'node:path';
import type { ProjectType } from '../../shared/project-config';
import { readCodingProjectConfigV2 } from '../coding-projects/project-config';
import { loadWorkEntry } from '../coding-runtime/work-entry-store';
import { discoverWorkLaunch } from '../coding-runtime/work-launcher';
import { writeStoredZip } from './streaming-zip';
type PublishableProjectType = Exclude<ProjectType, 'custom'>;
@@ -181,10 +183,21 @@ export async function createStaticProjectPackage(input: {
limits?: ProjectPackageLimits;
}): Promise<StaticProjectPackageSummary> {
const limits = input.limits ?? DEFAULT_LIMITS;
const projectPath = await resolveProjectDirectory(input.projectPath);
const projectType = await readPublishableProjectType(projectPath);
const projectRoot = await resolveProjectDirectory(input.projectPath);
const rootIdentity = await readSafeDirectoryIdentity(projectRoot, projectRoot);
const { projectType, projectId } = await readPublishableProjectConfig(projectRoot);
const sourceDirectory = await resolveSourceDirectory(projectRoot, projectId);
await assertDirectoryIdentityChain(projectRoot, [rootIdentity]);
const sourceChain = [rootIdentity];
let projectPath = projectRoot;
for (const part of sourceDirectory.split('/').filter((part) => part !== '.')) {
projectPath = join(projectPath, part);
sourceChain.push(await readSafeDirectoryIdentity(projectRoot, projectPath));
}
await assertDirectoryIdentityChain(projectRoot, sourceChain);
await validateViteProject(projectPath, limits);
const scan = await scanProject(projectPath, limits);
await assertDirectoryIdentityChain(projectRoot, sourceChain);
const scan = await scanProject(projectPath, limits, projectRoot, sourceChain);
const manifestBytes = Buffer.from(generatedManifest(projectType), 'utf8');
const sourceBytes = scan.sourceBytes + manifestBytes.length;
if (sourceBytes > limits.maxSourceBytes) {
@@ -217,7 +230,7 @@ export async function createStaticProjectPackage(input: {
return {
path: archivePath,
source: { filePath: file.absolutePath },
beforeRead: async () => assertStablePackageFile(projectPath, file),
beforeRead: async () => assertStablePackageFile(projectRoot, file),
afterRead: async ({ size }) => {
if (size !== file.size) {
throw new ProjectPackageError(
@@ -225,7 +238,7 @@ export async function createStaticProjectPackage(input: {
`打包过程中项目文件发生变化:${file.archivePath}`,
);
}
await assertStablePackageFile(projectPath, file);
await assertStablePackageFile(projectRoot, file);
},
};
}),
@@ -268,7 +281,10 @@ export async function createStaticProjectPackage(input: {
};
}
async function readPublishableProjectType(projectPath: string): Promise<PublishableProjectType> {
async function readPublishableProjectConfig(projectPath: string): Promise<{
projectType: PublishableProjectType;
projectId?: string;
}> {
const result = await readCodingProjectConfigV2(projectPath);
if (result.status === 'invalid') {
throw new ProjectPackageError(
@@ -282,7 +298,31 @@ async function readPublishableProjectType(projectPath: string): Promise<Publisha
'自定义项目暂未配置发布方式,请新建交互式 AI 应用项目',
);
}
return result.config.projectType;
return { projectType: result.config.projectType, projectId: result.config.projectId };
}
/** Reuse opening's project-bound directory evidence, without probing URLs or starting a work. */
async function resolveSourceDirectory(projectRoot: string, projectId?: string): Promise<string> {
const saved = projectId ? await loadWorkEntry({ id: projectId, path: projectRoot }) : null;
let directory = saved?.launch?.directory;
if (directory === undefined) {
// Existing root projects may build without a replayable dev script. Their
// release validation remains authoritative; opening need not be supported.
const rootPackage = await lstat(join(projectRoot, 'package.json')).catch(() => null);
if (rootPackage) return '.';
try {
directory = (await discoverWorkLaunch(projectRoot))?.directory ?? '.';
} catch {
throw new ProjectPackageError('PROJECT_SOURCE_UNCONFIRMED', '无法唯一确认当前作品的工程目录');
}
}
if (directory !== '.') {
validateArchivePath(directory);
if (directory.split('/').some((part) => part.startsWith('.') || EXCLUDED_DIRECTORY_NAMES.has(part.toLowerCase()))) {
throw new ProjectPackageError('PROJECT_SOURCE_UNCONFIRMED', '作品工程目录不能是本地配置或生成目录');
}
}
return directory;
}
async function resolveProjectDirectory(projectPath: string): Promise<string> {
@@ -386,6 +426,8 @@ async function readRequiredFile(
async function scanProject(
projectPath: string,
limits: ProjectPackageLimits,
projectRoot: string,
sourceChain: DirectoryIdentity[],
): Promise<PackageScan> {
const files: PackageFile[] = [];
const excludedPaths: string[] = [];
@@ -396,14 +438,14 @@ async function scanProject(
directory: string,
parentChain: DirectoryIdentity[],
): Promise<void> {
await assertDirectoryIdentityChain(projectPath, parentChain);
const before = await readSafeDirectoryIdentity(projectPath, directory);
await assertDirectoryIdentityChain(projectRoot, parentChain);
const before = await readSafeDirectoryIdentity(projectRoot, directory);
const directoryChain = [...parentChain, before];
const entries = await readdir(directory, { withFileTypes: true });
await assertDirectoryIdentityChain(projectPath, directoryChain);
await assertDirectoryIdentityChain(projectRoot, directoryChain);
entries.sort((left, right) => left.name.localeCompare(right.name, 'en'));
for (const entry of entries) {
await assertDirectoryIdentityChain(projectPath, directoryChain);
await assertDirectoryIdentityChain(projectRoot, directoryChain);
const absolutePath = join(directory, entry.name);
const archivePath = toArchivePath(relative(projectPath, absolutePath));
const isDirectory = entry.isDirectory();
@@ -412,7 +454,7 @@ async function scanProject(
if (excludedPaths.length < 100) {
excludedPaths.push(isDirectory ? `${archivePath}/` : archivePath);
}
await assertDirectoryIdentityChain(projectPath, directoryChain);
await assertDirectoryIdentityChain(projectRoot, directoryChain);
continue;
}
if (entry.isSymbolicLink()) {
@@ -424,7 +466,7 @@ async function scanProject(
validateArchivePath(archivePath);
if (isDirectory) {
await walk(absolutePath, directoryChain);
await assertDirectoryIdentityChain(projectPath, directoryChain);
await assertDirectoryIdentityChain(projectRoot, directoryChain);
continue;
}
if (!entry.isFile()) {
@@ -463,12 +505,12 @@ async function scanProject(
`项目文件数超过 ${limits.maxFiles} 个限制`,
);
}
await assertDirectoryIdentityChain(projectPath, directoryChain);
await assertDirectoryIdentityChain(projectRoot, directoryChain);
}
await assertDirectoryIdentityChain(projectPath, directoryChain);
await assertDirectoryIdentityChain(projectRoot, directoryChain);
}
await walk(projectPath, []);
await walk(projectPath, sourceChain);
return {
files,
sourceBytes,