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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(类型检查、单测、构建)
Expand Down
23 changes: 17 additions & 6 deletions src/auth.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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]}`,
);
}
}
}

Expand Down Expand Up @@ -281,5 +289,8 @@ async function loadCachedCookie(file: string): Promise<string | null> {

async function saveCookie(cookie: string): Promise<void> {
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);
}
4 changes: 2 additions & 2 deletions src/commands/list.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -14,6 +14,7 @@ const TITLE_MAX_WIDTH = 60;
/** 列出笔记 */
export async function listCommand(opts: ListOptions): Promise<void> {
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} 条笔记...`);
Expand All @@ -33,7 +34,6 @@ export async function listCommand(opts: ListOptions): Promise<void> {
(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));
Expand Down
4 changes: 2 additions & 2 deletions src/commands/search.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -13,6 +13,7 @@ export async function searchCommand(
opts: SearchOptions,
): Promise<void> {
try {
const limit = parseLimit(opts.limit, 20);
const client = await getClient();
const { entries } = await client.getAllNotes(200, (count: number) => {
process.stderr.write(`\r🔍 已检索 ${count} 条...`);
Expand All @@ -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) => ({
Expand Down
10 changes: 10 additions & 0 deletions src/commands/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Loading