@yuchenrx/pmt-cli 0.1.2
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/AGENTS.md +128 -0
- package/README.md +134 -0
- package/README.zh.md +132 -0
- package/dist/pmt.exe +0 -0
- package/package.json +44 -0
- package/src/apply.ts +59 -0
- package/src/cli-name.ts +13 -0
- package/src/cli.ts +31 -0
- package/src/command-apply.ts +84 -0
- package/src/command-completion.ts +31 -0
- package/src/command-config.ts +244 -0
- package/src/command-link.ts +126 -0
- package/src/command-open.ts +47 -0
- package/src/command-search.ts +52 -0
- package/src/command-status.ts +27 -0
- package/src/commands.ts +165 -0
- package/src/completion.ts +96 -0
- package/src/config.ts +159 -0
- package/src/content-source.ts +14 -0
- package/src/helpers.ts +95 -0
- package/src/link-index.ts +101 -0
- package/src/link-manager.ts +180 -0
- package/src/link-repair.ts +30 -0
- package/src/link.ts +82 -0
- package/src/links-store.ts +47 -0
- package/src/lock.ts +59 -0
- package/src/prompt-root.ts +27 -0
- package/src/prompt.ts +20 -0
- package/src/rename-daemon.ts +98 -0
- package/src/rename-watcher.ts +110 -0
- package/src/scope.ts +13 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
- This file provides build, test, and style guidance for agentic coding.
|
|
5
|
+
- Follow these conventions unless a user request overrides them.
|
|
6
|
+
|
|
7
|
+
## Workspace
|
|
8
|
+
- Root: `E:\study\node\md-cli`
|
|
9
|
+
- Runtime: Bun (ESM, `type: module`).
|
|
10
|
+
- Entry: `src/cli.ts`.
|
|
11
|
+
|
|
12
|
+
## Build and Run
|
|
13
|
+
- Run CLI locally: `bun src/cli.ts`
|
|
14
|
+
- NPM script: `bun run pmt`
|
|
15
|
+
- Build binary: `bun run build`
|
|
16
|
+
- Build output: `dist/<cliName>.exe` (default: `dist/pmt.exe`)
|
|
17
|
+
|
|
18
|
+
## Test
|
|
19
|
+
- Run all tests: `bun test`
|
|
20
|
+
- Run a single test file: `bun test tests/<file>.test.ts`
|
|
21
|
+
- Run by name pattern (Bun supports filter): `bun test --filter "name"`
|
|
22
|
+
- Coverage (optional): `bun test --coverage`
|
|
23
|
+
|
|
24
|
+
## Lint/Format
|
|
25
|
+
- No explicit lint or formatter configured.
|
|
26
|
+
- Do not introduce new linters/formatters without request.
|
|
27
|
+
|
|
28
|
+
## Repo Structure
|
|
29
|
+
- `src/cli.ts` initializes Commander and registers commands.
|
|
30
|
+
- `src/commands.ts` declares CLI commands and arguments.
|
|
31
|
+
- `src/command-*.ts` holds command implementations.
|
|
32
|
+
- `src/config.ts` loads/merges config and resolves paths.
|
|
33
|
+
- `src/helpers.ts` provides filesystem/path helpers.
|
|
34
|
+
- `src/link*.ts` handles link metadata and linking logic.
|
|
35
|
+
- `src/completion.ts` handles shell completion.
|
|
36
|
+
- `scripts/build.ts` builds the binary.
|
|
37
|
+
- Tests live in `tests/*.test.ts`.
|
|
38
|
+
|
|
39
|
+
## Configuration Files
|
|
40
|
+
- Project config: `.promptslotrc.json` in repo root.
|
|
41
|
+
- Global config: `~/.promptslotrc.json`.
|
|
42
|
+
- Link store: `.promptslot-links.json` in repo root.
|
|
43
|
+
- Index: `.promptslot-index.json` in repo root.
|
|
44
|
+
|
|
45
|
+
## CLI Conventions
|
|
46
|
+
- Use Commander `Command` with chained calls.
|
|
47
|
+
- Use `.argument()` for positional arguments.
|
|
48
|
+
- Use `.option()` only when truly optional.
|
|
49
|
+
- Use async handlers: `.action(async () => { ... })`.
|
|
50
|
+
- Set `process.exitCode = 1` on errors instead of throwing.
|
|
51
|
+
- Print errors via `console.error`, success via `console.log`.
|
|
52
|
+
|
|
53
|
+
## Imports and Modules
|
|
54
|
+
- Use ESM import syntax.
|
|
55
|
+
- Prefer `node:` prefix for Node builtins.
|
|
56
|
+
- Prefer `import type` for types.
|
|
57
|
+
- Avoid unused imports; keep import order stable.
|
|
58
|
+
|
|
59
|
+
## Naming Conventions
|
|
60
|
+
- Functions: lowerCamelCase.
|
|
61
|
+
- Files: kebab-case for command files (`command-*.ts`).
|
|
62
|
+
- Config keys: snake_case (e.g., `prompts_root`).
|
|
63
|
+
- Link default name: `AGENTS.md`.
|
|
64
|
+
|
|
65
|
+
## Error Handling
|
|
66
|
+
- Use small `fail()` helpers that set `process.exitCode`.
|
|
67
|
+
- Avoid throwing for normal CLI errors.
|
|
68
|
+
- Return early after error reporting.
|
|
69
|
+
- For filesystem errors, check existence explicitly before use.
|
|
70
|
+
|
|
71
|
+
## Async and I/O
|
|
72
|
+
- Prefer `fs.promises` APIs.
|
|
73
|
+
- Avoid synchronous FS unless required (e.g., `spawnSync` for git).
|
|
74
|
+
- When reading remote sources, treat them as read-only.
|
|
75
|
+
|
|
76
|
+
## Completion
|
|
77
|
+
- Autocomplete uses `omelette` and `enquirer` for interactive search.
|
|
78
|
+
- Completion is initialized in `src/cli.ts`.
|
|
79
|
+
- Avoid heavy work in completion handlers.
|
|
80
|
+
|
|
81
|
+
## Link Behavior
|
|
82
|
+
- `link` attempts hardlink -> symlink -> meta-link fallback.
|
|
83
|
+
- Hard/soft links are real-time (filesystem-level).
|
|
84
|
+
- Meta-link uses a header with source path + hash.
|
|
85
|
+
- Meta-link content is read-only unless explicitly changed by CLI logic.
|
|
86
|
+
|
|
87
|
+
## Index and Auto-Repair
|
|
88
|
+
- Link index maps source -> targets and roots.
|
|
89
|
+
- Auto-repair runs at CLI start and updates broken paths.
|
|
90
|
+
- Do not scan full disks; use index roots only.
|
|
91
|
+
|
|
92
|
+
## Testing Conventions
|
|
93
|
+
- Use `bun:test` (`describe`, `it`, `expect`).
|
|
94
|
+
- Keep tests deterministic and filesystem-safe.
|
|
95
|
+
- Use `os.tmpdir()` for temp directories.
|
|
96
|
+
- Clean up watchers or temporary resources in tests.
|
|
97
|
+
|
|
98
|
+
## Style Notes
|
|
99
|
+
- Keep functions small and focused.
|
|
100
|
+
- Avoid deep nesting; use early returns.
|
|
101
|
+
- Avoid inline comments unless needed for clarity.
|
|
102
|
+
- Keep output messages user-facing and concise.
|
|
103
|
+
|
|
104
|
+
## Changes to Avoid
|
|
105
|
+
- Do not add new CLI commands unless requested.
|
|
106
|
+
- Do not change config schema without migration logic.
|
|
107
|
+
- Do not modify `dist/` outputs manually.
|
|
108
|
+
- Do not add network dependencies without approval.
|
|
109
|
+
|
|
110
|
+
## Single-Test Workflow
|
|
111
|
+
- Use `bun test tests/xyz.test.ts`.
|
|
112
|
+
- If needed, add `--filter` for a single case.
|
|
113
|
+
|
|
114
|
+
## Release (GitHub Actions)
|
|
115
|
+
- Tag push `vX.Y.Z` triggers build + release + npm publish.
|
|
116
|
+
- Workflow sets `package.json` version from tag; uses `NPM_TOKEN` secret.
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
## Notes on External Libraries
|
|
120
|
+
- Commander for CLI parsing.
|
|
121
|
+
- Cosmiconfig for configuration loading.
|
|
122
|
+
- Enquirer for interactive selection.
|
|
123
|
+
- Omelette for shell completion.
|
|
124
|
+
|
|
125
|
+
## Platform Notes
|
|
126
|
+
- Windows is the primary platform.
|
|
127
|
+
- Prefer cross-platform APIs; avoid shell-specific commands.
|
|
128
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# pmt
|
|
2
|
+
|
|
3
|
+
Prompt/spec manager CLI that keeps shared rules synchronized across projects.
|
|
4
|
+
|
|
5
|
+
[中文文档](./README.zh.md)
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
- Centralizes prompt/spec files in a global repo (`prompts_root`).
|
|
10
|
+
- Creates link files in any project (hardlink -> symlink -> meta-link fallback).
|
|
11
|
+
- Tracks link moves/renames in background (no full-disk scans).
|
|
12
|
+
- Shows link status without scanning the filesystem.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
### npm (Windows)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm i -g @yuchenrx/pmt-cli
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
This package publishes a Windows binary. For other platforms, build from source.
|
|
23
|
+
|
|
24
|
+
### From source
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
bun install
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Run (global)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pmt
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Run (dev)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
bun run pmt
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Build
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
bun run build
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Output: `dist/pmt.exe`
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
### 1) Init
|
|
53
|
+
|
|
54
|
+
Global prompt repo:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pmt init
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`init` initializes the global prompt repo and runs `git init` under `prompts_root` if needed.
|
|
61
|
+
|
|
62
|
+
### 2) Link
|
|
63
|
+
|
|
64
|
+
Create a link file using the source filename as default:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pmt link 规范
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Create to a specific path:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pmt link 规范 docs/AGENTS.md
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Interactive search:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pmt link
|
|
80
|
+
pmt link -i
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 3) Status
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pmt status
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Example output:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
docs/AGENTS.md -> C:\...\prompts\规范.md [hardlink] ok
|
|
93
|
+
AGENTS.md -> C:\...\prompts\rules.md [meta] missing
|
|
94
|
+
roots: C:\repo, D:\shared
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Config
|
|
98
|
+
|
|
99
|
+
Config files:
|
|
100
|
+
|
|
101
|
+
- Project: `.promptslotrc.json`
|
|
102
|
+
- Global: `~/.promptslotrc.json`
|
|
103
|
+
- Link store: `.promptslot-links.json`
|
|
104
|
+
- Link index: `.promptslot-index.json`
|
|
105
|
+
|
|
106
|
+
Common keys:
|
|
107
|
+
|
|
108
|
+
- `prompts_root`: global prompt repo directory
|
|
109
|
+
- `prompts_dir`: project prompt directory (fallback)
|
|
110
|
+
- `link_default`: default link filename
|
|
111
|
+
- `sources`: name → file path/URL
|
|
112
|
+
- `current`: active source name
|
|
113
|
+
|
|
114
|
+
## Link modes
|
|
115
|
+
|
|
116
|
+
1) **Hardlink**: real-time sync at filesystem level
|
|
117
|
+
2) **Symlink**: real-time sync at filesystem level
|
|
118
|
+
3) **Meta-link**: header + hash (used when linking is not possible)
|
|
119
|
+
|
|
120
|
+
Meta-link is for read-only or cross-device cases (e.g. URL sources).
|
|
121
|
+
|
|
122
|
+
## Rename tracking
|
|
123
|
+
|
|
124
|
+
- A background rename daemon starts after creating a meta-link.
|
|
125
|
+
- It only watches directories that already contain link files.
|
|
126
|
+
- No full-disk scanning; updates `.promptslot-links.json` on rename.
|
|
127
|
+
|
|
128
|
+
## CLI name override
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
CLI_NAME=pmt
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# pmt
|
|
2
|
+
|
|
3
|
+
用于统一管理/同步规范与提示文件的 CLI。
|
|
4
|
+
|
|
5
|
+
[English](./README.md)
|
|
6
|
+
|
|
7
|
+
## 功能概述
|
|
8
|
+
|
|
9
|
+
- 全局集中管理 prompt/spec 文件(`prompts_root`)。
|
|
10
|
+
- 在任意项目创建 link 文件(硬链接 → 软链接 → meta-link 降级)。
|
|
11
|
+
- 后台追踪 link 重命名/移动(只监听已有目录,不全盘扫描)。
|
|
12
|
+
- `status` 查看所有 link 状态(不扫描磁盘)。
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
### npm(Windows)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm i -g @yuchenrx/pmt-cli
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
该包发布的是 Windows 二进制文件,其他平台请从源码构建。
|
|
23
|
+
|
|
24
|
+
### 源码安装
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
bun install
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 运行(全局)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pmt
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 运行(开发)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
bun run pmt
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 构建
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
bun run build
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
输出:`dist/pmt.exe`
|
|
49
|
+
|
|
50
|
+
## 快速开始
|
|
51
|
+
|
|
52
|
+
### 1)初始化
|
|
53
|
+
|
|
54
|
+
全局 prompt 仓库:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pmt init
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`init` 会在 `prompts_root` 下自动执行 `git init`(若未初始化)。
|
|
61
|
+
|
|
62
|
+
### 2)创建链接
|
|
63
|
+
|
|
64
|
+
默认使用源文件名作为目标名:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pmt link 规范
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
指定目标路径:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pmt link 规范 docs/AGENTS.md
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
交互搜索:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pmt link
|
|
80
|
+
pmt link -i
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 3)查看状态
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pmt status
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
示例输出:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
docs/AGENTS.md -> C:\...\prompts\规范.md [hardlink] ok
|
|
93
|
+
AGENTS.md -> C:\...\prompts\rules.md [meta] missing
|
|
94
|
+
roots: C:\repo, D:\shared
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 配置
|
|
98
|
+
|
|
99
|
+
配置文件:
|
|
100
|
+
|
|
101
|
+
- 项目配置:`.promptslotrc.json`
|
|
102
|
+
- 全局配置:`~/.promptslotrc.json`
|
|
103
|
+
- 链接记录:`.promptslot-links.json`
|
|
104
|
+
- 索引文件:`.promptslot-index.json`
|
|
105
|
+
|
|
106
|
+
常用字段:
|
|
107
|
+
|
|
108
|
+
- `prompts_root`:全局 prompt 仓库目录
|
|
109
|
+
- `prompts_dir`:项目 prompts 目录(fallback)
|
|
110
|
+
- `link_default`:默认 link 文件名
|
|
111
|
+
- `sources`:name → 文件路径/URL
|
|
112
|
+
- `current`:当前激活的 source 名
|
|
113
|
+
|
|
114
|
+
## 链接模式
|
|
115
|
+
|
|
116
|
+
1)**硬链接**:文件系统级实时同步
|
|
117
|
+
2)**软链接**:文件系统级实时同步
|
|
118
|
+
3)**Meta-link**:头部 + hash(无法链接时降级)
|
|
119
|
+
|
|
120
|
+
Meta-link 用于跨盘、只读或 URL 等场景。
|
|
121
|
+
|
|
122
|
+
## 重命名追踪
|
|
123
|
+
|
|
124
|
+
- 创建 meta-link 后自动启动后台重命名守护。
|
|
125
|
+
- 仅监听已有 link 文件所在目录。
|
|
126
|
+
- 不做全盘扫描,仅更新 `.promptslot-links.json`。
|
|
127
|
+
|
|
128
|
+
## CLI 名称覆盖
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
CLI_NAME=pmt
|
|
132
|
+
```
|
package/dist/pmt.exe
ADDED
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yuchenrx/pmt-cli",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"pmt": "dist/pmt.exe"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"pmt": "bun src/cli.ts",
|
|
10
|
+
"build": "bun scripts/build.ts",
|
|
11
|
+
"test": "bun test"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src",
|
|
16
|
+
"README.md",
|
|
17
|
+
"README.zh.md",
|
|
18
|
+
"AGENTS.md"
|
|
19
|
+
],
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+ssh://git@github.com/YuChenRX/pmt.git"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/YuChenRX/pmt",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/YuChenRX/pmt/issues"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"commander": "^14.0.2",
|
|
33
|
+
"cosmiconfig": "^9.0.0",
|
|
34
|
+
"dotenv": "^17.2.3",
|
|
35
|
+
"enquirer": "^2.4.1",
|
|
36
|
+
"omelette": "^0.4.17",
|
|
37
|
+
"watcher": "^2.3.1"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/bun": "^1.3.6",
|
|
41
|
+
"@types/node": "^25.0.8",
|
|
42
|
+
"typescript": "^5.9.3"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/apply.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { promises as fs } from 'node:fs'
|
|
3
|
+
import type { PromptslotConfig } from './config'
|
|
4
|
+
|
|
5
|
+
const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
6
|
+
|
|
7
|
+
const buildBlock = (value: string, markers: PromptslotConfig['markers']) => {
|
|
8
|
+
const trimmed = value.replace(/\s+$/, '')
|
|
9
|
+
return `${markers.start}\n${trimmed}\n${markers.end}`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const replaceInContent = (
|
|
13
|
+
content: string,
|
|
14
|
+
value: string,
|
|
15
|
+
config: PromptslotConfig,
|
|
16
|
+
) => {
|
|
17
|
+
const block = buildBlock(value, config.markers)
|
|
18
|
+
const start = escapeRegExp(config.markers.start)
|
|
19
|
+
const end = escapeRegExp(config.markers.end)
|
|
20
|
+
const blockRegex = new RegExp(`${start}[\\s\\S]*?${end}`, 'g')
|
|
21
|
+
|
|
22
|
+
if (blockRegex.test(content)) {
|
|
23
|
+
const next = content.replace(blockRegex, block)
|
|
24
|
+
return { next, changed: next !== content }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (content.includes(config.placeholder)) {
|
|
28
|
+
const next = content.split(config.placeholder).join(block)
|
|
29
|
+
return { next, changed: next !== content }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return { next: content, changed: false }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const fileExists = async (filePath: string) =>
|
|
36
|
+
fs.access(filePath).then(() => true).catch(() => false)
|
|
37
|
+
|
|
38
|
+
export const applyToFiles = async (
|
|
39
|
+
targets: string[],
|
|
40
|
+
value: string,
|
|
41
|
+
config: PromptslotConfig,
|
|
42
|
+
) => {
|
|
43
|
+
const changedFiles: string[] = []
|
|
44
|
+
|
|
45
|
+
for (const target of targets) {
|
|
46
|
+
const resolved = path.resolve(target)
|
|
47
|
+
const exists = await fileExists(resolved)
|
|
48
|
+
if (!exists) continue
|
|
49
|
+
|
|
50
|
+
const content = await fs.readFile(resolved, 'utf8')
|
|
51
|
+
const { next, changed } = replaceInContent(content, value, config)
|
|
52
|
+
if (!changed) continue
|
|
53
|
+
|
|
54
|
+
await fs.writeFile(resolved, next)
|
|
55
|
+
changedFiles.push(target)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return changedFiles
|
|
59
|
+
}
|
package/src/cli-name.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import dotenv from 'dotenv'
|
|
2
|
+
|
|
3
|
+
dotenv.config({ quiet: true })
|
|
4
|
+
|
|
5
|
+
const DEFAULT_CLI_NAME = 'pmt'
|
|
6
|
+
|
|
7
|
+
export const getCliName = () => {
|
|
8
|
+
const envName = process.env.CLI_NAME?.trim()
|
|
9
|
+
if (envName) return envName
|
|
10
|
+
return DEFAULT_CLI_NAME
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const formatCommand = (command: string) => `${getCliName()} ${command}`
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { Command } from 'commander'
|
|
3
|
+
import { getCliName } from './cli-name'
|
|
4
|
+
import { initCompletion } from './completion'
|
|
5
|
+
import { registerCommands } from './commands'
|
|
6
|
+
import { autoRepairLinks } from './link-repair'
|
|
7
|
+
|
|
8
|
+
const program = new Command()
|
|
9
|
+
|
|
10
|
+
program
|
|
11
|
+
.name(getCliName())
|
|
12
|
+
.description('占位符 prompt 管理 CLI')
|
|
13
|
+
.helpOption('-h, --help', '显示帮助')
|
|
14
|
+
.version('0.1.0', '-v, --version', '显示版本')
|
|
15
|
+
|
|
16
|
+
registerCommands(program)
|
|
17
|
+
initCompletion()
|
|
18
|
+
|
|
19
|
+
const args = process.argv.slice(2)
|
|
20
|
+
if (!args.length) {
|
|
21
|
+
program.outputHelp()
|
|
22
|
+
process.exit(0)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (args.length === 1 && (args[0] === '-h' || args[0] === '--help')) {
|
|
26
|
+
program.outputHelp()
|
|
27
|
+
process.exit(0)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
await autoRepairLinks()
|
|
31
|
+
await program.parseAsync(process.argv)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs'
|
|
2
|
+
import { applyToFiles } from './apply'
|
|
3
|
+
import { formatCommand } from './cli-name'
|
|
4
|
+
import { readSourceText } from './content-source'
|
|
5
|
+
import { loadMergedConfig } from './config'
|
|
6
|
+
import { fileExists, isUrl, resolvePath, resolveTargets } from './helpers'
|
|
7
|
+
|
|
8
|
+
const log = (text: string) => console.log(text)
|
|
9
|
+
const fail = (text: string) => {
|
|
10
|
+
console.error(text)
|
|
11
|
+
process.exitCode = 1
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const requireMergedConfig = async () => {
|
|
15
|
+
const merged = await loadMergedConfig()
|
|
16
|
+
if (!merged.hasConfig) {
|
|
17
|
+
fail(`未找到配置文件,请先运行: ${formatCommand('init')}`)
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
return merged
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const insertPlaceholder = async (target?: string) => {
|
|
24
|
+
if (!target) {
|
|
25
|
+
fail(`用法: ${formatCommand('insert <file>')}`)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const merged = await requireMergedConfig()
|
|
30
|
+
if (!merged) return
|
|
31
|
+
|
|
32
|
+
const resolved = resolvePath(target)
|
|
33
|
+
const exists = await fileExists(resolved)
|
|
34
|
+
if (!exists) {
|
|
35
|
+
fail(`文件不存在: ${resolved}`)
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const content = await fs.readFile(resolved, 'utf8')
|
|
40
|
+
if (content.includes(merged.config.placeholder)) {
|
|
41
|
+
log('文件已包含占位符')
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const next = content.replace(/\s*$/, `\n${merged.config.placeholder}\n`)
|
|
46
|
+
await fs.writeFile(resolved, next)
|
|
47
|
+
log(`已插入占位符: ${target}`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const applyCurrent = async (targets: string[]) => {
|
|
51
|
+
const merged = await requireMergedConfig()
|
|
52
|
+
if (!merged) return
|
|
53
|
+
|
|
54
|
+
const current = merged.config.current
|
|
55
|
+
if (!current) {
|
|
56
|
+
fail(`未设置当前值,请先运行: ${formatCommand('set <name> <file>')}`)
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const sourcePath = merged.resolvedSources[current]
|
|
61
|
+
if (!sourcePath) {
|
|
62
|
+
fail(`当前值未配置路径: ${current}`)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!isUrl(sourcePath)) {
|
|
67
|
+
const sourceExists = await fileExists(sourcePath)
|
|
68
|
+
if (!sourceExists) {
|
|
69
|
+
fail(`当前文件不存在: ${sourcePath}`)
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const value = await readSourceText(sourcePath)
|
|
75
|
+
const allTargets = await resolveTargets(targets, Object.values(merged.resolvedSources))
|
|
76
|
+
const changed = await applyToFiles(allTargets, value, merged.config)
|
|
77
|
+
|
|
78
|
+
if (!changed.length) {
|
|
79
|
+
log('未找到需要替换的文件')
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
log(`已更新 ${changed.length} 个文件`)
|
|
84
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { cleanupCompletion, installCompletion } from './completion'
|
|
2
|
+
import { formatCommand } from './cli-name'
|
|
3
|
+
|
|
4
|
+
const fail = (text: string) => {
|
|
5
|
+
console.error(text)
|
|
6
|
+
process.exitCode = 1
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const completionInstall = async () => {
|
|
10
|
+
try {
|
|
11
|
+
installCompletion()
|
|
12
|
+
} catch (error) {
|
|
13
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
14
|
+
fail(`自动安装失败: ${message}`)
|
|
15
|
+
fail(`可手动执行: ${formatCommand('completion show')}`)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const completionCleanup = async () => {
|
|
20
|
+
try {
|
|
21
|
+
cleanupCompletion()
|
|
22
|
+
} catch (error) {
|
|
23
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
24
|
+
fail(`自动卸载失败: ${message}`)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const completionShow = async () => {
|
|
29
|
+
process.stdout.write(`${formatCommand('--completion')}\n`)
|
|
30
|
+
process.stdout.write(`${formatCommand('--completion-fish')}\n`)
|
|
31
|
+
}
|