@jonathanleelx/meddle 0.5.0-beta.9 → 0.5.1
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/CONFIG_STRUCTURE.md +9 -2
- package/README.md +18 -0
- package/bin/commands/mock/add.js +59 -21
- package/bin/commands/mock/delete.js +16 -24
- package/bin/commands/mock/index.js +59 -25
- package/bin/commands/mock/list.js +35 -18
- package/bin/commands/mock/toggle.js +17 -27
- package/bin/commands/mock/update.js +39 -30
- package/bin/commands/route/active.js +20 -3
- package/bin/commands/route/add.js +113 -94
- package/bin/commands/route/create.js +8 -1
- package/bin/commands/route/delete.js +87 -53
- package/bin/commands/route/index.js +74 -26
- package/bin/commands/route/list.js +2 -2
- package/bin/commands/route/preview.js +2 -2
- package/bin/commands/route/show.js +12 -3
- package/bin/commands/route/update.js +135 -92
- package/bin/commands/status.js +27 -11
- package/bin/commands/update.js +4 -0
- package/bin/doctor.js +8 -4
- package/bin/index.js +11 -7
- package/bin/lib/api-client.js +14 -2
- package/bin/lib/file-access.js +234 -35
- package/bin/lib/meddle-home.js +14 -1
- package/bin/lib/output.js +30 -4
- package/bin/lib/proxy-detect.js +99 -20
- package/bin/lib/proxy-or-file.js +93 -0
- package/bin/lib/route-write-args.js +150 -0
- package/bin/lib/rule-file-edit.js +263 -0
- package/bin/lib/ruleset-readonly.js +61 -0
- package/bin/lib/update-check.js +67 -6
- package/dist/cert.d.ts +2 -0
- package/dist/cert.d.ts.map +1 -1
- package/dist/cert.js +53 -1
- package/dist/cert.js.map +1 -1
- package/dist/core/browser.d.ts +10 -1
- package/dist/core/browser.d.ts.map +1 -1
- package/dist/core/browser.js +252 -36
- package/dist/core/browser.js.map +1 -1
- package/dist/core/config-diagnostics.d.ts.map +1 -1
- package/dist/core/config-diagnostics.js +6 -1
- package/dist/core/config-diagnostics.js.map +1 -1
- package/dist/core/custom-plugin-loader.d.ts.map +1 -1
- package/dist/core/custom-plugin-loader.js +2 -0
- package/dist/core/custom-plugin-loader.js.map +1 -1
- package/dist/core/mock-handler.d.ts +63 -0
- package/dist/core/mock-handler.d.ts.map +1 -1
- package/dist/core/mock-handler.js +641 -29
- package/dist/core/mock-handler.js.map +1 -1
- package/dist/core/mock-utils.d.ts +58 -0
- package/dist/core/mock-utils.d.ts.map +1 -0
- package/dist/core/mock-utils.js +167 -0
- package/dist/core/mock-utils.js.map +1 -0
- package/dist/core/plugin-bootstrap-runner.d.ts +1 -0
- package/dist/core/plugin-bootstrap-runner.d.ts.map +1 -1
- package/dist/core/plugin-bootstrap-runner.js +37 -1
- package/dist/core/plugin-bootstrap-runner.js.map +1 -1
- package/dist/core/plugin-source.d.ts +24 -0
- package/dist/core/plugin-source.d.ts.map +1 -0
- package/dist/core/plugin-source.js +47 -0
- package/dist/core/plugin-source.js.map +1 -0
- package/dist/core/proxy-record.d.ts +14 -0
- package/dist/core/proxy-record.d.ts.map +1 -1
- package/dist/core/proxy-record.js +39 -0
- package/dist/core/proxy-record.js.map +1 -1
- package/dist/core/rule-file-edit.d.ts +81 -0
- package/dist/core/rule-file-edit.d.ts.map +1 -0
- package/dist/core/rule-file-edit.js +243 -0
- package/dist/core/rule-file-edit.js.map +1 -0
- package/dist/core/static-server.d.ts.map +1 -1
- package/dist/core/static-server.js +15 -13
- package/dist/core/static-server.js.map +1 -1
- package/dist/core/types.d.ts +15 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/plugins/builtin/mock-plugin.d.ts.map +1 -1
- package/dist/plugins/builtin/mock-plugin.js +6 -5
- package/dist/plugins/builtin/mock-plugin.js.map +1 -1
- package/dist/server/agent/tools.d.ts.map +1 -1
- package/dist/server/agent/tools.js +24 -18
- package/dist/server/agent/tools.js.map +1 -1
- package/dist/server/app-info.d.ts +33 -0
- package/dist/server/app-info.d.ts.map +1 -0
- package/dist/server/app-info.js +116 -0
- package/dist/server/app-info.js.map +1 -0
- package/dist/server/config.d.ts +1 -0
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js +114 -16
- package/dist/server/config.js.map +1 -1
- package/dist/server/health.d.ts.map +1 -1
- package/dist/server/health.js +8 -0
- package/dist/server/health.js.map +1 -1
- package/dist/server/index.d.ts +16 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +10 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/json-parse-error.d.ts +7 -0
- package/dist/server/json-parse-error.d.ts.map +1 -0
- package/dist/server/json-parse-error.js +28 -0
- package/dist/server/json-parse-error.js.map +1 -0
- package/dist/server/mocks.d.ts +26 -0
- package/dist/server/mocks.d.ts.map +1 -1
- package/dist/server/mocks.js +110 -13
- package/dist/server/mocks.js.map +1 -1
- package/dist/server/plugins.d.ts.map +1 -1
- package/dist/server/plugins.js +56 -11
- package/dist/server/plugins.js.map +1 -1
- package/dist/server/rule-files.d.ts +36 -0
- package/dist/server/rule-files.d.ts.map +1 -1
- package/dist/server/rule-files.js +388 -107
- package/dist/server/rule-files.js.map +1 -1
- package/dist/server/ruleset-lock.d.ts +62 -0
- package/dist/server/ruleset-lock.d.ts.map +1 -0
- package/dist/server/ruleset-lock.js +234 -0
- package/dist/server/ruleset-lock.js.map +1 -0
- package/index.js +128 -16
- package/mcp-server.js +108 -93
- package/package.json +1 -1
- package/plugins/builtin/mock-plugin.ts +9 -5
- package/web/dist/assets/alert-B514Frte.js +1 -0
- package/web/dist/assets/arrow-right-BNum_060.js +1 -0
- package/web/dist/assets/{body-diff-view-B-4XWmL6.js → body-diff-view-SfLCLzs9.js} +2 -2
- package/web/dist/assets/button-BFNlGZ5p.js +1 -0
- package/web/dist/assets/check-BkfHQ6re.js +1 -0
- package/web/dist/assets/checkbox-CQyFsZzS.js +1 -0
- package/web/dist/assets/chevron-down-DHwUtL4O.js +1 -0
- package/web/dist/assets/{circle-alert-Q8cZk9Rv.js → circle-alert-zMagrFuI.js} +1 -1
- package/web/dist/assets/{circle-x-C9USji_R.js → circle-x-BEkinhez.js} +1 -1
- package/web/dist/assets/code-DD-AYxxu.js +1 -0
- package/web/dist/assets/{copy-BYjq1hdN.js → copy-BRnscYy1.js} +1 -1
- package/web/dist/assets/detail-panel-DZRKJG2W.js +4 -0
- package/web/dist/assets/dist-BQN0rzUS.js +1 -0
- package/web/dist/assets/{dist-CEd_wnRC.js → dist-C-E2R2jk.js} +1 -1
- package/web/dist/assets/{dist-C6eaYCUo.js → dist-CFZ4xMSF.js} +1 -1
- package/web/dist/assets/{dist-DWmDaXVN.js → dist-CRmX_NvH.js} +1 -1
- package/web/dist/assets/{dist-C8RHq5At.js → dist-DLQKRjuW.js} +1 -1
- package/web/dist/assets/{dist-Blyulj4_.js → dist-LiAcI8CK.js} +1 -1
- package/web/dist/assets/{dist-Ce4ujolc.js → dist-fzPL-8F9.js} +1 -1
- package/web/dist/assets/{dist-DoE7uybN.js → dist-wv9ebJK_.js} +1 -1
- package/web/dist/assets/editor-pane-actions-_ckwcCII.js +1 -0
- package/web/dist/assets/{es2015-DG46NGN7.js → es2015-DPJTBYa9.js} +1 -1
- package/web/dist/assets/global-panel-shell-Iir-c_pQ.js +6 -0
- package/web/dist/assets/health-panel-Dmf-EhJz.js +1 -0
- package/web/dist/assets/index-C4dIj1nT.js +21 -0
- package/web/dist/assets/index-CdYMl1M7.css +2 -0
- package/web/dist/assets/{mobile-proxy-panel-tU0NyJ3P.js → mobile-proxy-panel-CFcwa11A.js} +6 -6
- package/web/dist/assets/mock-config-CGl50MIr.js +1 -0
- package/web/dist/assets/mock-editor-panel-xcCk23oH.js +20 -0
- package/web/dist/assets/mocks-file-missing-note-CW_ONVLA.js +1 -0
- package/web/dist/assets/monaco-editor-BkP7envQ.js +11 -0
- package/web/dist/assets/pane-bar-5j9Nf7W_.js +1 -0
- package/web/dist/assets/pane-split-C0OSe365.js +1 -0
- package/web/dist/assets/panel-heading-izT5q0ZN.js +1 -0
- package/web/dist/assets/{pencil-line-Cqe41E4h.js → pencil-line-CEZ7D-de.js} +1 -1
- package/web/dist/assets/plugin-code-editor-DKE9dFEE.js +2 -0
- package/web/dist/assets/plugin-config-Cg09TsuV.js +2 -0
- package/web/dist/assets/plugin-generator-CT1f1tu0.js +2 -0
- package/web/dist/assets/plugin-test-dialog-BbLIMnkJ.js +7 -0
- package/web/dist/assets/plus-ixZQRprm.js +1 -0
- package/web/dist/assets/{refresh-cw-CG3HaiQT.js → refresh-cw-BfLoHqg5.js} +1 -1
- package/web/dist/assets/rotate-ccw-CRguJya4.js +1 -0
- package/web/dist/assets/route-preview-CKO7nF4W.js +1 -0
- package/web/dist/assets/{rule-ai-assistant-panel-DMh0F9dr.js → rule-ai-assistant-panel-CtfUqdYG.js} +2 -2
- package/web/dist/assets/rule-config-Cb4ejIXI.js +15 -0
- package/web/dist/assets/rule-overview-panel-xtHTS27O.js +1 -0
- package/web/dist/assets/{server-Cpj4KNwN.js → server-eYXq3PbE.js} +1 -1
- package/web/dist/assets/settings-panel-CpQuE6nr.js +1 -0
- package/web/dist/assets/{shield-check-OqUmVLQt.js → shield-check-hQXjdfZ7.js} +1 -1
- package/web/dist/assets/smartphone-D4Q7cQfm.js +1 -0
- package/web/dist/assets/switch-94vgsMHm.js +1 -0
- package/web/dist/assets/{syntax-highlight-Dk0-ewLI.js → syntax-highlight-BFgN_C0Y.js} +1 -1
- package/web/dist/assets/{table-2-BR5e3z8m.js → table-2-DvGHhMm4.js} +1 -1
- package/web/dist/assets/table-DaXD6jJv.js +1 -0
- package/web/dist/assets/textarea-9wB8Hsz-.js +1 -0
- package/web/dist/assets/toast-9yeTf7d9.js +1 -0
- package/web/dist/assets/use-mocks-file-status-BUIUJ4oQ.js +1 -0
- package/web/dist/assets/{wand-sparkles-CJ7fSnom.js → wand-sparkles-Bmt4dDOf.js} +1 -1
- package/web/dist/assets/{wifi-CCLKyySQ.js → wifi-Cjj_Br1u.js} +1 -1
- package/web/dist/assets/{zap-DR-xiUMe.js → zap-BuOtogAv.js} +1 -1
- package/web/dist/index.html +16 -10
- package/web/dist/assets/alert-DCvlZAiv.js +0 -1
- package/web/dist/assets/arrow-right-B4Mrlj2-.js +0 -1
- package/web/dist/assets/button-BTToMaBW.js +0 -1
- package/web/dist/assets/checkbox-D1Z9DtjF.js +0 -1
- package/web/dist/assets/chevron-down-CPngAx4M.js +0 -1
- package/web/dist/assets/chevron-right-V1DGIDbL.js +0 -1
- package/web/dist/assets/code-5_eJ_XpJ.js +0 -1
- package/web/dist/assets/code-fixer-D48sCDtp.js +0 -20
- package/web/dist/assets/detail-panel-BSFz9CUw.js +0 -4
- package/web/dist/assets/file-system-access-_og-Tg9o.js +0 -1
- package/web/dist/assets/global-panel-shell-5cJneeis.js +0 -6
- package/web/dist/assets/health-panel-BS3L2e5d.js +0 -1
- package/web/dist/assets/index-CvTR1IVi.js +0 -18
- package/web/dist/assets/index-bfV7sxx5.css +0 -2
- package/web/dist/assets/mock-config-CULJP6Vt.js +0 -1
- package/web/dist/assets/mock-editor-panel-9wlDaySA.js +0 -1
- package/web/dist/assets/monaco-editor-BttaUoOz.js +0 -11
- package/web/dist/assets/plugin-code-editor-BAIBp4lg.js +0 -2
- package/web/dist/assets/plugin-config-ButUV2SZ.js +0 -1
- package/web/dist/assets/plugin-generator-uceUPSCv.js +0 -2
- package/web/dist/assets/plugin-test-dialog-BrJ4HfRX.js +0 -7
- package/web/dist/assets/plus-tVxSydJ8.js +0 -1
- package/web/dist/assets/rotate-ccw-CAYM6Vv0.js +0 -1
- package/web/dist/assets/route-preview-C4cGrS1-.js +0 -1
- package/web/dist/assets/rule-config-BTTGNwMs.js +0 -15
- package/web/dist/assets/rule-overview-panel-SFR2OloL.js +0 -1
- package/web/dist/assets/select-aBXQ0zNa.js +0 -1
- package/web/dist/assets/settings-panel-BMReDG8b.js +0 -1
- package/web/dist/assets/smartphone-DKeDuJTU.js +0 -1
- package/web/dist/assets/switch-Bes_YiJu.js +0 -1
- package/web/dist/assets/table-nW7uQVcl.js +0 -1
- package/web/dist/assets/textarea-BSI0rnW6.js +0 -1
- /package/web/dist/assets/{babel-DCr4VA1c.js → babel-bwZm0qi3.js} +0 -0
- /package/web/dist/assets/{clipboard-VhPXNN5m.js → clipboard-Bt1qY01V.js} +0 -0
- /package/web/dist/assets/{estree-qO_m2Qaw.js → estree-MatbZDwq.js} +0 -0
- /package/web/dist/assets/{html-VDGahtyk.js → html-CCrsbFYX.js} +0 -0
- /package/web/dist/assets/{postcss-BUm8EKMw.js → postcss-BxN7Gr8n.js} +0 -0
- /package/web/dist/assets/{standalone-Bj8SSd8Q.js → standalone-DsnFnsqM.js} +0 -0
|
@@ -57,7 +57,7 @@ async function run() {
|
|
|
57
57
|
const content = await fetch(`${baseUrl}/api/rule-files/${encodeURIComponent(ruleFileArg)}/content`)
|
|
58
58
|
.then(r => r.ok ? r.text() : null)
|
|
59
59
|
.catch(() => null)
|
|
60
|
-
if (
|
|
60
|
+
if (content == null) {
|
|
61
61
|
output.error(`Rule file not found: ${ruleFileArg}`)
|
|
62
62
|
process.exit(1)
|
|
63
63
|
}
|
|
@@ -91,7 +91,7 @@ async function run() {
|
|
|
91
91
|
if (ruleFileArg) {
|
|
92
92
|
// Use specified rule file
|
|
93
93
|
const content = getRuleFileContent(ruleFileArg)
|
|
94
|
-
if (
|
|
94
|
+
if (content == null) {
|
|
95
95
|
output.error(`Rule file not found: ${ruleFileArg}`)
|
|
96
96
|
process.exit(1)
|
|
97
97
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
const output = require('../../lib/output')
|
|
6
6
|
const { isProxyRunning } = require('../../lib/proxy-detect')
|
|
7
7
|
const { apiGet } = require('../../lib/api-client')
|
|
8
|
-
const { getRuleFileContent, parseEprcWithExclusions } = require('../../lib/file-access')
|
|
8
|
+
const { getRuleFileContent, parseEprcWithExclusions, isRuleFileReadonly } = require('../../lib/file-access')
|
|
9
9
|
|
|
10
10
|
// Parse arguments (skip 'route' and 'show' from argv)
|
|
11
11
|
const args = process.argv.slice(4)
|
|
@@ -26,33 +26,40 @@ async function run() {
|
|
|
26
26
|
let content = null
|
|
27
27
|
let ruleMap = {}
|
|
28
28
|
let excludeMap = {}
|
|
29
|
+
// Issue #137: report the lock state (read-only for the CLI either way).
|
|
30
|
+
let readonly = false
|
|
29
31
|
|
|
30
32
|
if (running) {
|
|
31
33
|
try {
|
|
32
34
|
content = await apiGet(`/api/rule-files/${encodeURIComponent(fileName)}/content`)
|
|
35
|
+
const files = await apiGet('/api/rule-files')
|
|
36
|
+
const info = Array.isArray(files) ? files.find((f) => f && f.name === fileName) : null
|
|
37
|
+
readonly = Boolean(info && info.readonly)
|
|
33
38
|
const parsed = parseEprcWithExclusions(typeof content === 'string' ? content : String(content))
|
|
34
39
|
ruleMap = parsed.ruleMap
|
|
35
40
|
excludeMap = parsed.excludeMap
|
|
36
41
|
} catch (e) {
|
|
37
42
|
// Fallback to file mode
|
|
38
43
|
content = getRuleFileContent(fileName)
|
|
39
|
-
if (
|
|
44
|
+
if (content == null) {
|
|
40
45
|
output.error(`Route file not found: ${fileName}`)
|
|
41
46
|
process.exit(1)
|
|
42
47
|
}
|
|
43
48
|
const parsed = parseEprcWithExclusions(content)
|
|
44
49
|
ruleMap = parsed.ruleMap
|
|
45
50
|
excludeMap = parsed.excludeMap
|
|
51
|
+
readonly = isRuleFileReadonly(fileName)
|
|
46
52
|
}
|
|
47
53
|
} else {
|
|
48
54
|
content = getRuleFileContent(fileName)
|
|
49
|
-
if (
|
|
55
|
+
if (content == null) {
|
|
50
56
|
output.error(`Route file not found: ${fileName}`)
|
|
51
57
|
process.exit(1)
|
|
52
58
|
}
|
|
53
59
|
const parsed = parseEprcWithExclusions(content)
|
|
54
60
|
ruleMap = parsed.ruleMap
|
|
55
61
|
excludeMap = parsed.excludeMap
|
|
62
|
+
readonly = isRuleFileReadonly(fileName)
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
// Build display rules (handle [marker] syntax)
|
|
@@ -76,6 +83,7 @@ async function run() {
|
|
|
76
83
|
if (jsonFlag) {
|
|
77
84
|
output.jsonRaw({
|
|
78
85
|
file: fileName,
|
|
86
|
+
readonly,
|
|
79
87
|
rules: displayRules,
|
|
80
88
|
ruleCount: Object.keys(displayRules).length,
|
|
81
89
|
exclusionCount: totalExclusions
|
|
@@ -84,6 +92,7 @@ async function run() {
|
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
output.header(`Route File: ${fileName}`)
|
|
95
|
+
output.kv('Read-only', readonly ? 'Yes (unlock in the Meddle UI)' : 'No')
|
|
87
96
|
output.kv('Rules', Object.keys(displayRules).length)
|
|
88
97
|
if (totalExclusions > 0) {
|
|
89
98
|
output.kv('Exclusions', totalExclusions)
|
|
@@ -1,126 +1,169 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* meddle route update - Update a rule in a route file
|
|
3
|
+
*
|
|
4
|
+
* Issue #149: `--exclusions=` clears a rule's exclusions. Omitting the flag
|
|
5
|
+
* (and any !path in the pattern) still keeps them.
|
|
3
6
|
*/
|
|
4
7
|
|
|
5
8
|
const output = require('../../lib/output')
|
|
6
9
|
const { isProxyRunning } = require('../../lib/proxy-detect')
|
|
7
10
|
const { apiGet, apiPut } = require('../../lib/api-client')
|
|
8
|
-
const { getRuleFileContent, saveRuleFileContent
|
|
11
|
+
const { getRuleFileContent, saveRuleFileContent } = require('../../lib/file-access')
|
|
12
|
+
const { updateRouteRule, patternKey, parseRuleLine } = require('../../lib/rule-file-edit')
|
|
13
|
+
const { isRulesetReadonlyError, exitRulesetReadonly } = require('../../lib/ruleset-readonly')
|
|
14
|
+
const {
|
|
15
|
+
parseRouteWriteArgs,
|
|
16
|
+
parsePatternArg,
|
|
17
|
+
resolveExclusions,
|
|
18
|
+
UPDATE_HELP,
|
|
19
|
+
} = require('../../lib/route-write-args')
|
|
9
20
|
|
|
10
|
-
// Parse arguments (skip 'route' and 'update' from argv)
|
|
11
21
|
const args = process.argv.slice(4)
|
|
12
|
-
|
|
22
|
+
|
|
23
|
+
function fail(message, jsonFlag) {
|
|
24
|
+
if (jsonFlag) {
|
|
25
|
+
console.log(JSON.stringify({ success: false, error: { message } }))
|
|
26
|
+
console.error(message)
|
|
27
|
+
} else {
|
|
28
|
+
output.setJsonMode(false)
|
|
29
|
+
output.error(message)
|
|
30
|
+
}
|
|
31
|
+
process.exit(1)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let parsed
|
|
35
|
+
try {
|
|
36
|
+
parsed = parseRouteWriteArgs(args)
|
|
37
|
+
} catch (e) {
|
|
38
|
+
fail(e.message, args.includes('--json'))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (parsed.help) {
|
|
42
|
+
console.log(UPDATE_HELP.trim())
|
|
43
|
+
process.exit(0)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const jsonFlag = parsed.json
|
|
13
47
|
output.setJsonMode(jsonFlag)
|
|
14
48
|
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const pattern = positionalArgs[1]
|
|
19
|
-
const newTarget = positionalArgs[2]
|
|
49
|
+
const fileName = parsed.positional[0]
|
|
50
|
+
const pattern = parsed.positional[1]
|
|
51
|
+
const newTarget = parsed.positional[2]
|
|
20
52
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
53
|
+
/** Set once arguments are valid. `explicit` means the user asked to set exclusions, including clear. */
|
|
54
|
+
let explicitExclusions = false
|
|
55
|
+
let appliedExclusions = []
|
|
56
|
+
|
|
57
|
+
function planUpdate() {
|
|
58
|
+
const { patterns, exclusions: patternExclusions } = parsePatternArg(pattern)
|
|
59
|
+
if (patterns.length === 0) {
|
|
60
|
+
fail('Missing required argument: <pattern>', jsonFlag)
|
|
25
61
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
62
|
+
const resolved = resolveExclusions(patternExclusions, parsed.exclusions)
|
|
63
|
+
explicitExclusions = resolved.explicit
|
|
64
|
+
appliedExclusions = resolved.exclusions || []
|
|
65
|
+
return { patterns, exclusions: resolved.explicit ? resolved.exclusions : undefined }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Issue #145: rewrite only the line(s) holding this pattern. The rule keeps its
|
|
70
|
+
* own `!exclusions` unless new ones are given. Issue #149: `--exclusions=` passes
|
|
71
|
+
* `[]`, which removes `!path` from that line. Every other line keeps its bytes.
|
|
72
|
+
*/
|
|
73
|
+
function applyUpdate(text, plan) {
|
|
74
|
+
const target = newTarget ? newTarget.trim() : undefined
|
|
75
|
+
let next = text
|
|
76
|
+
for (const p of plan.patterns) {
|
|
77
|
+
const result = updateRouteRule(next, { pattern: p, target, exclusions: plan.exclusions })
|
|
78
|
+
if (!result.found) {
|
|
79
|
+
fail(`Pattern not found: ${pattern.trim()}`, jsonFlag)
|
|
80
|
+
}
|
|
81
|
+
next = result.content
|
|
29
82
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
83
|
+
return next
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Target token actually written, for feedback when the user did not pass one. */
|
|
87
|
+
function targetOnDisk(content, patternToken) {
|
|
88
|
+
const key = patternKey(patternToken)
|
|
89
|
+
const lines = content.split(/\r?\n/)
|
|
90
|
+
for (const line of lines) {
|
|
91
|
+
const parsedLine = parseRuleLine(line)
|
|
92
|
+
if (parsedLine && parsedLine.patterns.some((p) => patternKey(p) === key)) return parsedLine.target
|
|
33
93
|
}
|
|
94
|
+
return ''
|
|
95
|
+
}
|
|
34
96
|
|
|
35
|
-
|
|
97
|
+
function emitUpdated({ fileOnly, target }) {
|
|
98
|
+
const payload = { success: true, file: fileName, pattern: pattern.trim(), newTarget: target }
|
|
99
|
+
if (explicitExclusions) payload.exclusions = appliedExclusions
|
|
100
|
+
if (fileOnly) {
|
|
101
|
+
payload.fileOnly = true
|
|
102
|
+
payload.warning = output.FILE_ONLY_WARNING
|
|
103
|
+
}
|
|
104
|
+
if (jsonFlag) {
|
|
105
|
+
output.jsonRaw(payload)
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
output.header('Route Rule Updated')
|
|
109
|
+
output.success(`${pattern.trim()} -> ${target}`)
|
|
110
|
+
if (explicitExclusions) {
|
|
111
|
+
output.kv('Exclusions', appliedExclusions.length ? appliedExclusions.map((e) => `!${e}`).join(' ') : 'cleared')
|
|
112
|
+
}
|
|
113
|
+
output.kv('File', fileName)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function run() {
|
|
117
|
+
if (!fileName) fail('Missing required argument: <file>', jsonFlag)
|
|
118
|
+
if (!pattern) fail('Missing required argument: <pattern>', jsonFlag)
|
|
119
|
+
if (parsed.positional.length > 3) fail(`Unexpected argument: ${parsed.positional[3]}`, jsonFlag)
|
|
120
|
+
|
|
121
|
+
let plan
|
|
122
|
+
try {
|
|
123
|
+
plan = planUpdate()
|
|
124
|
+
} catch (e) {
|
|
125
|
+
fail(e.message, jsonFlag)
|
|
126
|
+
}
|
|
127
|
+
if (!newTarget && !explicitExclusions) fail('Missing required argument: <target>', jsonFlag)
|
|
36
128
|
|
|
37
|
-
const
|
|
38
|
-
const tgt = newTarget.trim()
|
|
129
|
+
const running = await isProxyRunning()
|
|
39
130
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
131
|
+
const finish = (content, fileOnly) => {
|
|
132
|
+
const target = newTarget ? newTarget.trim() : targetOnDisk(content, plan.patterns[0])
|
|
133
|
+
emitUpdated({ fileOnly, target })
|
|
134
|
+
}
|
|
43
135
|
|
|
44
136
|
if (running) {
|
|
45
137
|
try {
|
|
46
138
|
let content = await apiGet(`/api/rule-files/${encodeURIComponent(fileName)}/content`)
|
|
47
139
|
const text = typeof content === 'string' ? content : String(content)
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
if (!Object.prototype.hasOwnProperty.call(ruleMap, internalKey)) {
|
|
51
|
-
output.error(`Pattern not found: ${pat}`)
|
|
52
|
-
process.exit(1)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
ruleMap[internalKey] = bm ? tgt + bm[0] : tgt
|
|
56
|
-
const newContent = ruleMapToEprcText(ruleMap)
|
|
140
|
+
const newContent = applyUpdate(text, plan)
|
|
57
141
|
await apiPut(`/api/rule-files/${encodeURIComponent(fileName)}/content`, { content: newContent })
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
output.jsonRaw({ success: true, file: fileName, pattern: pat, newTarget: tgt })
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
output.header('Route Rule Updated')
|
|
65
|
-
output.success(`${pat} -> ${tgt}`)
|
|
66
|
-
output.kv('File', fileName)
|
|
142
|
+
finish(newContent, false)
|
|
143
|
+
return
|
|
67
144
|
} catch (e) {
|
|
145
|
+
// Issue #137: a locked ruleset is a final answer, never a cue to write the file directly.
|
|
146
|
+
if (isRulesetReadonlyError(e)) exitRulesetReadonly(e, { json: jsonFlag, ruleFile: fileName })
|
|
68
147
|
// Fallback to file mode
|
|
69
148
|
const content = getRuleFileContent(fileName)
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
process.exit(1)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const ruleMap = parseEprc(content)
|
|
76
|
-
if (!Object.prototype.hasOwnProperty.call(ruleMap, internalKey)) {
|
|
77
|
-
output.error(`Pattern not found: ${pat}`)
|
|
78
|
-
process.exit(1)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
ruleMap[internalKey] = bm ? tgt + bm[0] : tgt
|
|
82
|
-
const newContent = ruleMapToEprcText(ruleMap)
|
|
149
|
+
if (content == null) fail(`Route file not found: ${fileName}`, jsonFlag)
|
|
150
|
+
const newContent = applyUpdate(content, plan)
|
|
83
151
|
saveRuleFileContent(fileName, newContent)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
output.jsonRaw({ success: true, file: fileName, pattern: pat, newTarget: tgt })
|
|
87
|
-
return
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
output.header('Route Rule Updated')
|
|
91
|
-
output.success(`${pat} -> ${tgt}`)
|
|
92
|
-
output.kv('File', fileName)
|
|
93
|
-
}
|
|
94
|
-
} else {
|
|
95
|
-
// File mode
|
|
96
|
-
const content = getRuleFileContent(fileName)
|
|
97
|
-
if (!content) {
|
|
98
|
-
output.error(`Route file not found: ${fileName}`)
|
|
99
|
-
process.exit(1)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const ruleMap = parseEprc(content)
|
|
103
|
-
if (!Object.prototype.hasOwnProperty.call(ruleMap, internalKey)) {
|
|
104
|
-
output.error(`Pattern not found: ${pat}`)
|
|
105
|
-
process.exit(1)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
ruleMap[internalKey] = bm ? tgt + bm[0] : tgt
|
|
109
|
-
const newContent = ruleMapToEprcText(ruleMap)
|
|
110
|
-
saveRuleFileContent(fileName, newContent)
|
|
111
|
-
|
|
112
|
-
if (jsonFlag) {
|
|
113
|
-
output.jsonRaw({ success: true, file: fileName, pattern: pat, newTarget: tgt })
|
|
152
|
+
output.warnFileOnlyMode()
|
|
153
|
+
finish(newContent, true)
|
|
114
154
|
return
|
|
115
155
|
}
|
|
116
|
-
|
|
117
|
-
output.header('Route Rule Updated')
|
|
118
|
-
output.success(`${pat} -> ${tgt}`)
|
|
119
|
-
output.kv('File', fileName)
|
|
120
156
|
}
|
|
157
|
+
|
|
158
|
+
const content = getRuleFileContent(fileName)
|
|
159
|
+
if (content == null) fail(`Route file not found: ${fileName}`, jsonFlag)
|
|
160
|
+
const newContent = applyUpdate(content, plan)
|
|
161
|
+
saveRuleFileContent(fileName, newContent)
|
|
162
|
+
output.warnFileOnlyMode()
|
|
163
|
+
finish(newContent, true)
|
|
121
164
|
}
|
|
122
165
|
|
|
123
166
|
run().catch(e => {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
})
|
|
167
|
+
if (isRulesetReadonlyError(e)) exitRulesetReadonly(e, { json: jsonFlag, ruleFile: fileName })
|
|
168
|
+
fail(e.message, jsonFlag)
|
|
169
|
+
})
|
package/bin/commands/status.js
CHANGED
|
@@ -22,6 +22,22 @@ async function run() {
|
|
|
22
22
|
let activeRoutes = 0
|
|
23
23
|
let health = null
|
|
24
24
|
|
|
25
|
+
let mocksError = null
|
|
26
|
+
|
|
27
|
+
function readMocksFromFile() {
|
|
28
|
+
try {
|
|
29
|
+
const mocks = loadMockRules()
|
|
30
|
+
mockCount = mocks.length
|
|
31
|
+
activeMocks = mocks.filter(m => m.enabled).length
|
|
32
|
+
} catch (err) {
|
|
33
|
+
mocksError = err && err.code === 'MOCKS_FILE_INVALID'
|
|
34
|
+
? 'Mock 规则文件格式错误'
|
|
35
|
+
: (err && err.message ? String(err.message) : String(err))
|
|
36
|
+
mockCount = 0
|
|
37
|
+
activeMocks = 0
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
25
41
|
if (running) {
|
|
26
42
|
try {
|
|
27
43
|
// Get mock rules from API
|
|
@@ -36,19 +52,13 @@ async function run() {
|
|
|
36
52
|
health = await apiGet('/api/health')
|
|
37
53
|
} catch (_) {
|
|
38
54
|
// Fallback to file access
|
|
39
|
-
|
|
40
|
-
mockCount = mocks.length
|
|
41
|
-
activeMocks = mocks.filter(m => m.enabled).length
|
|
42
|
-
|
|
55
|
+
readMocksFromFile()
|
|
43
56
|
routeFiles = listRuleFiles()
|
|
44
57
|
activeRoutes = routeFiles.filter(f => f.enabled).length
|
|
45
58
|
}
|
|
46
59
|
} else {
|
|
47
60
|
// File mode
|
|
48
|
-
|
|
49
|
-
mockCount = mocks.length
|
|
50
|
-
activeMocks = mocks.filter(m => m.enabled).length
|
|
51
|
-
|
|
61
|
+
readMocksFromFile()
|
|
52
62
|
routeFiles = listRuleFiles()
|
|
53
63
|
activeRoutes = routeFiles.filter(f => f.enabled).length
|
|
54
64
|
}
|
|
@@ -61,7 +71,9 @@ async function run() {
|
|
|
61
71
|
proxyUrl: url,
|
|
62
72
|
running,
|
|
63
73
|
health,
|
|
64
|
-
mocks:
|
|
74
|
+
mocks: mocksError
|
|
75
|
+
? { total: mockCount, active: activeMocks, error: mocksError }
|
|
76
|
+
: { total: mockCount, active: activeMocks },
|
|
65
77
|
routes: { files: routeFiles.length, activeFiles: activeRoutes, totalRules: totalRouteRules, totalExclusions }
|
|
66
78
|
})
|
|
67
79
|
return
|
|
@@ -81,8 +93,12 @@ async function run() {
|
|
|
81
93
|
}
|
|
82
94
|
|
|
83
95
|
output.section('Mock Rules')
|
|
84
|
-
|
|
85
|
-
|
|
96
|
+
if (mocksError) {
|
|
97
|
+
output.warning(mocksError)
|
|
98
|
+
} else {
|
|
99
|
+
output.kv('Total', mockCount)
|
|
100
|
+
output.kv('Active', activeMocks)
|
|
101
|
+
}
|
|
86
102
|
|
|
87
103
|
output.section('Route Rules')
|
|
88
104
|
output.kv('Files', routeFiles.length)
|
package/bin/commands/update.js
CHANGED
|
@@ -77,6 +77,10 @@ meddle update - 检查并升级 meddle
|
|
|
77
77
|
meddle update --auto on|off 启用/禁用自动更新 (默认关闭)
|
|
78
78
|
meddle update status 查看安装方式、当前版本和自动更新开关
|
|
79
79
|
meddle update --help, -h 显示帮助
|
|
80
|
+
|
|
81
|
+
私有仓库二进制更新需要设置 GH_TOKEN 或 GITHUB_TOKEN(优先 GH_TOKEN;
|
|
82
|
+
权限:repo,或 fine-grained contents:read)。也可:
|
|
83
|
+
gh release download -R JonathanLee-LX/meddle -p 'meddle-*'
|
|
80
84
|
`)
|
|
81
85
|
}
|
|
82
86
|
|
package/bin/doctor.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
const fs = require('fs')
|
|
8
8
|
const path = require('path')
|
|
9
9
|
const chalk = require('chalk')
|
|
10
|
-
const { resolveMeddleHome } = require('./lib/meddle-home')
|
|
10
|
+
const { resolveMeddleHome, resolveConfiguredMocksPath } = require('./lib/meddle-home')
|
|
11
11
|
|
|
12
12
|
const meddleDir = resolveMeddleHome()
|
|
13
13
|
const settingsPath = path.resolve(meddleDir, 'settings.json')
|
|
@@ -183,10 +183,14 @@ function checkMocksFile(settings) {
|
|
|
183
183
|
|
|
184
184
|
let mocksPath = defaultMocksPath
|
|
185
185
|
|
|
186
|
-
//
|
|
186
|
+
// Same resolution as the proxy: ~ expands, relative paths use MEDDLE_HOME.
|
|
187
187
|
if (settings && settings.mocksFilePath) {
|
|
188
|
-
|
|
189
|
-
|
|
188
|
+
const resolved = resolveConfiguredMocksPath(settings.mocksFilePath, meddleDir)
|
|
189
|
+
mocksPath = resolved || defaultMocksPath
|
|
190
|
+
printInfo(`使用自定义路径: ${settings.mocksFilePath}`)
|
|
191
|
+
if (resolved && resolved !== settings.mocksFilePath) {
|
|
192
|
+
printInfo(`解析后路径: ${resolved}`)
|
|
193
|
+
}
|
|
190
194
|
} else {
|
|
191
195
|
printInfo(`使用默认路径: ${mocksPath}`)
|
|
192
196
|
}
|
package/bin/index.js
CHANGED
|
@@ -59,13 +59,13 @@ if (command === 'mock' || command === 'route' || command === 'session') {
|
|
|
59
59
|
// No command or only flags - start proxy (default behavior)
|
|
60
60
|
require('../index.js')
|
|
61
61
|
} else {
|
|
62
|
-
console.
|
|
63
|
-
showHelp()
|
|
62
|
+
console.error(`Unknown command: ${command}`)
|
|
63
|
+
showHelp(process.stderr)
|
|
64
64
|
process.exit(1)
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
function showHelp() {
|
|
68
|
-
|
|
67
|
+
function showHelp(stream = process.stdout) {
|
|
68
|
+
stream.write(`
|
|
69
69
|
Meddle - 开发代理工具
|
|
70
70
|
|
|
71
71
|
用法:
|
|
@@ -106,8 +106,10 @@ Route 命令:
|
|
|
106
106
|
meddle route active 查看当前激活的路由文件
|
|
107
107
|
meddle route active set <file> 设置激活的路由文件
|
|
108
108
|
meddle route create <name> 创建路由文件
|
|
109
|
-
meddle route add <file> <pattern> <target>
|
|
110
|
-
|
|
109
|
+
meddle route add <file> <pattern> <target> [--exclusions=]
|
|
110
|
+
添加路由规则;--exclusions= 清空排除项
|
|
111
|
+
meddle route update <file> <pattern> [<target>] [--exclusions=]
|
|
112
|
+
更新路由规则;--exclusions= 清空排除项
|
|
111
113
|
meddle route delete <file> <pattern> 删除路由规则
|
|
112
114
|
|
|
113
115
|
全局选项:
|
|
@@ -137,8 +139,10 @@ Route 命令:
|
|
|
137
139
|
meddle route preview "https://api.example.com/v1/users" # 预览转发目标
|
|
138
140
|
meddle route preview "https://cdn.com/assets/js/app.js" --file dev-rules --json
|
|
139
141
|
meddle route add dev-rules "api.test.com" "localhost:3000"
|
|
142
|
+
meddle route add dev-rules "api.test.com" "localhost:3000" --exclusions=
|
|
143
|
+
meddle route update dev-rules "api.test.com" --exclusions=
|
|
140
144
|
meddle route active set beta-rules
|
|
141
145
|
|
|
142
146
|
更多信息请访问: https://github.com/JonathanLee-LX/meddle
|
|
143
|
-
|
|
147
|
+
`)
|
|
144
148
|
}
|
package/bin/lib/api-client.js
CHANGED
|
@@ -25,14 +25,26 @@ async function apiRequest(method, pathname, body, timeoutMs = DEFAULT_TIMEOUT) {
|
|
|
25
25
|
|
|
26
26
|
if (!response.ok) {
|
|
27
27
|
const text = await response.text()
|
|
28
|
-
|
|
28
|
+
const err = new Error(text || response.statusText)
|
|
29
|
+
err.status = response.status
|
|
30
|
+
// Structured errors (e.g. issue #137 RULESET_READONLY) keep their code.
|
|
31
|
+
try {
|
|
32
|
+
const data = JSON.parse(text)
|
|
33
|
+
if (data && typeof data === 'object') {
|
|
34
|
+
if (typeof data.code === 'string') err.code = data.code
|
|
35
|
+
if (typeof data.ruleFile === 'string') err.ruleFile = data.ruleFile
|
|
36
|
+
}
|
|
37
|
+
} catch (_) {}
|
|
38
|
+
throw err
|
|
29
39
|
}
|
|
30
40
|
|
|
31
41
|
const contentType = response.headers.get('content-type')
|
|
32
42
|
if (contentType && contentType.includes('application/json')) {
|
|
33
43
|
return response.json()
|
|
34
44
|
}
|
|
35
|
-
|
|
45
|
+
// Issue #145: `Response.text()` strips a leading UTF-8 BOM, so a rule file
|
|
46
|
+
// read and written back through the API would silently lose it.
|
|
47
|
+
return new TextDecoder('utf-8', { ignoreBOM: true }).decode(await response.arrayBuffer())
|
|
36
48
|
}
|
|
37
49
|
|
|
38
50
|
/**
|