@night-slayer18/leetcode-cli 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +53 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -454,7 +454,7 @@ var LeetCodeClient = class {
|
|
|
454
454
|
if (result.state === "SUCCESS" || result.state === "FAILURE") {
|
|
455
455
|
return schema.parse(result);
|
|
456
456
|
}
|
|
457
|
-
await new Promise((
|
|
457
|
+
await new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
458
458
|
}
|
|
459
459
|
throw new Error("Submission timeout: Result not available after 30 seconds");
|
|
460
460
|
}
|
|
@@ -1732,20 +1732,27 @@ function getLangSlugFromExtension(ext) {
|
|
|
1732
1732
|
}
|
|
1733
1733
|
|
|
1734
1734
|
// src/utils/validation.ts
|
|
1735
|
+
import { resolve, sep } from "path";
|
|
1735
1736
|
function isProblemId(input) {
|
|
1736
1737
|
return /^\d+$/.test(input);
|
|
1737
1738
|
}
|
|
1738
1739
|
function isFileName(input) {
|
|
1739
1740
|
return !input.includes("/") && !input.includes("\\") && input.includes(".");
|
|
1740
1741
|
}
|
|
1742
|
+
function isPathInsideWorkDir(filePath, workDir) {
|
|
1743
|
+
const resolvedFilePath = resolve(filePath);
|
|
1744
|
+
const resolvedWorkDir = resolve(workDir);
|
|
1745
|
+
const workDirWithSep = resolvedWorkDir.endsWith(sep) ? resolvedWorkDir : resolvedWorkDir + sep;
|
|
1746
|
+
return resolvedFilePath === resolvedWorkDir || resolvedFilePath.startsWith(workDirWithSep);
|
|
1747
|
+
}
|
|
1741
1748
|
|
|
1742
1749
|
// src/commands/test.ts
|
|
1743
1750
|
async function testCommand(fileOrId, options) {
|
|
1744
1751
|
const { authorized } = await requireAuth();
|
|
1745
1752
|
if (!authorized) return;
|
|
1746
1753
|
let filePath = fileOrId;
|
|
1754
|
+
const workDir = config.getWorkDir();
|
|
1747
1755
|
if (isProblemId(fileOrId)) {
|
|
1748
|
-
const workDir = config.getWorkDir();
|
|
1749
1756
|
const found = await findSolutionFile(workDir, fileOrId);
|
|
1750
1757
|
if (!found) {
|
|
1751
1758
|
console.log(chalk9.red(`No solution file found for problem ${fileOrId}`));
|
|
@@ -1756,7 +1763,6 @@ async function testCommand(fileOrId, options) {
|
|
|
1756
1763
|
filePath = found;
|
|
1757
1764
|
console.log(chalk9.gray(`Found: ${filePath}`));
|
|
1758
1765
|
} else if (isFileName(fileOrId)) {
|
|
1759
|
-
const workDir = config.getWorkDir();
|
|
1760
1766
|
const found = await findFileByName(workDir, fileOrId);
|
|
1761
1767
|
if (!found) {
|
|
1762
1768
|
console.log(chalk9.red(`File not found: ${fileOrId}`));
|
|
@@ -1770,6 +1776,14 @@ async function testCommand(fileOrId, options) {
|
|
|
1770
1776
|
console.log(chalk9.red(`File not found: ${filePath}`));
|
|
1771
1777
|
return;
|
|
1772
1778
|
}
|
|
1779
|
+
if (!isPathInsideWorkDir(filePath, workDir)) {
|
|
1780
|
+
console.log(chalk9.red("\u26A0\uFE0F Security Error: File path is outside the configured workspace"));
|
|
1781
|
+
console.log(chalk9.gray(`File: ${filePath}`));
|
|
1782
|
+
console.log(chalk9.gray(`Workspace: ${workDir}`));
|
|
1783
|
+
console.log(chalk9.yellow("\nFor security reasons, you can only test files from within your workspace."));
|
|
1784
|
+
console.log(chalk9.gray('Use "leetcode config workdir <path>" to change your workspace.'));
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1773
1787
|
const spinner = ora5({ text: "Reading solution file...", spinner: "dots" }).start();
|
|
1774
1788
|
try {
|
|
1775
1789
|
const fileName = basename(filePath);
|
|
@@ -1902,8 +1916,8 @@ async function submitCommand(fileOrId) {
|
|
|
1902
1916
|
const { authorized } = await requireAuth();
|
|
1903
1917
|
if (!authorized) return;
|
|
1904
1918
|
let filePath = fileOrId;
|
|
1919
|
+
const workDir = config.getWorkDir();
|
|
1905
1920
|
if (isProblemId(fileOrId)) {
|
|
1906
|
-
const workDir = config.getWorkDir();
|
|
1907
1921
|
const found = await findSolutionFile(workDir, fileOrId);
|
|
1908
1922
|
if (!found) {
|
|
1909
1923
|
console.log(chalk10.red(`No solution file found for problem ${fileOrId}`));
|
|
@@ -1914,7 +1928,6 @@ async function submitCommand(fileOrId) {
|
|
|
1914
1928
|
filePath = found;
|
|
1915
1929
|
console.log(chalk10.gray(`Found: ${filePath}`));
|
|
1916
1930
|
} else if (isFileName(fileOrId)) {
|
|
1917
|
-
const workDir = config.getWorkDir();
|
|
1918
1931
|
const found = await findFileByName(workDir, fileOrId);
|
|
1919
1932
|
if (!found) {
|
|
1920
1933
|
console.log(chalk10.red(`File not found: ${fileOrId}`));
|
|
@@ -1928,6 +1941,14 @@ async function submitCommand(fileOrId) {
|
|
|
1928
1941
|
console.log(chalk10.red(`File not found: ${filePath}`));
|
|
1929
1942
|
return;
|
|
1930
1943
|
}
|
|
1944
|
+
if (!isPathInsideWorkDir(filePath, workDir)) {
|
|
1945
|
+
console.log(chalk10.red("\u26A0\uFE0F Security Error: File path is outside the configured workspace"));
|
|
1946
|
+
console.log(chalk10.gray(`File: ${filePath}`));
|
|
1947
|
+
console.log(chalk10.gray(`Workspace: ${workDir}`));
|
|
1948
|
+
console.log(chalk10.yellow("\nFor security reasons, you can only submit files from within your workspace."));
|
|
1949
|
+
console.log(chalk10.gray('Use "leetcode config workdir <path>" to change your workspace.'));
|
|
1950
|
+
return;
|
|
1951
|
+
}
|
|
1931
1952
|
const spinner = ora6({ text: "Reading solution file...", spinner: "dots" }).start();
|
|
1932
1953
|
try {
|
|
1933
1954
|
const fileName = basename2(filePath);
|
|
@@ -2752,6 +2773,17 @@ import { existsSync as existsSync9 } from "fs";
|
|
|
2752
2773
|
import chalk20 from "chalk";
|
|
2753
2774
|
import inquirer3 from "inquirer";
|
|
2754
2775
|
import ora13 from "ora";
|
|
2776
|
+
function sanitizeRepoName(name) {
|
|
2777
|
+
return name.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/--+/g, "-");
|
|
2778
|
+
}
|
|
2779
|
+
function isValidGitUrl(url) {
|
|
2780
|
+
const httpsPattern = /^https:\/\/[\w.-]+\/[\w./-]+$/;
|
|
2781
|
+
const sshPattern = /^git@[\w.-]+:[\w./-]+$/;
|
|
2782
|
+
return httpsPattern.test(url) || sshPattern.test(url);
|
|
2783
|
+
}
|
|
2784
|
+
function escapeShellArg(arg) {
|
|
2785
|
+
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
2786
|
+
}
|
|
2755
2787
|
function isGitInstalled() {
|
|
2756
2788
|
try {
|
|
2757
2789
|
execSync("git --version", { stdio: "ignore" });
|
|
@@ -2823,7 +2855,8 @@ async function setupRemote(workDir) {
|
|
|
2823
2855
|
if (createGh) {
|
|
2824
2856
|
spinner.start("Creating GitHub repository...");
|
|
2825
2857
|
try {
|
|
2826
|
-
const
|
|
2858
|
+
const rawRepoName = workDir.split("/").pop() || "leetcode-solutions";
|
|
2859
|
+
const repoName = sanitizeRepoName(rawRepoName);
|
|
2827
2860
|
execSync(`gh repo create ${repoName} --private --source=. --remote=origin`, { cwd: workDir });
|
|
2828
2861
|
spinner.succeed("Created and linked GitHub repository");
|
|
2829
2862
|
repoUrl = getRemoteUrl(workDir) || "";
|
|
@@ -2850,6 +2883,11 @@ async function setupRemote(workDir) {
|
|
|
2850
2883
|
repoUrl = url;
|
|
2851
2884
|
}
|
|
2852
2885
|
}
|
|
2886
|
+
if (repoUrl && !isValidGitUrl(repoUrl)) {
|
|
2887
|
+
console.log(chalk20.red("Invalid repository URL format."));
|
|
2888
|
+
console.log(chalk20.gray("Expected: https://github.com/user/repo or git@github.com:user/repo"));
|
|
2889
|
+
return "";
|
|
2890
|
+
}
|
|
2853
2891
|
if (repoUrl) {
|
|
2854
2892
|
config.setRepo(repoUrl);
|
|
2855
2893
|
}
|
|
@@ -2891,7 +2929,7 @@ async function syncCommand() {
|
|
|
2891
2929
|
const count = lines.length;
|
|
2892
2930
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").substring(0, 19);
|
|
2893
2931
|
const message = `Sync: ${count} solutions - ${timestamp}`;
|
|
2894
|
-
execSync(`git commit -m
|
|
2932
|
+
execSync(`git commit -m ${escapeShellArg(message)}`, { cwd: workDir });
|
|
2895
2933
|
try {
|
|
2896
2934
|
execSync("git push -u origin main", { cwd: workDir, stdio: "ignore" });
|
|
2897
2935
|
} catch {
|
|
@@ -3811,6 +3849,13 @@ async function diffCommand(problemId, options) {
|
|
|
3811
3849
|
console.log(chalk24.red(`File not found: ${options.file}`));
|
|
3812
3850
|
return;
|
|
3813
3851
|
}
|
|
3852
|
+
if (!isPathInsideWorkDir(options.file, workDir)) {
|
|
3853
|
+
console.log(chalk24.red("\u26A0\uFE0F Security Error: File path is outside the configured workspace"));
|
|
3854
|
+
console.log(chalk24.gray(`File: ${options.file}`));
|
|
3855
|
+
console.log(chalk24.gray(`Workspace: ${workDir}`));
|
|
3856
|
+
console.log(chalk24.yellow("\nFor security reasons, you can only diff files from within your workspace."));
|
|
3857
|
+
return;
|
|
3858
|
+
}
|
|
3814
3859
|
const otherCode = await readFile6(options.file, "utf-8");
|
|
3815
3860
|
showComparison(currentCode, otherCode, "Your Solution", options.file, options.unified ?? false);
|
|
3816
3861
|
return;
|
|
@@ -3953,7 +3998,7 @@ program.configureHelp({
|
|
|
3953
3998
|
optionTerm: (option) => chalk26.yellow(option.flags),
|
|
3954
3999
|
optionDescription: (option) => chalk26.white(option.description)
|
|
3955
4000
|
});
|
|
3956
|
-
program.name("leetcode").usage("[command] [options]").description(chalk26.bold.cyan("\u{1F525} A modern LeetCode CLI built with TypeScript")).version("2.0.
|
|
4001
|
+
program.name("leetcode").usage("[command] [options]").description(chalk26.bold.cyan("\u{1F525} A modern LeetCode CLI built with TypeScript")).version("2.0.1", "-v, --version", "Output the version number").helpOption("-h, --help", "Display help for command").addHelpText("after", `
|
|
3957
4002
|
${chalk26.yellow("Examples:")}
|
|
3958
4003
|
${chalk26.cyan("$ leetcode login")} Login to LeetCode
|
|
3959
4004
|
${chalk26.cyan("$ leetcode list -d easy")} List easy problems
|