ข้ามไปยังเนื้อหา

ตัวจัดการปลั๊กอินและการติดตั้งภายใน

เอกสารนี้อธิบายว่าการทำงานของ xcsh plugin เปลี่ยนแปลงสถานะปลั๊กอินบนดิสก์อย่างไร และปลั๊กอินที่ติดตั้งแล้วกลายเป็นความสามารถขณะรันไทม์ได้อย่างไร (ปัจจุบันเป็นเครื่องมือ รวมถึงการแก้ไขเส้นทาง hooks/commands ที่พร้อมใช้งาน)

มีการนำไปใช้งานของการจัดการปลั๊กอินสองแบบในโค้ดเบส:

  1. เส้นทางที่ใช้งานโดยคำสั่ง CLI: PluginManager (src/extensibility/plugins/manager.ts)
  2. โมดูลช่วยเหลือรุ่นเก่า: ฟังก์ชัน installer (src/extensibility/plugins/installer.ts)

การเรียกใช้คำสั่ง xcsh plugin ... ผ่าน PluginManager

installer.ts ยังคงบันทึกการตรวจสอบความปลอดภัยที่สำคัญและพฤติกรรมของระบบไฟล์ แต่ไม่ใช่เส้นทางที่ใช้โดย src/commands/plugin.ts + src/cli/plugin-cli.ts

วงจรชีวิต: จากการเรียกใช้ CLI ไปสู่ความพร้อมใช้งานขณะรันไทม์

หัวข้อที่มีชื่อว่า “วงจรชีวิต: จากการเรียกใช้ CLI ไปสู่ความพร้อมใช้งานขณะรันไทม์”
xcsh plugin <action> ...
-> src/commands/plugin.ts
-> runPluginCommand(...) in src/cli/plugin-cli.ts
-> PluginManager method (install/list/uninstall/link/...)
-> mutate ~/.xcsh/plugins/{package.json,node_modules,xcsh-plugins.lock.json}
-> runtime discovery: discoverAndLoadCustomTools(...)
-> getAllPluginToolPaths(cwd)
-> custom tool loader imports tool modules
  • src/commands/plugin.ts กำหนดคำสั่ง/แฟล็ก และส่งต่อไปยัง runPluginCommand
  • src/cli/plugin-cli.ts แมปคำสั่งย่อยไปยังเมธอดของ PluginManager:
    • install, uninstall, list, link, doctor, features, config, enable, disable
  • ไม่มีการกระทำ update อย่างชัดเจน การอัปเดตทำโดยการรัน install ซ้ำด้วยสเปคแพ็กเกจ/เวอร์ชันใหม่

สถานะปลั๊กอินส่วนกลางอยู่ภายใต้ ~/.xcsh/plugins:

  • package.json — ไฟล์ manifest ของการพึ่งพาที่ใช้โดย bun install/bun uninstall
  • node_modules/ — แพ็กเกจปลั๊กอินที่ติดตั้งแล้วหรือ symlinks
  • xcsh-plugins.lock.json — สถานะขณะรันไทม์:
    • เปิด/ปิดใช้งานต่อปลั๊กอิน
    • ชุดฟีเจอร์ที่เลือกต่อปลั๊กอิน
    • การตั้งค่าปลั๊กอินที่บันทึกไว้

การแทนที่ระดับโปรเจกต์อยู่ที่:

  • <cwd>/.xcsh/plugin-overrides.json

การแทนที่เป็นแบบอ่านอย่างเดียวจากมุมมองของ manager/loader (ไม่มีเส้นทางการเขียนที่นี่) และสามารถปิดใช้งานปลั๊กอินหรือแทนที่ฟีเจอร์/การตั้งค่าสำหรับโปรเจกต์นี้

การแยกวิเคราะห์สเปคปลั๊กอินและการตีความ metadata

หัวข้อที่มีชื่อว่า “การแยกวิเคราะห์สเปคปลั๊กอินและการตีความ metadata”

parsePluginSpec (parser.ts) รองรับ:

  • pkg -> features: null (พฤติกรรมค่าเริ่มต้น)
  • pkg[*] -> เปิดใช้งานทุกฟีเจอร์ใน manifest
  • pkg[] -> ไม่เปิดใช้งานฟีเจอร์เสริม
  • pkg[a,b] -> เปิดใช้งานฟีเจอร์ที่ระบุชื่อ
  • @scope/pkg@1.2.3[feat] -> แพ็กเกจแบบมีขอบเขต + มีเวอร์ชัน พร้อมการเลือกฟีเจอร์อย่างชัดเจน

extractPackageName ตัดส่วนต่อท้ายเวอร์ชันออกสำหรับการค้นหาเส้นทางบนดิสก์หลังการติดตั้ง

Manifest ถูกแก้ไขดังนี้:

  1. package.json.xcsh
  2. ทางเลือกสำรอง package.json.pi
  3. ทางเลือกสำรอง { version: package.version }

ผลกระทบ:

  • ไม่มีการตรวจสอบ schema อย่างเข้มงวดใน manager/loader
  • แพ็กเกจที่ขาด manifest xcsh/pi ยังคงสามารถติดตั้งและแสดงรายการได้
  • การโหลดปลั๊กอินขณะรันไทม์ (getEnabledPlugins) ข้ามแพ็กเกจที่ไม่มี manifest xcsh/pi
  • manifest.version ถูกเขียนทับเสมอจาก version ของแพ็กเกจ

JSON ของ package.json ที่มีรูปแบบผิดพลาดเป็นความล้มเหลวร้ายแรงในขณะอ่าน รูปร่าง manifest ที่มีรูปแบบผิดพลาดอาจล้มเหลวในภายหลังเมื่อมีการใช้งานฟิลด์เฉพาะ

  1. แยกวิเคราะห์ไวยากรณ์วงเล็บฟีเจอร์จากสเปคการติดตั้ง
  2. ตรวจสอบชื่อแพ็กเกจกับ regex + รายการที่ปฏิเสธอักขระพิเศษของ shell
  3. ตรวจสอบให้แน่ใจว่า package.json ของปลั๊กอินมีอยู่ (xcsh-plugins, แผนที่การพึ่งพาส่วนตัว)
  4. รัน bun install <packageSpec> ใน ~/.xcsh/plugins
  5. อ่าน node_modules/<name>/package.json ของแพ็กเกจที่ติดตั้ง
  6. แก้ไข manifest และคำนวณ enabledFeatures:
    • [*]: ทุกฟีเจอร์ที่ประกาศ (หรือ null ถ้าไม่มีแผนที่ฟีเจอร์)
    • [a,b]: ตรวจสอบว่าแต่ละฟีเจอร์มีอยู่ในแผนที่ฟีเจอร์ manifest
    • []: รายการฟีเจอร์ว่าง
    • สเปคเปล่า: null (ใช้นโยบายค่าเริ่มต้นในภายหลังใน loader)
  7. อัปเดต upsert สถานะขณะรันไทม์ใน lockfile: { version, enabledFeatures, enabled: true }

เนื่องจากการอัปเดตขับเคลื่อนโดย install:

  • xcsh plugin install pkg@newVersion อัปเดตการพึ่งพาและเวอร์ชันใน lockfile
  • การตั้งค่าที่มีอยู่ถูกเก็บรักษาไว้ รายการสถานะถูกเขียนทับสำหรับเวอร์ชัน/ฟีเจอร์/การเปิดใช้งาน
  • ไม่มีตรรกะ “ตรวจสอบการอัปเดต” แยกต่างหากหรือการย้ายข้อมูลแบบทรานแซกชัน
  1. ตรวจสอบชื่อแพ็กเกจ
  2. รัน bun uninstall <name> ในไดเรกทอรีปลั๊กอิน
  3. ลบสถานะขณะรันไทม์ของปลั๊กอินออกจาก lockfile:
    • config.plugins[name]
    • config.settings[name]

หากคำสั่ง uninstall ล้มเหลว สถานะขณะรันไทม์จะไม่เปลี่ยนแปลง

  1. อ่านแผนที่การพึ่งพาปลั๊กอินจาก ~/.xcsh/plugins/package.json
  2. โหลดการกำหนดค่าขณะรันไทม์ใน lockfile (ไฟล์หายไป -> ค่าเริ่มต้นว่าง)
  3. โหลดการแทนที่ของโปรเจกต์ (<cwd>/.xcsh/plugin-overrides.json, ข้อผิดพลาดในการแยกวิเคราะห์/อ่าน -> วัตถุว่างพร้อมคำเตือน)
  4. สำหรับแต่ละการพึ่งพาที่มี package.json ที่แก้ไขได้:
    • สร้างบันทึก InstalledPlugin
    • รวมสถานะฟีเจอร์/การเปิดใช้งาน:
      • ฐานจาก lockfile (หรือค่าเริ่มต้น)
      • การแทนที่ของโปรเจกต์สามารถแทนที่การเลือกฟีเจอร์ได้
      • รายการ disabled ของโปรเจกต์ทำให้ปลั๊กอินถูกมาสก์ว่าปิดใช้งาน

นี่คือสถานะที่มีผลซึ่งใช้โดยเอาต์พุตสถานะ CLI และการทำงานของ settings/features

link รองรับการพัฒนาปลั๊กอินในเครื่องโดยการสร้าง symlink ของแพ็กเกจในเครื่องไปยัง ~/.xcsh/plugins/node_modules/<pkg.name>

พฤติกรรม:

  1. แก้ไข localPath กับ cwd ของ manager
  2. ต้องการ package.json ในเครื่องและฟิลด์ name
  3. ตรวจสอบให้แน่ใจว่าไดเรกทอรีปลั๊กอินมีอยู่
  4. สำหรับชื่อแบบมีขอบเขต สร้างไดเรกทอรีขอบเขต
  5. ลบเส้นทางที่มีอยู่ที่ตำแหน่ง link เป้าหมาย
  6. สร้าง symlink
  7. เพิ่มรายการใน lockfile ขณะรันไทม์โดยเปิดใช้งานพร้อมฟีเจอร์เริ่มต้น (null)

ข้อควรระวัง: PluginManager.link ปัจจุบันไม่บังคับใช้การตรวจสอบขอบเขตเส้นทาง cwd ที่มีอยู่ใน installer.ts รุ่นเก่า (normalizedPath.startsWith(normalizedCwd)) ดังนั้นความน่าเชื่อถือจึงเป็นความรับผิดชอบของผู้เรียกใช้

การโหลดขณะรันไทม์: จากปลั๊กอินที่ติดตั้งแล้วไปสู่ความสามารถที่เรียกได้

หัวข้อที่มีชื่อว่า “การโหลดขณะรันไทม์: จากปลั๊กอินที่ติดตั้งแล้วไปสู่ความสามารถที่เรียกได้”

getEnabledPlugins(cwd) (plugins/loader.ts) อ่าน:

  • manifest การพึ่งพาปลั๊กอิน (package.json)
  • สถานะขณะรันไทม์ใน lockfile
  • การแทนที่ของโปรเจกต์ผ่าน getConfigDirPaths("plugin-overrides.json", { user: false, cwd })

การกรอง:

  • ข้ามถ้าไม่มี package.json ของปลั๊กอิน
  • ข้ามถ้าไม่มี manifest (xcsh/pi)
  • ข้ามถ้าปิดใช้งานส่วนกลางใน lockfile
  • ข้ามถ้าโปรเจกต์ปิดใช้งาน

สำหรับแต่ละปลั๊กอินที่เปิดใช้งาน:

  • resolvePluginToolPaths(plugin)
  • resolvePluginHookPaths(plugin)
  • resolvePluginCommandPaths(plugin)

ตัวแก้ไขแต่ละตัวรวมรายการฐานบวกรายการฟีเจอร์:

  • รายการฟีเจอร์ที่ชัดเจน -> เฉพาะฟีเจอร์ที่เลือก
  • enabledFeatures === null -> เปิดใช้งานฟีเจอร์ที่ทำเครื่องหมาย default: true

ไฟล์ที่หายไปจะถูกข้ามอย่างเงียบๆ (การตรวจสอบด้วย existsSync)

ความแตกต่างในการเดินสายขณะรันไทม์ปัจจุบัน

หัวข้อที่มีชื่อว่า “ความแตกต่างในการเดินสายขณะรันไทม์ปัจจุบัน”
  • เครื่องมือถูกเดินสายเข้าสู่รันไทม์ในปัจจุบัน ผ่าน discoverAndLoadCustomTools (custom-tools/loader.ts) ซึ่งเรียก getAllPluginToolPaths(cwd)
  • เส้นทางถูกลบข้อมูลซ้ำโดยเส้นทางสัมบูรณ์ที่แก้ไขแล้วในการค้นพบเครื่องมือที่กำหนดเอง (ชุด seen, เส้นทางแรกชนะ)
  • ตัวแก้ไข Hooks/commands มีอยู่ และถูก export แล้ว แต่เส้นทางโค้ดนี้ปัจจุบันยังไม่ได้เดินสายเข้าสู่ registry ขณะรันไทม์ในลักษณะเดียวกับที่เครื่องมือถูกเดินสาย

PluginManager แคชการกำหนดค่าขณะรันไทม์ในหน่วยความจำต่อ instance (#runtimeConfig) และโหลดแบบ lazy ครั้งเดียว

พฤติกรรมการโหลด:

  • lockfile หายไป -> { plugins: {}, settings: {} }
  • ความล้มเหลวในการอ่าน/แยกวิเคราะห์ lockfile -> คำเตือน + ค่าเริ่มต้นว่างเช่นกัน

พฤติกรรมการบันทึก:

  • เขียน JSON ของ lockfile แบบ pretty-printed เต็มรูปแบบในแต่ละการเปลี่ยนแปลง

ไม่มีการล็อคข้ามกระบวนการหรือกลยุทธ์การรวมที่มีอยู่ การเขียนพร้อมกันสามารถเขียนทับซึ่งกันและกันได้

การตรวจสอบความปลอดภัยและขอบเขตความน่าเชื่อถือ

หัวข้อที่มีชื่อว่า “การตรวจสอบความปลอดภัยและขอบเขตความน่าเชื่อถือ”

เส้นทาง manager ที่ใช้งานบังคับใช้การตรวจสอบชื่อแพ็กเกจ:

  • regex สำหรับสเปคแพ็กเกจแบบมีขอบเขต/ไม่มีขอบเขต (พร้อมเวอร์ชันตามต้องการ)
  • รายการที่ปฏิเสธอักขระพิเศษ shell อย่างชัดเจน ([;&|$(){}[]<>\]`)

สิ่งนี้จำกัดความเสี่ยงการ inject คำสั่งเมื่อเรียกใช้ bun install/uninstall

  • โค้ดปลั๊กอินทำงานใน process เมื่อโมดูลเครื่องมือที่กำหนดเองถูก import ไม่มี sandboxing
  • เส้นทางสัมพัทธ์ของ manifest ถูกรวมกับไดเรกทอรีแพ็กเกจปลั๊กอินและตรวจสอบเฉพาะการมีอยู่
  • แพ็กเกจปลั๊กอินเองเป็นโค้ดที่น่าเชื่อถือเมื่อติดตั้งแล้ว

installer.ts รวมการตรวจสอบเพิ่มเติมในขณะ link ที่ไม่ได้สะท้อนใน PluginManager.link:

  • เส้นทางในเครื่องต้องแก้ไขภายใน cwd ของโปรเจกต์
  • การตรวจสอบชื่อแพ็กเกจ/การข้ามผ่านเส้นทางเพิ่มเติมสำหรับการตั้งชื่อ target ของ symlink

เนื่องจาก CLI ใช้ PluginManager การตรวจสอบ link ที่เข้มงวดกว่าเหล่านี้จึงไม่ได้อยู่บนเส้นทางหลักในปัจจุบัน

พฤติกรรมความล้มเหลว ความสำเร็จบางส่วน และการย้อนกลับ

หัวข้อที่มีชื่อว่า “พฤติกรรมความล้มเหลว ความสำเร็จบางส่วน และการย้อนกลับ”

ตัวจัดการปลั๊กอินไม่ใช่แบบทรานแซกชัน

ขั้นตอนการทำงานพฤติกรรมเมื่อล้มเหลวการย้อนกลับ
bun install ล้มเหลวการติดตั้งยกเลิกพร้อม stderrไม่มี (ยังไม่มีการเขียนสถานะ)
ติดตั้งสำเร็จ จากนั้นการตรวจสอบ manifest/ฟีเจอร์ล้มเหลวคำสั่งล้มเหลวไม่มีการย้อนกลับการ uninstall การพึ่งพาอาจยังคงอยู่ใน node_modules/package.json
ติดตั้งสำเร็จ จากนั้นการเขียน lockfile ล้มเหลวคำสั่งล้มเหลวไม่มีการย้อนกลับของแพ็กเกจที่ติดตั้ง
bun uninstall สำเร็จ การเขียน lockfile ล้มเหลวคำสั่งล้มเหลวแพ็กเกจถูกลบ สถานะขณะรันไทม์ที่ค้างอยู่อาจยังคงอยู่
link ลบ target เก่าแล้ว การสร้าง symlink ล้มเหลวคำสั่งล้มเหลวไม่มีการกู้คืน link/ไดเรกทอรีก่อนหน้า

ในทางปฏิบัติ doctor --fix สามารถซ่อมแซมความเบี่ยงเบนบางส่วนได้ (bun install, การล้างค่า config ที่ไม่มีเจ้าของ, การล้างฟีเจอร์ที่ไม่ถูกต้อง) แต่เป็นแบบ best-effort

สรุปพฤติกรรมเมื่อ manifest มีรูปแบบผิดพลาดหรือขาดหายไป

หัวข้อที่มีชื่อว่า “สรุปพฤติกรรมเมื่อ manifest มีรูปแบบผิดพลาดหรือขาดหายไป”
  • ฟิลด์ xcsh/pi หายไป:
    • ติดตั้ง/แสดงรายการ: ยอมรับได้ (manifest ขั้นต่ำ)
    • การค้นพบปลั๊กอินที่เปิดใช้งานขณะรันไทม์: ถูกข้ามในฐานะที่ไม่ใช่ปลั๊กอิน
  • ฟีเจอร์ที่ขาดหายไปซึ่งอ้างถึงโดยสเปคการติดตั้งหรือ features --set/--enable: ข้อผิดพลาดร้ายแรงพร้อมรายการฟีเจอร์ที่มีอยู่
  • plugin-overrides.json ที่ไม่ถูกต้อง: ถูกละเว้นพร้อมการใช้ {} สำรองในทั้ง manager และ loader paths
  • เส้นทางไฟล์ tool/hook/command ที่ขาดหายไปซึ่งอ้างถึงโดย manifest: ถูกละเว้นอย่างเงียบๆ ในระหว่างการขยาย resolver ถูกระบุเป็นข้อผิดพลาดเฉพาะโดย doctor
  • --dry-run (install): ส่งคืนผลลัพธ์การติดตั้งสังเคราะห์ ไม่มีการเขียนระบบไฟล์/เครือข่าย/สถานะ
  • --json: การจัดรูปแบบเอาต์พุตเท่านั้น ไม่มีการเปลี่ยนแปลงพฤติกรรม
  • การแทนที่ของโปรเจกต์มีลำดับความสำคัญเหนือ lockfile ส่วนกลางเสมอสำหรับมุมมอง feature/settings
  • การเปิดใช้งานที่มีผลคือ runtimeEnabled && !projectDisabled