Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 67 additions & 45 deletions packages/upgrade-deps/dist/index.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { createRequire } from "node:module";
import * as path from "node:path";
import * as os$1 from "os";
import os, { EOL } from "os";
import * as fs from "fs";
Expand All @@ -9,6 +8,7 @@ import * as events from "events";
import { StringDecoder } from "string_decoder";
import * as child from "child_process";
import { setTimeout as setTimeout$1 } from "timers";
import * as path from "node:path";
//#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
Expand Down Expand Up @@ -16641,6 +16641,15 @@ function getBooleanInput(name, options) {
throw new TypeError(`Input does not meet YAML 1.2 "Core Schema" specification: ${name}\nSupport boolean input list: \`true | True | TRUE | false | False | FALSE\``);
}
/**
* Sets the action status to failed.
* When the action exits it will be with an exit code of 1
* @param message add error issue message
*/
function setFailed(message) {
process.exitCode = ExitCode.Failure;
error(message);
}
/**
* Adds an error issue
* @param message error issue message. Errors will be converted to string via toString()
* @param properties optional properties to add to the annotation.
Expand Down Expand Up @@ -19856,9 +19865,10 @@ var GitHelper = class {
], { cwd: this.repoPath });
}
async commit(message) {
await exec("git", ["add", "-A"], { cwd: this.repoPath });
await exec("git", [
"commit",
"-am",
"-m",
message,
"--no-verify"
], { cwd: this.repoPath });
Expand Down Expand Up @@ -19934,9 +19944,9 @@ var GithubHelper = class {
pull_number: prNumber
});
return data;
} catch (error$4) {
error(`获取PR数据失败: ${error$4}`);
throw error$4;
} catch (error$3) {
error(`获取PR数据失败: ${error$3}`);
throw error$3;
}
}
async getIssueData(issueNumber) {
Expand All @@ -19946,9 +19956,9 @@ var GithubHelper = class {
issue_number: issueNumber
});
return data;
} catch (error$6) {
error(`获取Issue数据失败: ${error$6}`);
throw error$6;
} catch (error$5) {
error(`获取Issue数据失败: ${error$5}`);
throw error$5;
}
}
async getIssueList(params) {
Expand All @@ -19958,9 +19968,9 @@ var GithubHelper = class {
...this.defaultRepoParams
});
return data.filter((item) => !item?.pull_request);
} catch (error$5) {
error(`获取Issue列表失败: ${error$5}`);
throw error$5;
} catch (error$4) {
error(`获取Issue列表失败: ${error$4}`);
throw error$4;
}
}
async closeIssue(issueNumber) {
Expand All @@ -19971,9 +19981,9 @@ var GithubHelper = class {
issue_number: issueNumber,
state: "closed"
});
} catch (error$7) {
error(`关闭Issue失败: ${error$7}`);
throw error$7;
} catch (error$6) {
error(`关闭Issue失败: ${error$6}`);
throw error$6;
}
}
async createPR(title, head, body, base = "develop") {
Expand All @@ -19992,9 +20002,9 @@ var GithubHelper = class {
body
});
return data;
} catch (error$2) {
error(`创建PR失败: ${error$2}`);
throw error$2;
} catch (error$1) {
error(`创建PR失败: ${error$1}`);
throw error$1;
}
}
async addComment(issueNumber, body) {
Expand All @@ -20009,9 +20019,9 @@ var GithubHelper = class {
body
});
return data;
} catch (error$3) {
error(`添加评论失败: ${error$3}`);
throw error$3;
} catch (error$2) {
error(`添加评论失败: ${error$2}`);
throw error$2;
}
}
async addLabels(issueNumber, labels) {
Expand All @@ -20026,9 +20036,9 @@ var GithubHelper = class {
labels
});
return data;
} catch (error$8) {
error(`添加标签失败: ${error$8}`);
throw error$8;
} catch (error$7) {
error(`添加标签失败: ${error$7}`);
throw error$7;
}
}
};
Expand All @@ -20045,11 +20055,14 @@ const PACKAGE_MANAGER_COMMANDS = {
},
npm: {
cmd: "npm",
args: ["update"]
args: ["install"]
}
};
function slugify(value) {
return value.replace(/@/g, "").replace(/[^\w.-]+/g, "-").replace(/^-+|-+$/g, "");
}
function getBranchName(deps) {
return `chore/deps/upgrade-${deps.map((d) => `${d.name.replace(/@/g, "").replace(/\//g, "-")}-${d.version}`).join("-")}`;
return `chore/deps/upgrade-${deps.map((d) => `${slugify(d.name)}-${slugify(d.version)}`).join("-")}`;
}
function getPrTitle(deps) {
return `chore: upgrade ${deps.map((d) => `${d.name} to ${d.version}`).join(", ")}`;
Expand All @@ -20058,34 +20071,42 @@ function getRepoPath(repo, targetDir) {
const base = `./${repo}`;
return targetDir ? path.join(base, targetDir) : base;
}
function parseDependencyName(spec) {
const value = spec.trim();
if (!value) throw new Error("Empty dependency name");
if ((value.startsWith("@") ? value.indexOf("@", value.indexOf("/") + 1) : value.lastIndexOf("@")) > 0) throw new Error(`Dependency versions are not supported: ${spec}. Please pass package names only.`);
return value;
}
function parseDependencyInputs(inputs) {
const deps = inputs.flatMap((input) => input.split(/\s+/)).map((item) => item.trim()).filter(Boolean).map(parseDependencyName);
if (!deps.length) throw new Error("Missing deps input");
return deps;
}
function validatePackageManager(packageManager) {
if (packageManager in PACKAGE_MANAGER_COMMANDS) return packageManager;
throw new Error(`Unsupported package-manager "${packageManager}". Supported values: npm, yarn, pnpm.`);
}
async function fetchPackageVersion(pkg) {
try {
const response = await fetch(`https://registry.npmjs.org/${pkg}/latest`);
if (!response.ok) {
error(`Failed to get ${pkg} info from npm registry, status code: ${response.status}`);
return null;
}
if (!response.ok) throw new Error(`status code: ${response.status}`);
const { version } = await response.json();
if (!version) {
error(`No version found for ${pkg}`);
return null;
}
if (!version) throw new Error("no version found");
info(`Latest version of ${pkg} is ${version}`);
return {
name: pkg,
version
};
} catch (error$1) {
error(`Error fetching ${pkg}: ${error$1}`);
return null;
} catch (error) {
throw new Error(`Failed to get ${pkg} info from npm registry: ${error instanceof Error ? error.message : String(error)}`);
}
}
async function getPkgLatestVersions(pkgNames) {
return (await Promise.all(pkgNames.map(fetchPackageVersion))).filter((r) => r !== null);
async function resolveDependencyInfos(deps) {
return Promise.all(deps.map(fetchPackageVersion));
}
async function updatePackageDependencies(packageManager, deps, repo, targetDir) {
const repoPath = getRepoPath(repo, targetDir);
const { cmd, args } = PACKAGE_MANAGER_COMMANDS[packageManager] ?? PACKAGE_MANAGER_COMMANDS.npm;
const { cmd, args } = PACKAGE_MANAGER_COMMANDS[packageManager];
await exec(cmd, [...args, ...deps], { cwd: repoPath });
}
async function createDepsPr(title, branchName, baseBranch, context) {
Expand All @@ -20097,18 +20118,17 @@ async function createDepsPr(title, branchName, baseBranch, context) {
}).createPR(title, branchName, title, baseBranch);
}
async function updateDependencies(context) {
const packageManager = getInput("package-manager") || "npm";
const packageManager = validatePackageManager(getInput("package-manager") || "npm");
const targetDir = getInput("target-dir") || "";
const customTitle = getInput("title") || "";
const deps = getMultilineInput("deps", {
const deps = parseDependencyInputs(getMultilineInput("deps", {
required: true,
trimWhitespace: true
});
}));
info(`deps: ${JSON.stringify(deps)}`);
info(`target-dir: ${targetDir || "default (repo root)"}`);
if (customTitle) info(`custom-title: ${customTitle}`);
if (!deps.length) throw new Error("Missing deps input");
const depInfos = await getPkgLatestVersions(deps);
const depInfos = await resolveDependencyInfos(deps);
info(`depInfos: ${JSON.stringify(depInfos)}`);
if (packageManager !== "npm") await exec("corepack", ["enable"]);
const gitHelper = new GitHelper({
Expand Down Expand Up @@ -20153,6 +20173,8 @@ async function main() {
}
//#endregion
//#region index.ts
main();
main().catch((error) => {
setFailed(`upgrade-deps failed: ${error instanceof Error ? error.message : String(error)}`);
});
//#endregion
export {};
5 changes: 4 additions & 1 deletion packages/upgrade-deps/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import * as core from '@actions/core'
import { main } from './main'

main()
main().catch((error) => {
core.setFailed(`upgrade-deps failed: ${error instanceof Error ? error.message : String(error)}`)
})
111 changes: 111 additions & 0 deletions packages/upgrade-deps/main.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import * as exec from '@actions/exec'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import {
fetchPackageVersion,
getBranchName,
getPrTitle,
parseDependencyInputs,
resolveDependencyInfos,
updatePackageDependencies,
validatePackageManager,
} from './main'

vi.mock('@actions/core', () => ({
endGroup: vi.fn(),
error: vi.fn(),
getBooleanInput: vi.fn(),
getInput: vi.fn(),
getMultilineInput: vi.fn(),
info: vi.fn(),
setFailed: vi.fn(),
startGroup: vi.fn(),
}))

vi.mock('@actions/exec', () => ({
exec: vi.fn(),
getExecOutput: vi.fn(),
}))

vi.mock('@actions/github', () => ({
context: {
eventName: 'workflow_dispatch',
repo: {
owner: 'Tencent',
repo: 'tdesign-vue-next',
},
},
getOctokit: vi.fn(),
}))

describe('升级依赖', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.stubGlobal('fetch', vi.fn())
})

it('解析空格和换行分隔的依赖输入', () => {
expect(parseDependencyInputs([
'@tdesign/site-components @tdesign/theme-generator',
'vite',
])).toEqual([
'@tdesign/site-components',
'@tdesign/theme-generator',
'vite',
])
})

it('拒绝带版本号的依赖输入', () => {
expect(() => parseDependencyInputs(['vite@7.0.0'])).toThrow('Dependency versions are not supported')
expect(() => parseDependencyInputs(['@tdesign/site-components@0.19.1'])).toThrow('Dependency versions are not supported')
})

it('拒绝空依赖输入', () => {
expect(() => parseDependencyInputs(['', ' '])).toThrow('Missing deps input')
})

it('拒绝不支持的 package-manager', () => {
expect(validatePackageManager('pnpm')).toBe('pnpm')
expect(() => validatePackageManager('bun')).toThrow('Unsupported package-manager "bun"')
})

it('全部依赖都查询 npm latest', async () => {
vi.mocked(fetch).mockResolvedValueOnce(new Response(JSON.stringify({ version: '0.19.1' }), { status: 200 }))
vi.mocked(fetch).mockResolvedValueOnce(new Response(JSON.stringify({ version: '7.0.0' }), { status: 200 }))

await expect(resolveDependencyInfos([
'@tdesign/site-components',
'vite',
])).resolves.toEqual([
{ name: '@tdesign/site-components', version: '0.19.1' },
{ name: 'vite', version: '7.0.0' },
])

expect(fetch).toHaveBeenCalledWith('https://registry.npmjs.org/@tdesign/site-components/latest')
})

it('npm registry 查询失败时中止流程', async () => {
vi.mocked(fetch).mockResolvedValueOnce(new Response('', { status: 404 }))

await expect(fetchPackageVersion('@tdesign/missing')).rejects.toThrow(
'Failed to get @tdesign/missing info from npm registry: status code: 404',
)
})

it('按包管理器执行升级命令', async () => {
await updatePackageDependencies('npm', ['vite'], 'tdesign-vue-next', '')
expect(exec.exec).toHaveBeenLastCalledWith('npm', ['install', 'vite'], { cwd: './tdesign-vue-next' })

await updatePackageDependencies('pnpm', ['@tdesign/site-components'], 'tdesign-vue-next', 'site')
expect(exec.exec).toHaveBeenLastCalledWith('pnpm', ['up', '--latest', '@tdesign/site-components'], { cwd: 'tdesign-vue-next/site' })
})

it('生成分支名和默认 PR 标题', () => {
const deps = [
{ name: '@tdesign/site-components', version: '0.19.1' },
{ name: 'vite', version: '^7.0.0' },
]

expect(getBranchName(deps)).toBe('chore/deps/upgrade-tdesign-site-components-0.19.1-vite-7.0.0')
expect(getPrTitle(deps)).toBe('chore: upgrade @tdesign/site-components to 0.19.1, vite to ^7.0.0')
})
})
Loading