diff --git a/plugins/vite-plugin-canyon/.gitignore b/plugins/vite-plugin-canyon/.gitignore index 35060845..2385fadc 100644 --- a/plugins/vite-plugin-canyon/.gitignore +++ b/plugins/vite-plugin-canyon/.gitignore @@ -129,3 +129,7 @@ demo/ .idea/ pnpm-lock.yaml + +dist-test + +.canyon_output diff --git a/plugins/vite-plugin-canyon/feature/main.ts b/plugins/vite-plugin-canyon/feature/main.ts new file mode 100644 index 00000000..c60aeb3a --- /dev/null +++ b/plugins/vite-plugin-canyon/feature/main.ts @@ -0,0 +1,5 @@ +function add(a,b) { + return a + b +} + +console.log(add(1,2)) diff --git a/plugins/vite-plugin-canyon/package.json b/plugins/vite-plugin-canyon/package.json index b045f5ed..be55e5d9 100644 --- a/plugins/vite-plugin-canyon/package.json +++ b/plugins/vite-plugin-canyon/package.json @@ -1,6 +1,6 @@ { "name": "vite-plugin-canyon", - "version": "0.0.1-beta.1", + "version": "0.0.1-beta.2", "license": "MIT", "files": [ "dist/*" @@ -18,7 +18,8 @@ "scripts": { "dev": "unbuild --stub", "build": "unbuild", - "prepublishOnly": "npm run build" + "prepublishOnly": "npm run build", + "test": "vite build" }, "keywords": [ "vite", @@ -29,7 +30,11 @@ "canyon" ], "dependencies": { - "picocolors": "^1.0.0" + "picocolors": "^1.0.0", + "@babel/generator": "^7.26.2", + "@babel/parser": "^7.26.2", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0" }, "peerDependencies": { "vite": ">=2.9.1" @@ -37,6 +42,8 @@ "devDependencies": { "@types/node": "^20.10.6", "typescript": "^5.3.3", - "unbuild": "^2.0.0" + "unbuild": "^2.0.0", + "vite": "latest", + "vite-plugin-istanbul": "latest" } } diff --git a/plugins/vite-plugin-canyon/src/ci_providers/index.ts b/plugins/vite-plugin-canyon/src/ci_providers/index.ts new file mode 100644 index 00000000..7534d0e3 --- /dev/null +++ b/plugins/vite-plugin-canyon/src/ci_providers/index.ts @@ -0,0 +1,13 @@ +import { IProvider } from '../types' + +import * as providerGitHubactions from './provider_githubactions' +import * as providerGitLabci from './provider_gitlabci' +import * as providerVercel from './provider_vercel' + +const providerList: IProvider[] = [ + providerGitHubactions, + providerGitLabci, + providerVercel +] + +export default providerList diff --git a/plugins/vite-plugin-canyon/src/ci_providers/provider_githubactions.ts b/plugins/vite-plugin-canyon/src/ci_providers/provider_githubactions.ts new file mode 100644 index 00000000..b99c4f1a --- /dev/null +++ b/plugins/vite-plugin-canyon/src/ci_providers/provider_githubactions.ts @@ -0,0 +1,70 @@ +import { IServiceParams, UploaderEnvs, UploaderInputs } from '../types' + + +export function detect(envs: UploaderEnvs): boolean { + return Boolean(envs.GITHUB_ACTIONS) +} + +function _getBranch(inputs: UploaderInputs): string { + const { args, envs } = inputs + const branchRegex = /refs\/heads\/(.*)/ + const branchMatches = branchRegex.exec(envs.GITHUB_REF || '') + let branch + if (branchMatches) { + branch = branchMatches[1] + } + + if (envs.GITHUB_HEAD_REF && envs.GITHUB_HEAD_REF !== '') { + branch = envs.GITHUB_HEAD_REF + } + return args.branch || branch || '' +} + +function _getJob(envs: UploaderEnvs): string { + return (envs.GITHUB_WORKFLOW || '') +} + +function _getService(): string { + return 'github-actions' +} + +export function getServiceName(): string { + return 'GitHub Actions' +} + +function _getSHA(inputs: UploaderInputs): string { + const { args, envs } = inputs + return args.sha || envs.GITHUB_SHA +} + +function _getSlug(inputs: UploaderInputs): string { + const { args, envs } = inputs + // if (args.slug !== '') return args.slug + return envs.GITHUB_REPOSITORY_ID || '' +} + +export function getServiceParams(inputs: UploaderInputs): IServiceParams { + return { + branch: _getBranch(inputs), + // build: _getBuild(inputs), + // buildURL: await _getBuildURL(inputs), + commit: _getSHA(inputs), + // job: _getJob(inputs.envs), + // pr: _getPR(inputs), + service: _getService(), + slug: _getSlug(inputs), + } +} + +export function getEnvVarNames(): string[] { + return [ + 'GITHUB_ACTION', + 'GITHUB_HEAD_REF', + 'GITHUB_REF', + 'GITHUB_REPOSITORY_ID', + 'GITHUB_RUN_ID', + 'GITHUB_SERVER_URL', + 'GITHUB_SHA', + 'GITHUB_WORKFLOW', + ] +} diff --git a/plugins/vite-plugin-canyon/src/ci_providers/provider_gitlabci.ts b/plugins/vite-plugin-canyon/src/ci_providers/provider_gitlabci.ts new file mode 100644 index 00000000..696ff23e --- /dev/null +++ b/plugins/vite-plugin-canyon/src/ci_providers/provider_gitlabci.ts @@ -0,0 +1,86 @@ +import { IServiceParams, UploaderEnvs, UploaderInputs } from '../types' + +// import { parseSlugFromRemoteAddr } from '../helpers/git' + +export function detect(envs: UploaderEnvs): boolean { + return Boolean(envs.GITLAB_CI) +} + +function _getBuild(inputs: UploaderInputs): string { + const { args, envs } = inputs + return args.build || envs.CI_BUILD_ID || envs.CI_JOB_ID || '' +} + +function _getBuildURL(): string { + return '' +} + +function _getBranch(inputs: UploaderInputs): string { + const { args, envs } = inputs + return args.branch || envs.CI_BUILD_REF_NAME || envs.CI_COMMIT_REF_NAME || '' +} + +function _getJob(): string { + return '' +} + +function _getPR(inputs: UploaderInputs): string { + const { args } = inputs + return args.pr || '' +} + +function _getService(): string { + return 'gitlab' +} + +export function getServiceName(): string { + return 'GitLab CI' +} + +function _getSHA(inputs: UploaderInputs): string { + const { args, envs } = inputs + return args.sha || envs.CI_MERGE_REQUEST_SOURCE_BRANCH_SHA || envs.CI_BUILD_REF || envs.CI_COMMIT_SHA || '' +} + +function _getSlug(inputs: UploaderInputs): string { + const { args, envs } = inputs + // if (args.slug !== '') return args.slug + // const remoteAddr = envs.CI_BUILD_REPO || envs.CI_REPOSITORY_URL || '' + // return ( + // envs.CI_PROJECT_ID + // // remoteAddr|| + // // envs.CI_PROJECT_ID || + // // parseSlugFromRemoteAddr(remoteAddr) || + // // '' + // ) + return envs.CI_PROJECT_ID +} + +export function getServiceParams(inputs: UploaderInputs): IServiceParams { + return { + branch: _getBranch(inputs), + // build: _getBuild(inputs), + // buildURL: _getBuildURL(), + commit: _getSHA(inputs), + // job: _getJob(), + // pr: _getPR(inputs), + service: _getService(), + slug: _getSlug(inputs), + } +} + +export function getEnvVarNames(): string[] { + return [ + 'CI_BUILD_ID', + 'CI_BUILD_REF', + 'CI_BUILD_REF_NAME', + 'CI_BUILD_REPO', + 'CI_COMMIT_REF_NAME', + 'CI_COMMIT_SHA', + 'CI_JOB_ID', + 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', + 'CI_PROJECT_PATH', + 'CI_REPOSITORY_URL', + 'GITLAB_CI', + ] +} diff --git a/plugins/vite-plugin-canyon/src/ci_providers/provider_vercel.ts b/plugins/vite-plugin-canyon/src/ci_providers/provider_vercel.ts new file mode 100644 index 00000000..e4a6acff --- /dev/null +++ b/plugins/vite-plugin-canyon/src/ci_providers/provider_vercel.ts @@ -0,0 +1,67 @@ +import { IServiceParams, UploaderEnvs, UploaderInputs } from '../types' + + +export function detect(envs: UploaderEnvs): boolean { + return Boolean(envs.VERCEL) +} + +function _getBranch(inputs: UploaderInputs): string { + const { args, envs } = inputs + const branchRegex = /refs\/heads\/(.*)/ + const branchMatches = branchRegex.exec(envs.VERCEL_GIT_COMMIT_REF || '') + let branch + if (branchMatches) { + branch = branchMatches[1] + } + + if (envs.VERCEL_GIT_COMMIT_REF && envs.VERCEL_GIT_COMMIT_REF !== '') { + branch = envs.VERCEL_GIT_COMMIT_REF + } + return args.branch || branch || '' +} + + +function _getService(): string { + return 'vercel' +} + +export function getServiceName(): string { + return 'Vercel' +} + +function _getSHA(inputs: UploaderInputs): string { + const { args, envs } = inputs + return args.sha || envs.VERCEL_GIT_COMMIT_SHA +} + +function _getSlug(inputs: UploaderInputs): string { + const { args, envs } = inputs + // if (args.slug !== '') return args.slug + return envs.VERCEL_GIT_REPO_ID || '' +} + +export function getServiceParams(inputs: UploaderInputs): IServiceParams { + return { + branch: _getBranch(inputs), + // build: _getBuild(inputs), + // buildURL: await _getBuildURL(inputs), + commit: _getSHA(inputs), + // job: _getJob(inputs.envs), + // pr: _getPR(inputs), + service: _getService(), + slug: _getSlug(inputs), + } +} + +export function getEnvVarNames(): string[] { + return [ + 'GITHUB_ACTION', + 'GITHUB_HEAD_REF', + 'GITHUB_REF', + 'GITHUB_REPOSITORY_ID', + 'GITHUB_RUN_ID', + 'GITHUB_SERVER_URL', + 'GITHUB_SHA', + 'GITHUB_WORKFLOW', + ] +} diff --git a/plugins/vite-plugin-canyon/src/helpers/extract-coverage-data.ts b/plugins/vite-plugin-canyon/src/helpers/extract-coverage-data.ts new file mode 100644 index 00000000..88e009d4 --- /dev/null +++ b/plugins/vite-plugin-canyon/src/helpers/extract-coverage-data.ts @@ -0,0 +1,23 @@ +export function extractCoverageData(scriptContent) { + const reg0 = /var\s+coverageData\s*=\s*({[\s\S]*?});/; + const reg1 = /var\s+(\w+)\s*=\s*function\s*\(\)\s*\{([\s\S]*?)\}\(\);/ + try { + // 可能性一 + const match0 = reg0.exec(scriptContent); + if (match0) { + const objectString = match0[1]; + return new Function('return ' + objectString)(); + } + // 可能性二 + const match1 = reg1.exec(scriptContent); + if (match1) { + const functionBody = match1[2]; + const func = new Function(functionBody + 'return coverageData;'); + const result = func(); + return result; + } + } catch (e) { + return null; + } + return null +} diff --git a/plugins/vite-plugin-canyon/src/helpers/generate-initial-coverage.ts b/plugins/vite-plugin-canyon/src/helpers/generate-initial-coverage.ts new file mode 100644 index 00000000..0404f8de --- /dev/null +++ b/plugins/vite-plugin-canyon/src/helpers/generate-initial-coverage.ts @@ -0,0 +1,19 @@ +import fs from 'fs'; +import path from 'path' +import {extractCoverageData} from "./extract-coverage-data"; + +export const generateInitialCoverage = (paramsPath) => { + const initialCoverageDataForTheCurrentFile = extractCoverageData(paramsPath) + const filePath = './.canyon_output/coverage-final.json'; + const dir = path.dirname(filePath); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, {recursive: true}); + } + // 防止返回的数据为空 + if (initialCoverageDataForTheCurrentFile && initialCoverageDataForTheCurrentFile.path) { + fs.writeFileSync(`./.canyon_output/coverage-map-${Math.random()}.json`, JSON.stringify({ + [initialCoverageDataForTheCurrentFile.path]: initialCoverageDataForTheCurrentFile + }, null, 2), 'utf-8'); + } + return initialCoverageDataForTheCurrentFile; +} diff --git a/plugins/vite-plugin-canyon/src/helpers/logger.ts b/plugins/vite-plugin-canyon/src/helpers/logger.ts new file mode 100644 index 00000000..8f7e797c --- /dev/null +++ b/plugins/vite-plugin-canyon/src/helpers/logger.ts @@ -0,0 +1,64 @@ +/** + * We really only need three log levels + * * Error + * * Info + * * Verbose + */ + +function _getTimestamp() { + return new Date().toISOString() +} + +/** + * + * @param {string} message - message to log + * @param {boolean} shouldVerbose - value of the verbose flag + * @return void + */ +export function verbose(message: string, shouldVerbose: boolean): void { + if (shouldVerbose === true) { + console.debug(`[${_getTimestamp()}] ['verbose'] ${message}`) + } +} + +/** + * + * @param {string} message - message to log + * @return void + */ +export function logError(message: string): void { + console.error(`[${_getTimestamp()}] ['error'] ${message}`) +} + +/** + * + * @param {string} message - message to log + * @return void + */ +export function info(message: string): void { + console.log(`[${_getTimestamp()}] ['info'] ${message}`) +} + +export class UploadLogger { + private static _instance: UploadLogger + logLevel = 'info' + + private constructor() { + // Intentionally empty + } + + static getInstance(): UploadLogger { + if (!UploadLogger._instance) { + UploadLogger._instance = new UploadLogger() + } + return UploadLogger._instance; + } + + static setLogLevel(level: string) { + UploadLogger.getInstance().logLevel = level + } + + static verbose(message: string) { + verbose(message, UploadLogger.getInstance().logLevel === 'verbose') + } +} diff --git a/plugins/vite-plugin-canyon/src/helpers/provider.ts b/plugins/vite-plugin-canyon/src/helpers/provider.ts new file mode 100644 index 00000000..5e2b1ee4 --- /dev/null +++ b/plugins/vite-plugin-canyon/src/helpers/provider.ts @@ -0,0 +1,62 @@ +import providers from '../ci_providers' +import { info, logError, UploadLogger } from '../helpers/logger' +import { IServiceParams, UploaderInputs } from '../types' + +export function detectProvider( + inputs: UploaderInputs, + hasToken = false, +): Partial { + const { args } = inputs + let serviceParams: Partial | undefined + + // check if we have a complete set of manual overrides (slug, SHA) + if (args.sha && (args.slug || hasToken)) { + // We have the needed args for a manual override + info(`Using manual override from args.`) + serviceParams = { + commit: args.sha, + ...(hasToken ? {} : { slug: args.slug }), + } + } else { + serviceParams = undefined + } + + // loop though all providers + try { + const serviceParams = walkProviders(inputs) + return { ...serviceParams, ...serviceParams } + } catch (error) { + // if fails, display message explaining failure, and explaining that SHA and slug need to be set as args + if (typeof serviceParams !== 'undefined') { + logError(`Error detecting repos setting using git: ${error}`) + } else { + // throw new Error( + // '\nUnable to detect SHA and slug, please specify them manually.\nSee the help for more details.', + // ) + } + } + return serviceParams +} + +export function walkProviders(inputs: UploaderInputs): IServiceParams { + for (const provider of providers) { + if (provider.detect(inputs.envs)) { + info(`Detected ${provider.getServiceName()} as the CI provider.`) + UploadLogger.verbose('-> Using the following env variables:') + for (const envVarName of provider.getEnvVarNames()) { + UploadLogger.verbose(` ${envVarName}: ${inputs.envs[envVarName]}`) + } + return provider.getServiceParams(inputs) + } + } + // 返回默认值 + return { + slug: '-', + commit: '-', + service: '-', + branch: '-', + // instrumentCwd: '-', + // branch: '-', + } + // throw new Error(`Unable to detect provider.`) +} diff --git a/plugins/vite-plugin-canyon/src/index.ts b/plugins/vite-plugin-canyon/src/index.ts index b70dcbab..93af9cf5 100644 --- a/plugins/vite-plugin-canyon/src/index.ts +++ b/plugins/vite-plugin-canyon/src/index.ts @@ -1,69 +1,69 @@ -import {Plugin, createLogger} from 'vite'; -import picocolors from 'picocolors'; +import { parse } from '@babel/parser'; +import _traverse from '@babel/traverse'; +import _generator from '@babel/generator'; -const {green} = picocolors; +import {visitorProgramExit} from "./visitor-program-exit"; +import {detectProvider} from "./helpers/provider"; -export interface canyonPluginOptions { - commitSha?: string; - projectID?: string; - compareTarget?: string; - dsn?: string; - reporter?: string; - instrumentCwd?: string; - branch?: string; -} - -// Custom extensions to include .vue files -const DEFAULT_EXTENSION = ['.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx', '.vue']; -const PLUGIN_NAME = 'vite:canyon'; +const { default: traverse } = _traverse; +const { default: generate } = _generator; function resolveFilename(id: string): string { // To remove the annoying query parameters from the filename const [filename] = id.split('?vue'); - return filename; -} - -function shouldInstrument(filename: string) { - if (filename.includes('node_modules')) { - return false; - } - return DEFAULT_EXTENSION.some(ext => filename.endsWith(ext)); -} - -function instrumentedData(args: canyonPluginOptions): string { - const canyon = { - // gitlab流水线自带 - projectID: args.projectID || process.env['CI_PROJECT_ID'] || '', - commitSha: args.commitSha || process.env['CI_COMMIT_SHA'] || '', - sha: args.commitSha || process.env['CI_COMMIT_SHA'] || '', - branch: args.branch || process.env['CI_COMMIT_BRANCH'] || process.env['CI_COMMIT_REF_NAME'] ||'', - // 自己配置 - dsn: args.dsn || process.env['DSN'] || '', - reporter: args.reporter || process.env['REPORTER'] || '', - // 可选 - compareTarget: args.compareTarget, - // 自动获取 - instrumentCwd: args.instrumentCwd || process.cwd(), - } - return `(function () {var isBrowser = typeof window!== 'undefined';var globalObj = isBrowser? window : global;return globalObj})().__canyon__ = ${JSON.stringify(canyon)}`; + return id; } -export default function canyonPlugin(opts: canyonPluginOptions = {}): Plugin { - const logger = createLogger('info', {prefix: 'vite-plugin-canyon'}); - const canyonStr = instrumentedData(opts); - // logger.warn(`${PLUGIN_NAME}> ${green(`instrumented data: ${canyonStr}`)}`); +export default function VitePluginInstrumentation(serviceParams) { return { - name: PLUGIN_NAME, + name: 'vite-plugin-instrumentation', enforce: 'post', - transform(srcCode, id, options) { - const newCode = `${canyonStr}\n${srcCode}` - const filename = resolveFilename(id); - if (shouldInstrument(filename)) { - return { - code: newCode, - map: null, - }; - } + transform(code, id) { + // 解析代码为 AST + const ast = parse(code, { + sourceType: 'module', + plugins: ['typescript'], + }); + + // 侦测流水线 + // 优先级:手动设置 > CI/CD提供商 + // hit需要打到__coverage__中,因为ui自动化测试工具部署地方不确定 + // map就不需要,写到本地时,可以侦测本地流水线变量,直接上报上来 + // 他的职责只是寻找到项目,和插桩路径 + const serviceParams = detectProvider({ + envs: process.env, + // @ts-ignore + args: { + // projectID: config.projectID, + // sha: config.sha, + // instrumentCwd: config.instrumentCwd, + // branch: config.branch, + } + }) + + // console.log(serviceParams,'serviceParams') + // 遍历和修改 AST + traverse(ast, { + Program: { + exit(path) { + // 在 Program 节点的退出时执行 + visitorProgramExit(undefined, path, { + projectID: serviceParams.slug||'-', + sha: serviceParams.commit||'-', + instrumentCwd: process.cwd(), + dsn: process.env['DSN']||'-' + }); + }, + }, + }); + + // 将修改后的 AST 转换回代码 + const output = generate(ast, {}, code); + + return { + code: output.code, + map: output.map, // 如果需要 Source Map,可以启用 + }; }, }; } diff --git a/plugins/vite-plugin-canyon/src/types.ts b/plugins/vite-plugin-canyon/src/types.ts new file mode 100644 index 00000000..20e093c5 --- /dev/null +++ b/plugins/vite-plugin-canyon/src/types.ts @@ -0,0 +1,91 @@ +// import { Dispatcher, ProxyAgent } from "undici"; + +export interface UploaderArgs { + branch?: string // Specify the branch manually + build?: string // Specify the build number manually + changelog?: string // Displays the changelog and exits + clean?: string // Move discovered coverage reports to the trash + dir?: string // Directory to search for coverage reports. + dryRun?: string // Don't upload files to Codecov + env?: string // Specify environment variables to be included with this build + feature?: string // Toggle features + file?: string | string[] // Target file(s) to upload + flags: string | string[] // Flag the upload to group coverage metrics + fullReport?: string // Specify the path to a previously uploaded Codecov report + gcov?: string // Run with gcov support + gcovArgs?: string | string[] // Extra arguments to pass to gcov + gcovIgnore?: string | string[] // Paths to ignore during gcov gathering + gcovInclude?: string | string[] // Paths to include during gcov gathering + gcovExecutable?: string // gcov executable to run. + name?: string // Custom defined name of the upload. Visible in Codecov UI + networkFilter?: string // Specify a prefix on the files listed in the network section of the Codecov report. Useful for upload-specific path fixing + networkPrefix?: string // Specify a prefix on files listed in the network section of the Codecov report. Useful to help resolve path fixing + nonZero?: string // Should errors exit with a non-zero (default: false) + parent?: string // The commit SHA of the parent for which you are uploading coverage. + pr?: string // Specify the pull request number manually + preventSymbolicLinks?: string // Specifies whether to prevent following of symoblic links + rootDir?: string // Specify the project root directory when not in a git repo + sha?: string // Specify the commit SHA manually + slug: string // Specify the slug manually + source?: string // Track wrappers of the uploader + swift?: string // Run with swift support + swiftProject?: string // Specify the swift project + tag?: string // Specify the git tag + token?: string // Codecov upload token + upstream: string // Upstream proxy to connect to + url?: string // Change the upload host (Enterprise use) + useCwd?: boolean + verbose?: string // Run with verbose logging + xcode?: string // Run with xcode support + xcodeArchivePath?: string // Specify the xcode archive path. Likely specified as the -resultBundlePath and should end in .xcresult +} + +export type UploaderEnvs = NodeJS.Dict + +export interface UploaderInputs { + envs: UploaderEnvs + args: UploaderArgs +} + +export interface IProvider { + detect: (arg0: UploaderEnvs) => boolean + getServiceName: () => string + getServiceParams: (arg0: UploaderInputs) => IServiceParams + getEnvVarNames: () => string[] +} + +export interface IServiceParams { + branch: string + // build: string + // buildURL: string + commit: string + // job: string + // pr: string | '' + service: string + slug: string + // name?: string + // tag?: string + // flags?: string + // parent?: string + // project?: string + // server_uri?: string +} + +// export interface IRequestHeaders { +// agent?: ProxyAgent +// url: URL +// options: Dispatcher.RequestOptions +// } + +export interface PostResults { + putURL: URL + resultURL: URL +} + +export interface PutResults { + status: string + resultURL: URL +} + +export type XcodeCoverageFileReport = Record +export type XcodeCoverageReport = Record diff --git a/plugins/vite-plugin-canyon/src/visitor-program-exit.ts b/plugins/vite-plugin-canyon/src/visitor-program-exit.ts new file mode 100644 index 00000000..ddcb9f23 --- /dev/null +++ b/plugins/vite-plugin-canyon/src/visitor-program-exit.ts @@ -0,0 +1,76 @@ +import {generateInitialCoverage} from "./helpers/generate-initial-coverage"; +// import _traverse from '@babel/traverse'; +import _generator from '@babel/generator'; +import t from '@babel/types'; +// const { default: traverse } = _traverse; +const { default: generate } = _generator; +// 关键参数 serviceParams ,它由 detectProvider 函数返回,手动设置的参数优先级高于 CI/CD 提供商 +export const visitorProgramExit = (api,path,serviceParams) => { +// 生成初始覆盖率数据 +// console.log(serviceParams) + generateInitialCoverage(generate(path.node).code) + if (generate(path.node).code.includes('coverageData')) { + // const t = api.types; + // 遍历 Program 中的所有节点 + path.traverse({ + VariableDeclarator(variablePath) { + // 检查是否是 coverageData + if ( + t.isIdentifier(variablePath.node.id, { name: "coverageData" }) && + t.isObjectExpression(variablePath.node.init) + ) { + // 查找插桩后的字段 + const hasInstrumentation = variablePath.node.init.properties.some((prop) => + t.isIdentifier(prop.key, { name: "_coverageSchema" }) || // 确保是已插桩的字段 + t.isIdentifier(prop.key, { name: "s" }) || + t.isIdentifier(prop.key, { name: "f" }) + ); + + if (hasInstrumentation) { + // 获取 coverageData 对象的 properties + const properties = variablePath.node.init.properties; + + // 删除 statementMap、fnMap 和 branchMap 属性 + const keysToRemove = ["statementMap", "fnMap", "branchMap","inputSourceMap"]; + + keysToRemove.forEach(key => { + const index = properties.findIndex(prop => + t.isIdentifier(prop.key, { name: key }) + ); + + if (index !== -1) { + properties.splice(index, 1); // 删除属性 + } + }); + + // 增加 sha 字段 + const shaField = t.objectProperty( + t.identifier("sha"), // 键名 + t.stringLiteral(serviceParams.sha) // 键值 + ); + properties.push(shaField); // 添加新字段 + // 增加 sha 字段 + const projectIDField = t.objectProperty( + t.identifier("projectID"), // 键名 + t.stringLiteral(serviceParams.projectID) // 键值 + ); + properties.push(projectIDField); // 添加新字段 + // 增加 sha 字段 + const instrumentCwdField = t.objectProperty( + t.identifier("instrumentCwd"), // 键名 + t.stringLiteral(serviceParams.instrumentCwd) // 键值 + ); + properties.push(instrumentCwdField); // 添加新字段 + // 增加 dsn 字段 + const dsnField = t.objectProperty( + t.identifier("dsn"), // 键名 + t.stringLiteral(serviceParams.dsn) // 键值 + ); + properties.push(dsnField); // 添加新字段 + } + } + }}) + // end + + } +} diff --git a/plugins/vite-plugin-canyon/tsconfig.json b/plugins/vite-plugin-canyon/tsconfig.json index 2a6e0a70..d3425075 100644 --- a/plugins/vite-plugin-canyon/tsconfig.json +++ b/plugins/vite-plugin-canyon/tsconfig.json @@ -1,18 +1,7 @@ { "compilerOptions": { - "target": "es2018", - "module": "commonjs", - "moduleResolution": "node", - "lib": [ "dom" ], - "rootDir": "src", - "outDir": "dist", - "sourceMap": true, - "declaration": true, - "declarationMap": true, - "strict": true, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true + "resolveJsonModule": true, + "esModuleInterop": true, }, - "include": [ "src/*" ], - "exclude": [ "**/node_modules" ] + "include": ["src"] } diff --git a/plugins/vite-plugin-canyon/vite.config.mts b/plugins/vite-plugin-canyon/vite.config.mts new file mode 100644 index 00000000..778a126f --- /dev/null +++ b/plugins/vite-plugin-canyon/vite.config.mts @@ -0,0 +1,20 @@ +import { defineConfig } from 'vite' +// import react from '@vitejs/plugin-react-swc' +import istanbul from 'vite-plugin-istanbul' +import canyonPlugin from "./src"; +// https://vitejs.dev/config/ +export default defineConfig({ + plugins:[ + istanbul({ + forceBuildInstrument: true, + }), + canyonPlugin() + ], + build: { + lib: { + entry: 'feature/main.ts', + name: 'vite-ts-lib' + }, + outDir: 'dist-test', + }, +})