fix: submit the confirmed project work source directory
This commit is contained in:
1 parent
f34e651774
commit
0d6ac413dc
8 files changed
+433
-28
No files matched your search
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user