diff --git a/packages/code-generator/package.json b/packages/code-generator/package.json index bf983b6c2..bfe83ccf4 100644 --- a/packages/code-generator/package.json +++ b/packages/code-generator/package.json @@ -22,6 +22,7 @@ "@babel/types": "^7.9.5", "@types/prettier": "^1.19.1", "change-case": "^3.1.0", + "jszip": "^3.5.0", "prettier": "^2.0.2", "short-uuid": "^3.1.1" }, diff --git a/packages/code-generator/src/index.ts b/packages/code-generator/src/index.ts index 2ef5cf28c..5d670b5ab 100644 --- a/packages/code-generator/src/index.ts +++ b/packages/code-generator/src/index.ts @@ -5,6 +5,7 @@ import { createProjectBuilder } from './generator/ProjectBuilder'; import { createModuleBuilder } from './generator/ModuleBuilder'; import { createDiskPublisher } from './publisher/disk'; +import { createZipPublisher } from './publisher/zip'; import createIceJsProjectBuilder from './solutions/icejs'; import createRecoreProjectBuilder from './solutions/recore'; @@ -57,6 +58,7 @@ export default { }, publishers: { disk: createDiskPublisher, + zip: createZipPublisher, }, plugins: { common: { diff --git a/packages/code-generator/src/publisher/disk/index.ts b/packages/code-generator/src/publisher/disk/index.ts index 77f45c07a..b63649ab9 100644 --- a/packages/code-generator/src/publisher/disk/index.ts +++ b/packages/code-generator/src/publisher/disk/index.ts @@ -1,21 +1,10 @@ -import { CodeGeneratorError, IResultDir } from '../../types'; - -export type PublisherFactory = (configuration?: Partial) => U; - -export interface IPublisher { - publish: (options?: T) => Promise>; - getProject: () => IResultDir | void; - setProject: (project: IResultDir) => void; -} - -export interface IPublisherFactoryParams { - project?: IResultDir; -} -export interface IPublisherResponse { - success: boolean; - payload?: T; -} - +import { + IResultDir, + PublisherFactory, + IPublisher, + IPublisherFactoryParams, + PublisherError, +} from '../../types'; import { writeFolder } from './utils'; export interface IDiskFactoryParams extends IPublisherFactoryParams { @@ -37,7 +26,7 @@ export const createDiskPublisher: PublisherFactory< const getProject = (): IResultDir => { if (!project) { - throw new CodeGeneratorError('Missing Project'); + throw new PublisherError('Missing Project'); } return project; }; @@ -55,7 +44,7 @@ export const createDiskPublisher: PublisherFactory< const publish = async (options: IDiskFactoryParams = {}) => { const projectToPublish = options.project || project; if (!projectToPublish) { - throw new CodeGeneratorError('Missing Project'); + throw new PublisherError('Missing Project'); } const projectOutputPath = options.outputPath || outputPath; @@ -75,7 +64,7 @@ export const createDiskPublisher: PublisherFactory< ); return { success: true, payload: projectOutputPath }; } catch (error) { - throw new CodeGeneratorError(error); + throw new PublisherError(error); } }; diff --git a/packages/code-generator/src/publisher/zip/index.ts b/packages/code-generator/src/publisher/zip/index.ts new file mode 100644 index 000000000..c400c6849 --- /dev/null +++ b/packages/code-generator/src/publisher/zip/index.ts @@ -0,0 +1,69 @@ +import { + IResultDir, + PublisherFactory, + IPublisher, + IPublisherFactoryParams, + PublisherError, +} from '../../types'; +import { isNodeProcess, writeZipToDisk, generateProjectZip } from './utils' + +// export type ZipBuffer = Buffer | Blob; +export type ZipBuffer = Buffer; + +declare type ZipPublisherResponse = string | ZipBuffer; + +export interface ZipFactoryParams extends IPublisherFactoryParams { + outputPath?: string; + projectSlug?: string; +} + +export interface ZipPublisher extends IPublisher { + getOutputPath: () => string | undefined; + setOutputPath: (path: string) => void; +} + +export const createZipPublisher: PublisherFactory = ( + params: ZipFactoryParams = {}, +): ZipPublisher => { + let { project, outputPath } = params; + + const getProject = () => project; + const setProject = (projectToSet: IResultDir) => { + project = projectToSet; + } + + const getOutputPath = () => outputPath; + const setOutputPath = (path: string) => { + outputPath = path; + } + + const publish = async (options: ZipFactoryParams = {}) => { + const projectToPublish = options.project || project; + if (!projectToPublish) { + throw new PublisherError('MissingProject'); + } + + const zipName = options.projectSlug || params.projectSlug || projectToPublish.name; + + try { + const zipContent = await generateProjectZip(projectToPublish); + + // If not output path is provided, zip is not written to disk + const projectOutputPath = options.outputPath || outputPath; + if (projectOutputPath && isNodeProcess()) { + await writeZipToDisk(projectOutputPath, zipContent, zipName); + } + return { success: true, payload: zipContent }; + } catch (error) { + throw new PublisherError(error); + } + } + + return { + publish, + getProject, + setProject, + getOutputPath, + setOutputPath, + }; +} diff --git a/packages/code-generator/src/publisher/zip/utils.ts b/packages/code-generator/src/publisher/zip/utils.ts new file mode 100644 index 000000000..b7e1100b8 --- /dev/null +++ b/packages/code-generator/src/publisher/zip/utils.ts @@ -0,0 +1,60 @@ +import JSZip from 'jszip'; +import { IResultDir, IResultFile } from '../../types'; +import { ZipBuffer } from './index'; + +export const isNodeProcess = (): boolean => { + return ( + typeof process === 'object' && + typeof process.versions === 'object' && + typeof process.versions.node !== 'undefined' + ); +} + +export const writeZipToDisk = ( + zipFolderPath: string, + content: ZipBuffer, + zipName: string, +): void => { + const fs = require('fs'); + const path = require('path'); + + if (!fs.existsSync(zipFolderPath)) { + fs.mkdirSync(zipFolderPath, { recursive: true }); + } + + const zipPath = path.join(zipFolderPath, `${zipName}.zip`); + + const writeStream = fs.createWriteStream(zipPath); + writeStream.write(content); + writeStream.end(); +} + +export const generateProjectZip = async (project: IResultDir): Promise => { + let zip = new JSZip(); + zip = writeFolderToZip(project, zip, true); + // const zipType = isNodeProcess() ? 'nodebuffer' : 'blob'; + const zipType = 'nodebuffer'; // 目前先只支持 node 调用 + return zip.generateAsync({ type: zipType }); +} + +const writeFolderToZip = ( + folder: IResultDir, + parentFolder: JSZip, + ignoreFolder: boolean = false, +) => { + const zipFolder = ignoreFolder ? parentFolder : parentFolder.folder(folder.name); + if (zipFolder !== null) { + folder.files.forEach((file: IResultFile) => { + // const options = file.contentEncoding === 'base64' ? { base64: true } : {}; + const options = {}; + const fileName = file.ext ? `${file.name}.${file.ext}` : file.name; + zipFolder.file(fileName, file.content, options); + }); + + folder.dirs.forEach((subFolder: IResultDir) => { + writeFolderToZip(subFolder, zipFolder); + }); + } + + return parentFolder; +} diff --git a/packages/code-generator/src/types/error.ts b/packages/code-generator/src/types/error.ts index b58e9fe90..8c0393a88 100644 --- a/packages/code-generator/src/types/error.ts +++ b/packages/code-generator/src/types/error.ts @@ -18,3 +18,10 @@ export class CompatibilityError extends CodeGeneratorError { super(errorString); } } + +// tslint:disable-next-line: max-classes-per-file +export class PublisherError extends CodeGeneratorError { + constructor(errorString: string) { + super(errorString); + } +} diff --git a/packages/code-generator/src/types/index.ts b/packages/code-generator/src/types/index.ts index 1938ed5f5..102515d43 100644 --- a/packages/code-generator/src/types/index.ts +++ b/packages/code-generator/src/types/index.ts @@ -4,3 +4,4 @@ export * from './error'; export * from './result'; export * from './schema'; export * from './intermediate'; +export * from './publisher'; diff --git a/packages/code-generator/src/types/publisher.ts b/packages/code-generator/src/types/publisher.ts new file mode 100644 index 000000000..a632e4d9b --- /dev/null +++ b/packages/code-generator/src/types/publisher.ts @@ -0,0 +1,19 @@ +import { + IResultDir, +} from './index'; + +export type PublisherFactory = (configuration?: Partial) => U; + +export interface IPublisher { + publish: (options?: T) => Promise>; + getProject: () => IResultDir | void; + setProject: (project: IResultDir) => void; +} + +export interface IPublisherFactoryParams { + project?: IResultDir; +} +export interface IPublisherResponse { + success: boolean; + payload?: T; +}