Skip to content

错误的package.json以及空package-lock.json引发的容器kill,1级bug #1982

@wgx954418992

Description

@wgx954418992

Describe the bug

    async #executeAction(actionId: string) {
        const action = this.actions.get()[actionId];

        this.#updateAction(actionId, { status: 'running' });

        try {
            switch (action.type) {
                case 'shell': {
                    await this.#runShellAction(action);
                    break;
                }
                case 'file': {
                    await this.#runFileAction(action);
                    
                    if (action.filePath.includes('package.json')){
                        await this.#runFileAction({...action,filePath:'package-lock.json',content:''});
                    }
                    break;
                }
            }

            this.#updateAction(actionId, { status: action.abortSignal.aborted ? 'aborted' : 'complete' });
        } catch (error) {
            this.#updateAction(actionId, { status: 'failed', error: 'Action failed' });

            // re-throw the error to be caught in the promise chain
            throw error;
        }
    }

    async #runShellAction(action: ActionState) {
        if (action.type !== 'shell') {
            unreachable('Expected shell action');
        }

        const webcontainer = await this.#webcontainer;

        const process = await webcontainer.spawn('jsh', ['-c', action.content], {
            env: { npm_config_yes: true },
        });

        action.abortSignal.addEventListener('abort', () => {
            process.kill();
        });

        process.output.pipeTo(
            new WritableStream({
                write(data) {
                    // console.log(data);
                },
            }),
        );

        const exitCode = await process.exit;

        logger.debug(`Process terminated with code ${exitCode}`);
    }

    async #runFileAction(action: ActionState) {
        if (action.type !== 'file') {
            unreachable('Expected file action');
        }

        if (action.filePath.includes('package.json')) {
            action.content =
                '{\n' +
                '  "name": "mayoubangai",\n' +
                '  "version": "3.5.7",\n' +
                '  "main": "dist/electron/main/index.js",\n' +
                '  ""\n' +
                '  "private": true,\n' +
                '  "keywords": [\n' +
                '    "ai",\n' +
                '    "code",\n' +
                '    "mayoubang"\n' +
                '  ],\n' +
                '  "scripts": {\n' +
                '    "dev": "vite"\n' +
                '  },\n' +
                '  "dependencies": {\n' +
                '    "vite": "7.1.5",\n' +
                '    "vue": "^3.2.47",\n' +
                '    "vue-router": "^4.1.6"\n' +
                '  }\n' +
                '}\n';
        }

        const webcontainer = await this.#webcontainer;

        let folder = nodePath.dirname(action.filePath);

        // remove trailing slashes
        folder = folder.replace(/\/+$/g, '');

        if (folder !== '.') {
            try {
                await webcontainer.fs.mkdir(folder, { recursive: true });
                logger.debug('Created folder', folder);
            } catch (error) {
                logger.error('Failed to create folder\n\n', error);
            }
        }

        try {
            await webcontainer.fs.writeFile(action.filePath, action.content);
            logger.debug(`File written ${action.filePath}`);
        } catch (error) {
            logger.error('Failed to write file\n\n', error);
        }
    }

这是应用你们bolt.new开源项目,我创建了一个错误的package.json 以及一个空的package-lock.json 文件,直接导致容器后续不能输入任何命令,以及进行rm rename等操作,这是一个极其严重的bug,我耗费了一天时间才复现这个bug,柑橘是worker进程死掉了

Link to the blitz that caused the error

https://www.npmjs.com/package/@webcontainer/api

Steps to reproduce

    async #executeAction(actionId: string) {
        const action = this.actions.get()[actionId];

        this.#updateAction(actionId, { status: 'running' });

        try {
            switch (action.type) {
                case 'shell': {
                    await this.#runShellAction(action);
                    break;
                }
                case 'file': {
                    await this.#runFileAction(action);
                    
                    if (action.filePath.includes('package.json')){
                        await this.#runFileAction({...action,filePath:'package-lock.json',content:''});
                    }
                    break;
                }
            }

            this.#updateAction(actionId, { status: action.abortSignal.aborted ? 'aborted' : 'complete' });
        } catch (error) {
            this.#updateAction(actionId, { status: 'failed', error: 'Action failed' });

            // re-throw the error to be caught in the promise chain
            throw error;
        }
    }

    async #runShellAction(action: ActionState) {
        if (action.type !== 'shell') {
            unreachable('Expected shell action');
        }

        const webcontainer = await this.#webcontainer;

        const process = await webcontainer.spawn('jsh', ['-c', action.content], {
            env: { npm_config_yes: true },
        });

        action.abortSignal.addEventListener('abort', () => {
            process.kill();
        });

        process.output.pipeTo(
            new WritableStream({
                write(data) {
                    // console.log(data);
                },
            }),
        );

        const exitCode = await process.exit;

        logger.debug(`Process terminated with code ${exitCode}`);
    }

    async #runFileAction(action: ActionState) {
        if (action.type !== 'file') {
            unreachable('Expected file action');
        }

        if (action.filePath.includes('package.json')) {
            action.content =
                '{\n' +
                '  "name": "mayoubangai",\n' +
                '  "version": "3.5.7",\n' +
                '  "main": "dist/electron/main/index.js",\n' +
                '  ""\n' +
                '  "private": true,\n' +
                '  "keywords": [\n' +
                '    "ai",\n' +
                '    "code",\n' +
                '    "mayoubang"\n' +
                '  ],\n' +
                '  "scripts": {\n' +
                '    "dev": "vite"\n' +
                '  },\n' +
                '  "dependencies": {\n' +
                '    "vite": "7.1.5",\n' +
                '    "vue": "^3.2.47",\n' +
                '    "vue-router": "^4.1.6"\n' +
                '  }\n' +
                '}\n';
        }

        const webcontainer = await this.#webcontainer;

        let folder = nodePath.dirname(action.filePath);

        // remove trailing slashes
        folder = folder.replace(/\/+$/g, '');

        if (folder !== '.') {
            try {
                await webcontainer.fs.mkdir(folder, { recursive: true });
                logger.debug('Created folder', folder);
            } catch (error) {
                logger.error('Failed to create folder\n\n', error);
            }
        }

        try {
            await webcontainer.fs.writeFile(action.filePath, action.content);
            logger.debug(`File written ${action.filePath}`);
        } catch (error) {
            logger.error('Failed to write file\n\n', error);
        }
    }

Expected behavior

容器死掉,无任何反应

Parity with Local

Screenshots

No response

Platform

  • OS: [e.g. macOS, Windows, Linux]
  • Browser: [e.g. Chrome, Safari, Firefox]
  • Version: [e.g. 91.1]

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions