mirror of
https://github.com/jackwener/OpenCLI.git
synced 2026-09-14 18:25:42 +08:00
80eef46b4e
* refactor: move adapters from src/clis/ to root clis/ for monorepo separation Separates CLI adapters from the core runtime to prepare for independent adapter distribution via postinstall fetch. Key changes: - Move src/clis/ → clis/ (adapters at repo root) - Change tsconfig rootDir from "src" to "." so tsc compiles both - Create root-level shim files (registry.ts, errors.ts, etc.) so adapter relative imports (../../registry.js) resolve correctly - Update build-manifest.ts, main.ts paths for new dist/src/ structure - Expand ensureUserCliCompatShims() to cover all adapter import targets (types, utils, logger, launcher, browser/*, download/*, pipeline/*) - Add scripts/fetch-adapters.js postinstall for ~/.opencli/clis/ sync - Update vitest.config.ts adapter test paths - Add package.json files field to exclude adapters from npm package Official adapter files are unconditionally overwritten on update; user-created files not in the manifest are preserved. * fix: add dist/clis/ and cli-manifest.json to npm files, harden fetch-adapters - Add dist/clis/ and dist/cli-manifest.json to package.json files field so built-in adapters and manifest ship with the npm package - Replace execSync with execFileSync to prevent command injection - Add version check to skip redundant adapter fetches - Track tmpRoot explicitly for reliable cleanup * fix: address review blockers — manifest-based updates, global-only fetch, first-run fallback 1. Manifest-based update strategy: - Read old manifest to identify previously-official files - Clean up files removed upstream (in old manifest but not new) - User-created files (never in any manifest) remain untouched 2. Only run fetch-adapters on global install (npm_config_global=true) or explicit OPENCLI_FETCH=1, preventing heavy side effects for local/dev installs 3. First-run fallback in discovery.ts: - ensureUserAdapters() checks for adapter-manifest.json - If missing and ~/.opencli/clis/ is empty, spawns fetch-adapters.js - Guarantees adapters are available even with --ignore-scripts * fix: remove OPENCLI_FETCH env var, use internal _OPENCLI_FIRST_RUN instead * feat: also support OPENCLI_FETCH=1 for explicit adapter fetch trigger * simplify: replace git clone with local copy from dist/clis/ Adapters already ship in the npm package (dist/clis/), so there's no need to clone from GitHub. Copy directly from the installed package: - Eliminates git, curl, tar dependencies - No network calls in postinstall - No timeout/offline issues - Version always matches the installed CLI - ~65 lines of clone/download code replaced by one cpSync loop
90 lines
2.9 KiB
TypeScript
90 lines
2.9 KiB
TypeScript
export interface XhsUserPageSnapshot {
|
|
noteGroups?: unknown;
|
|
pageData?: unknown;
|
|
}
|
|
|
|
export interface XhsUserNoteRow {
|
|
id: string;
|
|
title: string;
|
|
type: string;
|
|
likes: string;
|
|
cover: string;
|
|
url: string;
|
|
}
|
|
|
|
function toCleanString(value: unknown): string {
|
|
return typeof value === 'string' ? value.trim() : value == null ? '' : String(value).trim();
|
|
}
|
|
|
|
export function normalizeXhsUserId(input: string): string {
|
|
const trimmed = toCleanString(input);
|
|
const withoutQuery = trimmed.replace(/[?#].*$/, '');
|
|
const matched = withoutQuery.match(/\/user\/profile\/([a-zA-Z0-9]+)/);
|
|
if (matched?.[1]) return matched[1];
|
|
return withoutQuery.replace(/\/+$/, '').split('/').pop() ?? withoutQuery;
|
|
}
|
|
|
|
export function flattenXhsNoteGroups(noteGroups: unknown): any[] {
|
|
if (!Array.isArray(noteGroups)) return [];
|
|
|
|
const notes: any[] = [];
|
|
for (const group of noteGroups) {
|
|
if (!group) continue;
|
|
if (Array.isArray(group)) {
|
|
for (const item of group) {
|
|
if (item) notes.push(item);
|
|
}
|
|
continue;
|
|
}
|
|
notes.push(group);
|
|
}
|
|
|
|
return notes;
|
|
}
|
|
|
|
export function buildXhsNoteUrl(userId: string, noteId: string, xsecToken?: string): string {
|
|
const cleanUserId = toCleanString(userId);
|
|
const cleanNoteId = toCleanString(noteId);
|
|
if (!cleanUserId || !cleanNoteId) return '';
|
|
|
|
const url = new URL(`https://www.xiaohongshu.com/user/profile/${cleanUserId}/${cleanNoteId}`);
|
|
const cleanToken = toCleanString(xsecToken);
|
|
if (cleanToken) {
|
|
url.searchParams.set('xsec_token', cleanToken);
|
|
url.searchParams.set('xsec_source', 'pc_user');
|
|
}
|
|
return url.toString();
|
|
}
|
|
|
|
export function extractXhsUserNotes(snapshot: XhsUserPageSnapshot, fallbackUserId: string): XhsUserNoteRow[] {
|
|
const notes = flattenXhsNoteGroups(snapshot.noteGroups);
|
|
const rows: XhsUserNoteRow[] = [];
|
|
const seen = new Set<string>();
|
|
|
|
for (const entry of notes) {
|
|
const noteCard = entry?.noteCard ?? entry?.note_card ?? entry;
|
|
if (!noteCard || typeof noteCard !== 'object') continue;
|
|
|
|
const noteId = toCleanString(noteCard.noteId ?? noteCard.note_id ?? entry?.noteId ?? entry?.note_id ?? entry?.id);
|
|
if (!noteId || seen.has(noteId)) continue;
|
|
seen.add(noteId);
|
|
|
|
const userId = toCleanString(noteCard.user?.userId ?? noteCard.user?.user_id ?? fallbackUserId);
|
|
const xsecToken = toCleanString(entry?.xsecToken ?? entry?.xsec_token ?? noteCard.xsecToken ?? noteCard.xsec_token);
|
|
const likes = toCleanString(noteCard.interactInfo?.likedCount ?? noteCard.interact_info?.liked_count ?? 0) || '0';
|
|
|
|
const cover = toCleanString(noteCard.cover?.urlDefault ?? noteCard.cover?.urlPre ?? noteCard.cover?.url ?? '');
|
|
|
|
rows.push({
|
|
id: noteId,
|
|
title: toCleanString(noteCard.displayTitle ?? noteCard.display_title ?? noteCard.title),
|
|
type: toCleanString(noteCard.type),
|
|
likes,
|
|
cover,
|
|
url: buildXhsNoteUrl(userId || fallbackUserId, noteId, xsecToken),
|
|
});
|
|
}
|
|
|
|
return rows;
|
|
}
|