From b4a8bcb077729e2b3fe972e7871489aa62cabb7c Mon Sep 17 00:00:00 2001 From: ceynri Date: Fri, 2 Oct 2026 16:42:39 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=94=B6=E7=B4=A7=20cookie=20=E6=9D=83?= =?UTF-8?q?=E9=99=90=E5=B9=B6=E6=A0=A1=E9=AA=8C=20--limit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cookie 文件改为 0600,缓存目录 0700。找不到浏览器时给出安装提示。 list/search 的 --limit 必须是正整数,非法值在联网前报错。 --- CHANGELOG.md | 9 +++++++++ src/auth.ts | 23 +++++++++++++++++------ src/commands/list.ts | 4 ++-- src/commands/search.ts | 4 ++-- src/commands/shared.ts | 10 ++++++++++ 5 files changed, 40 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c5aae2..fc223fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ ## [Unreleased] [[compare]](https://github.com/ceynri/mi-note-cli/compare/v0.3.1...HEAD) +### Changed + +- `--limit` 必须是正整数,非法值直接报错 +- 找不到可用浏览器时提示安装 Chrome 或运行 `npx playwright install chromium` + +### Security + +- cookie 缓存文件权限收紧为仅当前用户可读写 + ### Internal - 新增 GitHub Actions CI(类型检查、单测、构建) diff --git a/src/auth.ts b/src/auth.ts index f055fca..aa5f41b 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -1,4 +1,4 @@ -import { readFile, writeFile, rm } from "node:fs/promises"; +import { readFile, writeFile, rm, chmod } from "node:fs/promises"; import { join } from "node:path"; import { getCacheDir, fileExists, ensureDir } from "./utils.js"; import type { AuthInfo } from "./types.js"; @@ -133,10 +133,18 @@ async function launchPersistentBrowser(headless: boolean): Promise< if (!headless) { console.error("⚠️ 未检测到系统 Chrome,回退到 Playwright 自带 Chromium"); } - return await chromium.launchPersistentContext(BROWSER_DATA_DIR, { - ...launchOptions, - channel: "chromium", - }); + try { + return await chromium.launchPersistentContext(BROWSER_DATA_DIR, { + ...launchOptions, + channel: "chromium", + }); + } catch (err) { + throw new Error( + "无法启动浏览器:未检测到系统 Chrome,也没有 Playwright 自带的 Chromium。" + + "请安装 Google Chrome,或运行 `npx playwright install chromium` 后重试。" + + `\n 原始错误:${(err as Error).message.split("\n")[0]}`, + ); + } } } @@ -281,5 +289,8 @@ async function loadCachedCookie(file: string): Promise { async function saveCookie(cookie: string): Promise { await ensureDir(getCacheDir()); - await writeFile(COOKIE_FILE, cookie, "utf-8"); + await chmod(getCacheDir(), 0o700); + await writeFile(COOKIE_FILE, cookie, { encoding: "utf-8", mode: 0o600 }); + // mode 只对新建文件生效,已存在的旧文件需显式收紧 + await chmod(COOKIE_FILE, 0o600); } diff --git a/src/commands/list.ts b/src/commands/list.ts index 28f6c5f..2b99d87 100644 --- a/src/commands/list.ts +++ b/src/commands/list.ts @@ -1,4 +1,4 @@ -import { getClient } from "./shared.js"; +import { getClient, parseLimit } from "./shared.js"; import { deriveTitle, xmlToMarkdown, truncateDisplay } from "../converter.js"; import { success, logInfo, fail } from "../output.js"; import type { NoteListItem, RawNoteEntry } from "../types.js"; @@ -14,6 +14,7 @@ const TITLE_MAX_WIDTH = 60; /** 列出笔记 */ export async function listCommand(opts: ListOptions): Promise { try { + const limit = parseLimit(opts.limit, Infinity); const client = await getClient(); const { entries, folders } = await client.getAllNotes(200, (count: number) => { process.stderr.write(`\r📋 已获取 ${count} 条笔记...`); @@ -33,7 +34,6 @@ export async function listCommand(opts: ListOptions): Promise { (b.modifyDate ?? 0) - (a.modifyDate ?? 0), ); - const limit = opts.limit ? parseInt(opts.limit, 10) : filtered.length; const sliced = filtered.slice(0, limit); const items: NoteListItem[] = sliced.map((e: RawNoteEntry) => toListItem(e)); diff --git a/src/commands/search.ts b/src/commands/search.ts index c2da1f8..3845b5b 100644 --- a/src/commands/search.ts +++ b/src/commands/search.ts @@ -1,4 +1,4 @@ -import { getClient } from "./shared.js"; +import { getClient, parseLimit } from "./shared.js"; import { deriveTitle, xmlToMarkdown } from "../converter.js"; import { success, logInfo, fail } from "../output.js"; import type { NoteListItem, RawNoteEntry } from "../types.js"; @@ -13,6 +13,7 @@ export async function searchCommand( opts: SearchOptions, ): Promise { try { + const limit = parseLimit(opts.limit, 20); const client = await getClient(); const { entries } = await client.getAllNotes(200, (count: number) => { process.stderr.write(`\r🔍 已检索 ${count} 条...`); @@ -30,7 +31,6 @@ export async function searchCommand( (a: RawNoteEntry, b: RawNoteEntry) => (b.modifyDate ?? 0) - (a.modifyDate ?? 0), ); - const limit = opts.limit ? parseInt(opts.limit, 10) : 20; const sliced = matches.slice(0, limit); const items: NoteListItem[] = sliced.map((e: RawNoteEntry) => ({ diff --git a/src/commands/shared.ts b/src/commands/shared.ts index cf51380..fe21c97 100644 --- a/src/commands/shared.ts +++ b/src/commands/shared.ts @@ -46,3 +46,13 @@ export async function resolveContent(opts: { } return await readStdin(); } + +/** 解析 --limit:必须是正整数;未提供时返回 fallback */ +export function parseLimit(raw: string | undefined, fallback: number): number { + if (raw === undefined) return fallback; + const n = Number(raw); + if (!Number.isInteger(n) || n <= 0) { + throw new Error(`--limit 必须是正整数,收到:${raw}`); + } + return n; +}