@ibotor/smart-trellis 0.5.21 → 0.5.22
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/README.md +43 -0
- package/dist/migrations/manifests/0.5.22.json +9 -0
- package/dist/templates/common/commands/joint-debugger.md +94 -2
- package/dist/templates/trellis/index.d.ts +1 -0
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +2 -0
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/apifox_token.py +128 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -223,6 +223,49 @@ smart-trellis init --tools codex,cursor --no-monorepo
|
|
|
223
223
|
|
|
224
224
|
所以,`smart-trellis init` 的目标不是只生成一个轻量配置,而是用更少确认步骤生成原 Trellis 初始化应有的完整结果。
|
|
225
225
|
|
|
226
|
+
## Apifox 前后端联调入口
|
|
227
|
+
|
|
228
|
+
初始化 Cursor / Codex 等平台后,会生成 `trellis-joint-debugger` 入口,用于根据 Apifox 接口链接自动解析接口详情并辅助前后端联调。
|
|
229
|
+
|
|
230
|
+
常见触发方式:
|
|
231
|
+
|
|
232
|
+
- Cursor:`/trellis-joint-debugger`
|
|
233
|
+
- Codex / `.agents/skills`:`$trellis-joint-debugger`
|
|
234
|
+
|
|
235
|
+
当用户提供以下 Apifox 链接,并提到“联调”“接口”“参数”“详情”“完成它”等开发意图时,入口会优先走 Apifox Web API,而不是默认打开浏览器页面:
|
|
236
|
+
|
|
237
|
+
```text
|
|
238
|
+
app.apifox.com/link/project/{projectId}/apis/api-{apiId}
|
|
239
|
+
app.apifox.com/project/{projectId}/apis/api-{apiId}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
第一次使用前,需要配置 Apifox access token:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
python3 ./.trellis/scripts/apifox_token.py check
|
|
246
|
+
python3 ./.trellis/scripts/apifox_token.py save
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
`save` 会使用隐藏输入,并把 token 保存到用户级配置:
|
|
250
|
+
|
|
251
|
+
```text
|
|
252
|
+
~/.trellis/apifox.env
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
因此同一台机器上只需要配置一次,后续所有 Trellis 项目都可以复用。运行时读取优先级为:
|
|
256
|
+
|
|
257
|
+
1. `APIFOX_ACCESS_TOKEN` 环境变量
|
|
258
|
+
2. `~/.trellis/apifox.env`
|
|
259
|
+
3. 旧项目内 `.trellis/.runtime/apifox.env`(仅兼容读取)
|
|
260
|
+
|
|
261
|
+
token 只用于请求 header,不会被命令打印。接口详情会通过:
|
|
262
|
+
|
|
263
|
+
```text
|
|
264
|
+
GET https://api.apifox.com/api/v1/projects/{projectId}/http-apis/{apiId}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
如请求体或响应中出现 `$ref: #/definitions/{schemaId}`,会继续根据接口详情里的 `moduleId` 请求 data-schemas 并解析入参/出参字段。随后 Trellis 会将 method、path、入参、出参与当前项目中的 API 封装核对;如果要联调真实后端,再根据项目代理和环境配置请求业务后端。若缺少业务登录态,应明确报告 `401` / 未登录,不猜测数据。
|
|
268
|
+
|
|
226
269
|
## 本地开发验证
|
|
227
270
|
|
|
228
271
|
在本仓库开发时,可以先构建 CLI:
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.5.22",
|
|
3
|
+
"description": "Patch: expand the Apifox joint debugger workflow and add user-level Apifox token storage.",
|
|
4
|
+
"breaking": false,
|
|
5
|
+
"recommendMigrate": false,
|
|
6
|
+
"changelog": "**Enhancements:**\n- feat(joint-debugger): expand `trellis-joint-debugger` into an Apifox-backed frontend-backend joint debugging workflow. It parses Apifox API links, fetches API details through the Apifox Web API, resolves referenced schemas, and compares method/path/fields with local API wrappers.\n- feat(scripts): add `.trellis/scripts/apifox_token.py` so users can configure `APIFOX_ACCESS_TOKEN` once per machine at `~/.trellis/apifox.env`. Environment variables still take precedence, and legacy project-local `.trellis/.runtime/apifox.env` files remain readable as a fallback.",
|
|
7
|
+
"migrations": [],
|
|
8
|
+
"notes": "No migration required. Run `trellis update` to receive the expanded joint debugger command/skill and the Apifox token helper script. Run `python3 ./.trellis/scripts/apifox_token.py save` once to store the Apifox token for all Trellis projects on this machine."
|
|
9
|
+
}
|
|
@@ -1,5 +1,97 @@
|
|
|
1
1
|
# Joint Debugger
|
|
2
2
|
|
|
3
|
-
Frontend-backend joint debugging entry point.
|
|
3
|
+
Frontend-backend joint debugging entry point for Apifox-backed API work.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Use this command when the user provides an Apifox API link and asks about
|
|
6
|
+
joint debugging, interface details, parameters, implementation, or completion.
|
|
7
|
+
|
|
8
|
+
## Step 0: Load Trellis Context
|
|
9
|
+
|
|
10
|
+
Run the same startup context that `/trellis start` would load, but do not create
|
|
11
|
+
a task unless the user explicitly asks for Trellis task management.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
{{PYTHON_CMD}} ./.trellis/scripts/get_context.py
|
|
15
|
+
{{PYTHON_CMD}} ./.trellis/scripts/get_context.py --mode phase
|
|
16
|
+
{{PYTHON_CMD}} ./.trellis/scripts/get_context.py --mode packages
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Step 1: Ensure Apifox Token
|
|
20
|
+
|
|
21
|
+
Before calling Apifox APIs, check whether `APIFOX_ACCESS_TOKEN` is already
|
|
22
|
+
configured. The token must only be used in request headers and must never be
|
|
23
|
+
printed.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
{{PYTHON_CMD}} ./.trellis/scripts/apifox_token.py check
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
If the check fails, ask the user to get an Apifox access token and run:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
{{PYTHON_CMD}} ./.trellis/scripts/apifox_token.py save
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The save prompt hides input and stores the token in the user-level config
|
|
36
|
+
`~/.trellis/apifox.env`, so it works across projects. After the user saves it,
|
|
37
|
+
rerun the check before continuing. Do not ask the user to paste the token into
|
|
38
|
+
chat. Legacy project-local `.trellis/.runtime/apifox.env` files are still read
|
|
39
|
+
as a fallback, but new saves are global.
|
|
40
|
+
|
|
41
|
+
## Step 2: Parse Apifox Link
|
|
42
|
+
|
|
43
|
+
Recognize these link forms:
|
|
44
|
+
|
|
45
|
+
- `app.apifox.com/link/project/{projectId}/apis/api-{apiId}`
|
|
46
|
+
- `app.apifox.com/project/{projectId}/apis/api-{apiId}`
|
|
47
|
+
|
|
48
|
+
Extract `projectId` and `apiId`. Do not open the Apifox page in a browser by
|
|
49
|
+
default.
|
|
50
|
+
|
|
51
|
+
## Step 3: Fetch API Detail
|
|
52
|
+
|
|
53
|
+
Load the token without printing it, then request the Apifox Web API:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
set -a
|
|
57
|
+
if [ -z "${APIFOX_ACCESS_TOKEN:-}" ]; then
|
|
58
|
+
APIFOX_TOKEN_FILE="$({{PYTHON_CMD}} ./.trellis/scripts/apifox_token.py path)"
|
|
59
|
+
[ -f "$APIFOX_TOKEN_FILE" ] && . "$APIFOX_TOKEN_FILE"
|
|
60
|
+
fi
|
|
61
|
+
set +a
|
|
62
|
+
curl -sS \
|
|
63
|
+
-H "Authorization: Bearer ${APIFOX_ACCESS_TOKEN}" \
|
|
64
|
+
-H "X-Branch-Id: 0" \
|
|
65
|
+
-H "X-Client-Version: 100.100.100" \
|
|
66
|
+
"https://api.apifox.com/api/v1/projects/{projectId}/http-apis/{apiId}"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Capture `method`, `path`, `moduleId`, request parameters, `requestBody`, and
|
|
70
|
+
`responses`.
|
|
71
|
+
|
|
72
|
+
## Step 4: Resolve Schema References
|
|
73
|
+
|
|
74
|
+
If `requestBody` or `responses` contains `$ref: #/definitions/{schemaId}`, use
|
|
75
|
+
the API detail's `moduleId` to fetch schemas:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
curl -sS \
|
|
79
|
+
-H "Authorization: Bearer ${APIFOX_ACCESS_TOKEN}" \
|
|
80
|
+
-H "X-Branch-Id: 0" \
|
|
81
|
+
-H "X-Client-Version: 100.100.100" \
|
|
82
|
+
"https://api.apifox.com/api/v1/projects/{projectId}/data-schemas?moduleId={moduleId}"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Match definitions by schema id and expand request/response fields before
|
|
86
|
+
summarizing the interface.
|
|
87
|
+
|
|
88
|
+
## Step 5: Compare With Code
|
|
89
|
+
|
|
90
|
+
Search the current codebase for existing API wrappers, request clients,
|
|
91
|
+
environment proxy settings, and matching paths. Compare the parsed
|
|
92
|
+
`method/path` and request/response fields with the local API encapsulation.
|
|
93
|
+
|
|
94
|
+
If the user wants real backend joint debugging, use the project's proxy and
|
|
95
|
+
environment configuration to call the business backend. If the backend returns
|
|
96
|
+
`401` or requires a login session, report that clearly as unauthenticated
|
|
97
|
+
instead of inventing data.
|
|
@@ -42,6 +42,7 @@ export declare const initDeveloperScript: string;
|
|
|
42
42
|
export declare const taskScript: string;
|
|
43
43
|
export declare const getContextScript: string;
|
|
44
44
|
export declare const addSessionScript: string;
|
|
45
|
+
export declare const apifoxTokenScript: string;
|
|
45
46
|
export declare const workflowMdTemplate: string;
|
|
46
47
|
export declare const configYamlTemplate: string;
|
|
47
48
|
export declare const gitignoreTemplate: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/trellis/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAcH,eAAO,MAAM,WAAW,QAAsC,CAAC;AAG/D,eAAO,MAAM,UAAU,QAA6C,CAAC;AACrE,eAAO,MAAM,WAAW,QAA0C,CAAC;AACnE,eAAO,MAAM,eAAe,QAA8C,CAAC;AAC3E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,YAAY,QAA2C,CAAC;AACrE,eAAO,MAAM,QAAQ,QAAuC,CAAC;AAC7D,eAAO,MAAM,SAAS,QAAwC,CAAC;AAC/D,eAAO,MAAM,SAAS,QAAwC,CAAC;AAC/D,eAAO,MAAM,WAAW,QAA0C,CAAC;AACnE,eAAO,MAAM,WAAW,QAA0C,CAAC;AACnE,eAAO,MAAM,iBAAiB,QAAiD,CAAC;AAChF,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AACF,eAAO,MAAM,qBAAqB,QAEjC,CAAC;AACF,eAAO,MAAM,mBAAmB,QAE/B,CAAC;AACF,eAAO,MAAM,mBAAmB,QAE/B,CAAC;AACF,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAG9E,eAAO,MAAM,kBAAkB,QAA2C,CAAC;AAC3E,eAAO,MAAM,mBAAmB,QAA4C,CAAC;AAC7E,eAAO,MAAM,UAAU,QAAkC,CAAC;AAC1D,eAAO,MAAM,gBAAgB,QAAyC,CAAC;AACvE,eAAO,MAAM,gBAAgB,QAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/trellis/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAcH,eAAO,MAAM,WAAW,QAAsC,CAAC;AAG/D,eAAO,MAAM,UAAU,QAA6C,CAAC;AACrE,eAAO,MAAM,WAAW,QAA0C,CAAC;AACnE,eAAO,MAAM,eAAe,QAA8C,CAAC;AAC3E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,YAAY,QAA2C,CAAC;AACrE,eAAO,MAAM,QAAQ,QAAuC,CAAC;AAC7D,eAAO,MAAM,SAAS,QAAwC,CAAC;AAC/D,eAAO,MAAM,SAAS,QAAwC,CAAC;AAC/D,eAAO,MAAM,WAAW,QAA0C,CAAC;AACnE,eAAO,MAAM,WAAW,QAA0C,CAAC;AACnE,eAAO,MAAM,iBAAiB,QAAiD,CAAC;AAChF,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AACF,eAAO,MAAM,qBAAqB,QAEjC,CAAC;AACF,eAAO,MAAM,mBAAmB,QAE/B,CAAC;AACF,eAAO,MAAM,mBAAmB,QAE/B,CAAC;AACF,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAG9E,eAAO,MAAM,kBAAkB,QAA2C,CAAC;AAC3E,eAAO,MAAM,mBAAmB,QAA4C,CAAC;AAC7E,eAAO,MAAM,UAAU,QAAkC,CAAC;AAC1D,eAAO,MAAM,gBAAgB,QAAyC,CAAC;AACvE,eAAO,MAAM,gBAAgB,QAAyC,CAAC;AACvE,eAAO,MAAM,iBAAiB,QAA0C,CAAC;AAGzE,eAAO,MAAM,kBAAkB,QAA8B,CAAC;AAC9D,eAAO,MAAM,kBAAkB,QAA8B,CAAC;AAC9D,eAAO,MAAM,iBAAiB,QAAgC,CAAC;AAE/D;;GAEG;AACH,wBAAgB,aAAa,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAsCnD"}
|
|
@@ -53,6 +53,7 @@ export const initDeveloperScript = readTemplate("scripts/init_developer.py");
|
|
|
53
53
|
export const taskScript = readTemplate("scripts/task.py");
|
|
54
54
|
export const getContextScript = readTemplate("scripts/get_context.py");
|
|
55
55
|
export const addSessionScript = readTemplate("scripts/add_session.py");
|
|
56
|
+
export const apifoxTokenScript = readTemplate("scripts/apifox_token.py");
|
|
56
57
|
// Configuration files
|
|
57
58
|
export const workflowMdTemplate = readTemplate("workflow.md");
|
|
58
59
|
export const configYamlTemplate = readTemplate("config.yaml");
|
|
@@ -92,6 +93,7 @@ export function getAllScripts() {
|
|
|
92
93
|
scripts.set("task.py", taskScript);
|
|
93
94
|
scripts.set("get_context.py", getContextScript);
|
|
94
95
|
scripts.set("add_session.py", addSessionScript);
|
|
96
|
+
scripts.set("apifox_token.py", apifoxTokenScript);
|
|
95
97
|
return scripts;
|
|
96
98
|
}
|
|
97
99
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/templates/trellis/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,SAAS,YAAY,CAAC,YAAoB;IACxC,OAAO,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;AAC9D,CAAC;AAED,gCAAgC;AAChC,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC,qBAAqB,CAAC,CAAC;AAE/D,0BAA0B;AAC1B,MAAM,CAAC,MAAM,UAAU,GAAG,YAAY,CAAC,4BAA4B,CAAC,CAAC;AACrE,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC,yBAAyB,CAAC,CAAC;AACnE,MAAM,CAAC,MAAM,eAAe,GAAG,YAAY,CAAC,6BAA6B,CAAC,CAAC;AAC3E,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC,+BAA+B,CAAC,CAAC;AAC9E,MAAM,CAAC,MAAM,eAAe,GAAG,YAAY,CAAC,8BAA8B,CAAC,CAAC;AAC5E,MAAM,CAAC,MAAM,eAAe,GAAG,YAAY,CAAC,8BAA8B,CAAC,CAAC;AAC5E,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC,+BAA+B,CAAC,CAAC;AAC9E,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC,+BAA+B,CAAC,CAAC;AAC9E,MAAM,CAAC,MAAM,YAAY,GAAG,YAAY,CAAC,0BAA0B,CAAC,CAAC;AACrE,MAAM,CAAC,MAAM,QAAQ,GAAG,YAAY,CAAC,sBAAsB,CAAC,CAAC;AAC7D,MAAM,CAAC,MAAM,SAAS,GAAG,YAAY,CAAC,uBAAuB,CAAC,CAAC;AAC/D,MAAM,CAAC,MAAM,SAAS,GAAG,YAAY,CAAC,uBAAuB,CAAC,CAAC;AAC/D,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC,yBAAyB,CAAC,CAAC;AACnE,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC,yBAAyB,CAAC,CAAC;AACnE,MAAM,CAAC,MAAM,iBAAiB,GAAG,YAAY,CAAC,gCAAgC,CAAC,CAAC;AAChF,MAAM,CAAC,MAAM,eAAe,GAAG,YAAY,CAAC,8BAA8B,CAAC,CAAC;AAC5E,MAAM,CAAC,MAAM,oBAAoB,GAAG,YAAY,CAC9C,mCAAmC,CACpC,CAAC;AACF,MAAM,CAAC,MAAM,qBAAqB,GAAG,YAAY,CAC/C,oCAAoC,CACrC,CAAC;AACF,MAAM,CAAC,MAAM,mBAAmB,GAAG,YAAY,CAC7C,kCAAkC,CACnC,CAAC;AACF,MAAM,CAAC,MAAM,mBAAmB,GAAG,YAAY,CAC7C,kCAAkC,CACnC,CAAC;AACF,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC,+BAA+B,CAAC,CAAC;AAE9E,wBAAwB;AACxB,MAAM,CAAC,MAAM,kBAAkB,GAAG,YAAY,CAAC,0BAA0B,CAAC,CAAC;AAC3E,MAAM,CAAC,MAAM,mBAAmB,GAAG,YAAY,CAAC,2BAA2B,CAAC,CAAC;AAC7E,MAAM,CAAC,MAAM,UAAU,GAAG,YAAY,CAAC,iBAAiB,CAAC,CAAC;AAC1D,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC,wBAAwB,CAAC,CAAC;AACvE,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC,wBAAwB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/templates/trellis/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Manage the user-level Apifox access token for Trellis joint debugging."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import getpass
|
|
8
|
+
import os
|
|
9
|
+
import shlex
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
TOKEN_NAME = "APIFOX_ACCESS_TOKEN"
|
|
15
|
+
GLOBAL_CONFIG_PATH = Path(
|
|
16
|
+
os.environ.get("TRELLIS_APIFOX_TOKEN_FILE", "~/.trellis/apifox.env")
|
|
17
|
+
).expanduser()
|
|
18
|
+
LEGACY_PROJECT_CONFIG_PATH = Path(".trellis/.runtime/apifox.env")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def parse_env_value(value: str) -> str:
|
|
22
|
+
try:
|
|
23
|
+
parts = shlex.split(value, comments=False, posix=True)
|
|
24
|
+
except ValueError:
|
|
25
|
+
return value.strip().strip("'\"")
|
|
26
|
+
if len(parts) == 1:
|
|
27
|
+
return parts[0]
|
|
28
|
+
return value.strip().strip("'\"")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def read_token_file(path: Path) -> str | None:
|
|
32
|
+
try:
|
|
33
|
+
content = path.read_text(encoding="utf-8")
|
|
34
|
+
except OSError:
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
for raw_line in content.splitlines():
|
|
38
|
+
line = raw_line.strip()
|
|
39
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
40
|
+
continue
|
|
41
|
+
key, value = line.split("=", 1)
|
|
42
|
+
if key.strip() != TOKEN_NAME:
|
|
43
|
+
continue
|
|
44
|
+
value = parse_env_value(value.strip())
|
|
45
|
+
return value or None
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def configured_source() -> str | None:
|
|
50
|
+
if os.environ.get(TOKEN_NAME):
|
|
51
|
+
return "environment"
|
|
52
|
+
if read_token_file(GLOBAL_CONFIG_PATH):
|
|
53
|
+
return "user config"
|
|
54
|
+
if read_token_file(LEGACY_PROJECT_CONFIG_PATH):
|
|
55
|
+
return "project-local config (legacy)"
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def cmd_check(_args: argparse.Namespace) -> int:
|
|
60
|
+
source = configured_source()
|
|
61
|
+
if source:
|
|
62
|
+
print(f"{TOKEN_NAME} is configured via {source}. Token value hidden.")
|
|
63
|
+
return 0
|
|
64
|
+
|
|
65
|
+
print(f"{TOKEN_NAME} is not configured.")
|
|
66
|
+
print("Run: python3 ./.trellis/scripts/apifox_token.py save")
|
|
67
|
+
print("Then paste the token into the hidden prompt.")
|
|
68
|
+
return 1
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def cmd_save(_args: argparse.Namespace) -> int:
|
|
72
|
+
token = getpass.getpass(f"Paste {TOKEN_NAME} (input hidden): ").strip()
|
|
73
|
+
if not token:
|
|
74
|
+
print("No token provided; nothing saved.", file=sys.stderr)
|
|
75
|
+
return 1
|
|
76
|
+
if "\n" in token or "\r" in token:
|
|
77
|
+
print("Token must be a single line.", file=sys.stderr)
|
|
78
|
+
return 1
|
|
79
|
+
|
|
80
|
+
GLOBAL_CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
81
|
+
try:
|
|
82
|
+
GLOBAL_CONFIG_PATH.parent.chmod(0o700)
|
|
83
|
+
except OSError:
|
|
84
|
+
pass
|
|
85
|
+
GLOBAL_CONFIG_PATH.write_text(
|
|
86
|
+
"# User-level Trellis config. Do not commit or print this value.\n"
|
|
87
|
+
f"{TOKEN_NAME}={shlex.quote(token)}\n",
|
|
88
|
+
encoding="utf-8",
|
|
89
|
+
)
|
|
90
|
+
GLOBAL_CONFIG_PATH.chmod(0o600)
|
|
91
|
+
print(f"{TOKEN_NAME} saved to {GLOBAL_CONFIG_PATH}. Token value hidden.")
|
|
92
|
+
return 0
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def cmd_path(_args: argparse.Namespace) -> int:
|
|
96
|
+
print(GLOBAL_CONFIG_PATH)
|
|
97
|
+
return 0
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
101
|
+
parser = argparse.ArgumentParser(
|
|
102
|
+
description="Manage the user-level Apifox access token for Trellis.",
|
|
103
|
+
)
|
|
104
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
105
|
+
|
|
106
|
+
check_parser = subparsers.add_parser("check", help="Check token presence.")
|
|
107
|
+
check_parser.set_defaults(func=cmd_check)
|
|
108
|
+
|
|
109
|
+
save_parser = subparsers.add_parser(
|
|
110
|
+
"save",
|
|
111
|
+
help="Save token to user-level config.",
|
|
112
|
+
)
|
|
113
|
+
save_parser.set_defaults(func=cmd_save)
|
|
114
|
+
|
|
115
|
+
path_parser = subparsers.add_parser("path", help="Print user-level config path.")
|
|
116
|
+
path_parser.set_defaults(func=cmd_path)
|
|
117
|
+
|
|
118
|
+
return parser
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def main() -> int:
|
|
122
|
+
parser = build_parser()
|
|
123
|
+
args = parser.parse_args()
|
|
124
|
+
return args.func(args)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
raise SystemExit(main())
|