@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
|
@@ -5,7 +5,13 @@
|
|
|
5
5
|
const output = require('../../lib/output')
|
|
6
6
|
const { isProxyRunning } = require('../../lib/proxy-detect')
|
|
7
7
|
const { apiGet, apiPut } = require('../../lib/api-client')
|
|
8
|
-
const { listRuleFiles, getActiveRuleFileNames,
|
|
8
|
+
const { listRuleFiles, getActiveRuleFileNames, setRuleFileEnabled } = require('../../lib/file-access')
|
|
9
|
+
const { isRulesetReadonlyError, exitRulesetReadonly, assertListedFilesWritable } = require('../../lib/ruleset-readonly')
|
|
10
|
+
|
|
11
|
+
/** Names whose enabled state `active set <file>` would change. */
|
|
12
|
+
function pendingToggles(files, target) {
|
|
13
|
+
return files.filter((f) => !!f.enabled !== (f.name === target)).map((f) => f.name)
|
|
14
|
+
}
|
|
9
15
|
|
|
10
16
|
// Parse arguments (skip 'route' and 'active' from argv)
|
|
11
17
|
const args = process.argv.slice(4)
|
|
@@ -39,6 +45,9 @@ async function run() {
|
|
|
39
45
|
process.exit(1)
|
|
40
46
|
}
|
|
41
47
|
|
|
48
|
+
// Issue #137: refuse up front so a locked ruleset never leaves a half-applied switch.
|
|
49
|
+
assertListedFilesWritable(files, pendingToggles(files, targetFile))
|
|
50
|
+
|
|
42
51
|
for (const f of files) {
|
|
43
52
|
const nextEnabled = f.name === targetFile
|
|
44
53
|
if (!!f.enabled === nextEnabled) continue
|
|
@@ -53,6 +62,7 @@ async function run() {
|
|
|
53
62
|
output.header('Active Route File Set')
|
|
54
63
|
output.success(targetFile)
|
|
55
64
|
} catch (e) {
|
|
65
|
+
if (isRulesetReadonlyError(e)) exitRulesetReadonly(e, { json: jsonFlag, ruleFile: targetFile })
|
|
56
66
|
// Fallback to file mode
|
|
57
67
|
const files = listRuleFiles()
|
|
58
68
|
const target = files.find(f => f.name === targetFile)
|
|
@@ -61,11 +71,14 @@ async function run() {
|
|
|
61
71
|
process.exit(1)
|
|
62
72
|
}
|
|
63
73
|
|
|
74
|
+
assertListedFilesWritable(files, pendingToggles(files, targetFile))
|
|
64
75
|
// Disable all, then enable target
|
|
65
76
|
files.forEach(f => setRuleFileEnabled(f.name, f.name === targetFile))
|
|
66
77
|
|
|
78
|
+
output.warnFileOnlyMode()
|
|
79
|
+
|
|
67
80
|
if (jsonFlag) {
|
|
68
|
-
output.jsonRaw({ success: true, activeFile: targetFile })
|
|
81
|
+
output.jsonRaw({ success: true, activeFile: targetFile, fileOnly: true, warning: output.FILE_ONLY_WARNING })
|
|
69
82
|
return
|
|
70
83
|
}
|
|
71
84
|
|
|
@@ -81,10 +94,13 @@ async function run() {
|
|
|
81
94
|
process.exit(1)
|
|
82
95
|
}
|
|
83
96
|
|
|
97
|
+
assertListedFilesWritable(files, pendingToggles(files, targetFile))
|
|
84
98
|
files.forEach(f => setRuleFileEnabled(f.name, f.name === targetFile))
|
|
85
99
|
|
|
100
|
+
output.warnFileOnlyMode()
|
|
101
|
+
|
|
86
102
|
if (jsonFlag) {
|
|
87
|
-
output.jsonRaw({ success: true, activeFile: targetFile })
|
|
103
|
+
output.jsonRaw({ success: true, activeFile: targetFile, fileOnly: true, warning: output.FILE_ONLY_WARNING })
|
|
88
104
|
return
|
|
89
105
|
}
|
|
90
106
|
|
|
@@ -136,6 +152,7 @@ async function run() {
|
|
|
136
152
|
}
|
|
137
153
|
|
|
138
154
|
run().catch(e => {
|
|
155
|
+
if (isRulesetReadonlyError(e)) exitRulesetReadonly(e, { json: jsonFlag, ruleFile: targetFile })
|
|
139
156
|
output.error(e.message)
|
|
140
157
|
process.exit(1)
|
|
141
158
|
})
|
|
@@ -5,123 +5,142 @@
|
|
|
5
5
|
const output = require('../../lib/output')
|
|
6
6
|
const { isProxyRunning } = require('../../lib/proxy-detect')
|
|
7
7
|
const { apiGet, apiPut } = require('../../lib/api-client')
|
|
8
|
-
const { getRuleFileContent, saveRuleFileContent
|
|
8
|
+
const { getRuleFileContent, saveRuleFileContent } = require('../../lib/file-access')
|
|
9
|
+
const { addRouteRule } = require('../../lib/rule-file-edit')
|
|
10
|
+
const { isRulesetReadonlyError, exitRulesetReadonly } = require('../../lib/ruleset-readonly')
|
|
11
|
+
const { parseRouteWriteArgs, parsePatternArg, resolveExclusions } = require('../../lib/route-write-args')
|
|
12
|
+
|
|
13
|
+
const ADD_HELP = `
|
|
14
|
+
meddle route add <file> <pattern> <target> [--exclusions=<list>] [--json]
|
|
15
|
+
|
|
16
|
+
Add a rule. If the pattern already exists, its target is overwritten in place.
|
|
17
|
+
--exclusions= clears that rule's exclusions; --exclusions=/a,/b replaces them.
|
|
18
|
+
Omit --exclusions and any !path in the pattern to keep existing exclusions.
|
|
19
|
+
See: meddle route update --help
|
|
20
|
+
`
|
|
9
21
|
|
|
10
|
-
// Parse arguments (skip 'route' and 'add' from argv)
|
|
11
22
|
const args = process.argv.slice(4)
|
|
12
|
-
|
|
23
|
+
|
|
24
|
+
function fail(message, jsonFlag) {
|
|
25
|
+
if (jsonFlag) {
|
|
26
|
+
console.log(JSON.stringify({ success: false, error: { message } }))
|
|
27
|
+
console.error(message)
|
|
28
|
+
} else {
|
|
29
|
+
output.setJsonMode(false)
|
|
30
|
+
output.error(message)
|
|
31
|
+
}
|
|
32
|
+
process.exit(1)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let parsed
|
|
36
|
+
try {
|
|
37
|
+
parsed = parseRouteWriteArgs(args)
|
|
38
|
+
} catch (e) {
|
|
39
|
+
fail(e.message, args.includes('--json'))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (parsed.help) {
|
|
43
|
+
console.log(ADD_HELP.trim())
|
|
44
|
+
process.exit(0)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const jsonFlag = parsed.json
|
|
13
48
|
output.setJsonMode(jsonFlag)
|
|
14
49
|
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const pattern = positionalArgs[1]
|
|
19
|
-
const target = positionalArgs[2]
|
|
50
|
+
const fileName = parsed.positional[0]
|
|
51
|
+
const pattern = parsed.positional[1]
|
|
52
|
+
const target = parsed.positional[2]
|
|
20
53
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
54
|
+
let explicitExclusions = false
|
|
55
|
+
let appliedExclusions = []
|
|
56
|
+
|
|
57
|
+
function planAdd() {
|
|
58
|
+
const { patterns, exclusions: patternExclusions } = parsePatternArg(pattern)
|
|
59
|
+
if (patterns.length === 0) fail('Missing required argument: <pattern>', jsonFlag)
|
|
60
|
+
const resolved = resolveExclusions(patternExclusions, parsed.exclusions)
|
|
61
|
+
explicitExclusions = resolved.explicit
|
|
62
|
+
appliedExclusions = resolved.exclusions || []
|
|
63
|
+
return { patterns, exclusions: resolved.explicit ? resolved.exclusions : undefined }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Issue #145: edit only the line(s) holding this pattern; comments, blank
|
|
68
|
+
* lines and other rules (with their `!exclusions`) keep their exact bytes.
|
|
69
|
+
* Exclusions may be given inside the pattern argument ("app.example.com !/api")
|
|
70
|
+
* or with --exclusions. `--exclusions=` clears them (issue #149).
|
|
71
|
+
*/
|
|
72
|
+
function applyAdd(text, plan) {
|
|
73
|
+
let next = text
|
|
74
|
+
for (const p of plan.patterns) {
|
|
75
|
+
next = addRouteRule(next, { pattern: p, target: target.trim(), exclusions: plan.exclusions }).content
|
|
25
76
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
77
|
+
return next
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function emitAdded({ fileOnly }) {
|
|
81
|
+
const payload = { success: true, file: fileName, pattern: pattern.trim(), target: target.trim() }
|
|
82
|
+
if (explicitExclusions) payload.exclusions = appliedExclusions
|
|
83
|
+
if (fileOnly) {
|
|
84
|
+
payload.fileOnly = true
|
|
85
|
+
payload.warning = output.FILE_ONLY_WARNING
|
|
29
86
|
}
|
|
30
|
-
if (
|
|
31
|
-
output.
|
|
32
|
-
|
|
87
|
+
if (jsonFlag) {
|
|
88
|
+
output.jsonRaw(payload)
|
|
89
|
+
return
|
|
33
90
|
}
|
|
91
|
+
output.header('Route Rule Added')
|
|
92
|
+
output.success(`${pattern.trim()} -> ${target.trim()}`)
|
|
93
|
+
if (explicitExclusions) {
|
|
94
|
+
output.kv('Exclusions', appliedExclusions.length ? appliedExclusions.map((e) => `!${e}`).join(' ') : 'cleared')
|
|
95
|
+
}
|
|
96
|
+
output.kv('File', fileName)
|
|
97
|
+
}
|
|
34
98
|
|
|
35
|
-
|
|
99
|
+
async function run() {
|
|
100
|
+
if (!fileName) fail('Missing required argument: <file>', jsonFlag)
|
|
101
|
+
if (!pattern) fail('Missing required argument: <pattern>', jsonFlag)
|
|
102
|
+
if (!target) fail('Missing required argument: <target>', jsonFlag)
|
|
103
|
+
if (parsed.positional.length > 3) fail(`Unexpected argument: ${parsed.positional[3]}`, jsonFlag)
|
|
104
|
+
|
|
105
|
+
let plan
|
|
106
|
+
try {
|
|
107
|
+
plan = planAdd()
|
|
108
|
+
} catch (e) {
|
|
109
|
+
fail(e.message, jsonFlag)
|
|
110
|
+
}
|
|
36
111
|
|
|
37
|
-
const
|
|
38
|
-
const tgt = target.trim()
|
|
112
|
+
const running = await isProxyRunning()
|
|
39
113
|
|
|
40
114
|
if (running) {
|
|
41
115
|
try {
|
|
42
|
-
// Get current content
|
|
43
116
|
let content = await apiGet(`/api/rule-files/${encodeURIComponent(fileName)}/content`)
|
|
44
117
|
const text = typeof content === 'string' ? content : String(content)
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
// Handle [marker] syntax
|
|
48
|
-
const bm = pat.match(/\[([^\]]+)\]/)
|
|
49
|
-
if (bm) {
|
|
50
|
-
ruleMap[pat.replace(bm[0], bm[1])] = tgt + bm[0]
|
|
51
|
-
} else {
|
|
52
|
-
ruleMap[pat] = tgt
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const newContent = ruleMapToEprcText(ruleMap)
|
|
118
|
+
const newContent = applyAdd(text, plan)
|
|
56
119
|
await apiPut(`/api/rule-files/${encodeURIComponent(fileName)}/content`, { content: newContent })
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
output.jsonRaw({ success: true, file: fileName, pattern: pat, target: tgt })
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
output.header('Route Rule Added')
|
|
64
|
-
output.success(`${pat} -> ${tgt}`)
|
|
65
|
-
output.kv('File', fileName)
|
|
120
|
+
emitAdded({ fileOnly: false })
|
|
121
|
+
return
|
|
66
122
|
} catch (e) {
|
|
67
|
-
//
|
|
123
|
+
// Issue #137: a locked ruleset is a final answer, never a cue to write the file directly.
|
|
124
|
+
if (isRulesetReadonlyError(e)) exitRulesetReadonly(e, { json: jsonFlag, ruleFile: fileName })
|
|
68
125
|
const content = getRuleFileContent(fileName)
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
process.exit(1)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const ruleMap = parseEprc(content)
|
|
75
|
-
const bm = pat.match(/\[([^\]]+)\]/)
|
|
76
|
-
if (bm) {
|
|
77
|
-
ruleMap[pat.replace(bm[0], bm[1])] = tgt + bm[0]
|
|
78
|
-
} else {
|
|
79
|
-
ruleMap[pat] = tgt
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const newContent = ruleMapToEprcText(ruleMap)
|
|
126
|
+
if (content == null) fail(`Route file not found: ${fileName}`, jsonFlag)
|
|
127
|
+
const newContent = applyAdd(content, plan)
|
|
83
128
|
saveRuleFileContent(fileName, newContent)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
output.jsonRaw({ success: true, file: fileName, pattern: pat, target: tgt })
|
|
87
|
-
return
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
output.header('Route Rule Added')
|
|
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
|
-
const bm = pat.match(/\[([^\]]+)\]/)
|
|
104
|
-
if (bm) {
|
|
105
|
-
ruleMap[pat.replace(bm[0], bm[1])] = tgt + bm[0]
|
|
106
|
-
} else {
|
|
107
|
-
ruleMap[pat] = tgt
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const newContent = ruleMapToEprcText(ruleMap)
|
|
111
|
-
saveRuleFileContent(fileName, newContent)
|
|
112
|
-
|
|
113
|
-
if (jsonFlag) {
|
|
114
|
-
output.jsonRaw({ success: true, file: fileName, pattern: pat, target: tgt })
|
|
129
|
+
output.warnFileOnlyMode()
|
|
130
|
+
emitAdded({ fileOnly: true })
|
|
115
131
|
return
|
|
116
132
|
}
|
|
117
|
-
|
|
118
|
-
output.header('Route Rule Added')
|
|
119
|
-
output.success(`${pat} -> ${tgt}`)
|
|
120
|
-
output.kv('File', fileName)
|
|
121
133
|
}
|
|
134
|
+
|
|
135
|
+
const content = getRuleFileContent(fileName)
|
|
136
|
+
if (content == null) fail(`Route file not found: ${fileName}`, jsonFlag)
|
|
137
|
+
const newContent = applyAdd(content, plan)
|
|
138
|
+
saveRuleFileContent(fileName, newContent)
|
|
139
|
+
output.warnFileOnlyMode()
|
|
140
|
+
emitAdded({ fileOnly: true })
|
|
122
141
|
}
|
|
123
142
|
|
|
124
143
|
run().catch(e => {
|
|
125
|
-
|
|
126
|
-
|
|
144
|
+
if (isRulesetReadonlyError(e)) exitRulesetReadonly(e, { json: jsonFlag, ruleFile: fileName })
|
|
145
|
+
fail(e.message, jsonFlag)
|
|
127
146
|
})
|
|
@@ -34,6 +34,7 @@ async function run() {
|
|
|
34
34
|
const running = await isProxyRunning()
|
|
35
35
|
|
|
36
36
|
let result
|
|
37
|
+
let fileOnly = false
|
|
37
38
|
if (running) {
|
|
38
39
|
try {
|
|
39
40
|
result = await apiPost('/api/rule-files', {
|
|
@@ -46,6 +47,7 @@ async function run() {
|
|
|
46
47
|
// Fallback to file mode
|
|
47
48
|
try {
|
|
48
49
|
result = createRuleFile(fileName, content, true)
|
|
50
|
+
fileOnly = true
|
|
49
51
|
} catch (err) {
|
|
50
52
|
output.error(err.message)
|
|
51
53
|
process.exit(1)
|
|
@@ -54,14 +56,19 @@ async function run() {
|
|
|
54
56
|
} else {
|
|
55
57
|
try {
|
|
56
58
|
result = createRuleFile(fileName, content, true)
|
|
59
|
+
fileOnly = true
|
|
57
60
|
} catch (err) {
|
|
58
61
|
output.error(err.message)
|
|
59
62
|
process.exit(1)
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
65
|
|
|
66
|
+
if (fileOnly) output.warnFileOnlyMode()
|
|
67
|
+
|
|
63
68
|
if (jsonFlag) {
|
|
64
|
-
output.jsonRaw(
|
|
69
|
+
output.jsonRaw(fileOnly
|
|
70
|
+
? { ...result, fileOnly: true, warning: output.FILE_ONLY_WARNING }
|
|
71
|
+
: result)
|
|
65
72
|
return
|
|
66
73
|
}
|
|
67
74
|
|
|
@@ -5,49 +5,87 @@
|
|
|
5
5
|
const output = require('../../lib/output')
|
|
6
6
|
const { isProxyRunning } = require('../../lib/proxy-detect')
|
|
7
7
|
const { apiGet, apiPut } = require('../../lib/api-client')
|
|
8
|
-
const { getRuleFileContent, saveRuleFileContent
|
|
8
|
+
const { getRuleFileContent, saveRuleFileContent } = require('../../lib/file-access')
|
|
9
|
+
const { deleteRouteRule } = require('../../lib/rule-file-edit')
|
|
10
|
+
const { isRulesetReadonlyError, exitRulesetReadonly } = require('../../lib/ruleset-readonly')
|
|
11
|
+
const { parseRouteWriteArgs, parsePatternArg } = require('../../lib/route-write-args')
|
|
12
|
+
|
|
13
|
+
const DELETE_HELP = `
|
|
14
|
+
meddle route delete <file> <pattern> [--json]
|
|
15
|
+
|
|
16
|
+
Delete a rule. This command does not take --exclusions.
|
|
17
|
+
`
|
|
9
18
|
|
|
10
|
-
// Parse arguments (skip 'route' and 'delete' from argv)
|
|
11
19
|
const args = process.argv.slice(4)
|
|
12
|
-
|
|
20
|
+
|
|
21
|
+
function fail(message, jsonFlag) {
|
|
22
|
+
if (jsonFlag) {
|
|
23
|
+
console.log(JSON.stringify({ success: false, error: { message } }))
|
|
24
|
+
console.error(message)
|
|
25
|
+
} else {
|
|
26
|
+
output.setJsonMode(false)
|
|
27
|
+
output.error(message)
|
|
28
|
+
}
|
|
29
|
+
process.exit(1)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let parsed
|
|
33
|
+
try {
|
|
34
|
+
parsed = parseRouteWriteArgs(args, { allowExclusions: false })
|
|
35
|
+
} catch (e) {
|
|
36
|
+
fail(e.message, args.includes('--json'))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (parsed.help) {
|
|
40
|
+
console.log(DELETE_HELP.trim())
|
|
41
|
+
process.exit(0)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const jsonFlag = parsed.json
|
|
13
45
|
output.setJsonMode(jsonFlag)
|
|
14
46
|
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
const fileName = positionalArgs[0]
|
|
18
|
-
const pattern = positionalArgs[1]
|
|
47
|
+
const fileName = parsed.positional[0]
|
|
48
|
+
const pattern = parsed.positional[1]
|
|
19
49
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Issue #145: remove only this pattern; every other line (comments, blank
|
|
52
|
+
* lines, other rules and their `!exclusions`) keeps its exact bytes.
|
|
53
|
+
*/
|
|
54
|
+
function applyDelete(text) {
|
|
55
|
+
const { patterns } = parsePatternArg(pattern)
|
|
56
|
+
if (patterns.length === 0) {
|
|
57
|
+
fail('Missing required argument: <pattern>', jsonFlag)
|
|
58
|
+
}
|
|
59
|
+
let next = text
|
|
60
|
+
for (const p of patterns) {
|
|
61
|
+
const result = deleteRouteRule(next, { pattern: p })
|
|
62
|
+
if (!result.found) {
|
|
63
|
+
fail(`Pattern not found: ${pattern.trim()}`, jsonFlag)
|
|
64
|
+
}
|
|
65
|
+
next = result.content
|
|
24
66
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
67
|
+
return next
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function run() {
|
|
71
|
+
if (!fileName) fail('Missing required argument: <file>', jsonFlag)
|
|
72
|
+
if (!pattern) fail('Missing required argument: <pattern>', jsonFlag)
|
|
73
|
+
if (parsed.positional.length > 2) fail(`Unexpected argument: ${parsed.positional[2]}`, jsonFlag)
|
|
74
|
+
try {
|
|
75
|
+
parsePatternArg(pattern)
|
|
76
|
+
} catch (e) {
|
|
77
|
+
fail(e.message, jsonFlag)
|
|
28
78
|
}
|
|
29
79
|
|
|
30
80
|
const running = await isProxyRunning()
|
|
31
81
|
|
|
32
82
|
const pat = pattern.trim()
|
|
33
83
|
|
|
34
|
-
// Handle [marker] syntax for lookup
|
|
35
|
-
const bm = pat.match(/\[([^\]]+)\]/)
|
|
36
|
-
const internalKey = bm ? pat.replace(bm[0], bm[1]) : pat
|
|
37
|
-
|
|
38
84
|
if (running) {
|
|
39
85
|
try {
|
|
40
86
|
let content = await apiGet(`/api/rule-files/${encodeURIComponent(fileName)}/content`)
|
|
41
87
|
const text = typeof content === 'string' ? content : String(content)
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
if (!Object.prototype.hasOwnProperty.call(ruleMap, internalKey)) {
|
|
45
|
-
output.error(`Pattern not found: ${pat}`)
|
|
46
|
-
process.exit(1)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
delete ruleMap[internalKey]
|
|
50
|
-
const newContent = ruleMapToEprcText(ruleMap)
|
|
88
|
+
const newContent = applyDelete(text)
|
|
51
89
|
await apiPut(`/api/rule-files/${encodeURIComponent(fileName)}/content`, { content: newContent })
|
|
52
90
|
|
|
53
91
|
if (jsonFlag) {
|
|
@@ -59,28 +97,27 @@ async function run() {
|
|
|
59
97
|
output.success(pat)
|
|
60
98
|
output.kv('File', fileName)
|
|
61
99
|
} catch (e) {
|
|
100
|
+
// Issue #137: a locked ruleset is a final answer, never a cue to write the file directly.
|
|
101
|
+
if (isRulesetReadonlyError(e)) exitRulesetReadonly(e, { json: jsonFlag, ruleFile: fileName })
|
|
62
102
|
// Fallback to file mode
|
|
63
103
|
const content = getRuleFileContent(fileName)
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
process.exit(1)
|
|
104
|
+
if (content == null) {
|
|
105
|
+
fail(`Route file not found: ${fileName}`, jsonFlag)
|
|
67
106
|
}
|
|
68
107
|
|
|
69
|
-
const
|
|
70
|
-
if (!Object.prototype.hasOwnProperty.call(ruleMap, internalKey)) {
|
|
71
|
-
output.error(`Pattern not found: ${pat}`)
|
|
72
|
-
process.exit(1)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
delete ruleMap[internalKey]
|
|
76
|
-
const newContent = ruleMapToEprcText(ruleMap)
|
|
108
|
+
const newContent = applyDelete(content)
|
|
77
109
|
saveRuleFileContent(fileName, newContent)
|
|
110
|
+
output.warnFileOnlyMode()
|
|
78
111
|
|
|
79
112
|
if (jsonFlag) {
|
|
80
|
-
output.jsonRaw({ success: true, file: fileName, pattern: pat
|
|
113
|
+
output.jsonRaw({ success: true, file: fileName, pattern: pat,
|
|
114
|
+
fileOnly: true,
|
|
115
|
+
warning: output.FILE_ONLY_WARNING
|
|
116
|
+
})
|
|
81
117
|
return
|
|
82
118
|
}
|
|
83
119
|
|
|
120
|
+
|
|
84
121
|
output.header('Route Rule Deleted')
|
|
85
122
|
output.success(pat)
|
|
86
123
|
output.kv('File', fileName)
|
|
@@ -88,26 +125,23 @@ async function run() {
|
|
|
88
125
|
} else {
|
|
89
126
|
// File mode
|
|
90
127
|
const content = getRuleFileContent(fileName)
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
process.exit(1)
|
|
128
|
+
if (content == null) {
|
|
129
|
+
fail(`Route file not found: ${fileName}`, jsonFlag)
|
|
94
130
|
}
|
|
95
131
|
|
|
96
|
-
const
|
|
97
|
-
if (!Object.prototype.hasOwnProperty.call(ruleMap, internalKey)) {
|
|
98
|
-
output.error(`Pattern not found: ${pat}`)
|
|
99
|
-
process.exit(1)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
delete ruleMap[internalKey]
|
|
103
|
-
const newContent = ruleMapToEprcText(ruleMap)
|
|
132
|
+
const newContent = applyDelete(content)
|
|
104
133
|
saveRuleFileContent(fileName, newContent)
|
|
134
|
+
output.warnFileOnlyMode()
|
|
105
135
|
|
|
106
136
|
if (jsonFlag) {
|
|
107
|
-
output.jsonRaw({ success: true, file: fileName, pattern: pat
|
|
137
|
+
output.jsonRaw({ success: true, file: fileName, pattern: pat,
|
|
138
|
+
fileOnly: true,
|
|
139
|
+
warning: output.FILE_ONLY_WARNING
|
|
140
|
+
})
|
|
108
141
|
return
|
|
109
142
|
}
|
|
110
143
|
|
|
144
|
+
|
|
111
145
|
output.header('Route Rule Deleted')
|
|
112
146
|
output.success(pat)
|
|
113
147
|
output.kv('File', fileName)
|
|
@@ -115,6 +149,6 @@ async function run() {
|
|
|
115
149
|
}
|
|
116
150
|
|
|
117
151
|
run().catch(e => {
|
|
118
|
-
|
|
119
|
-
|
|
152
|
+
if (isRulesetReadonlyError(e)) exitRulesetReadonly(e, { json: jsonFlag, ruleFile: fileName })
|
|
153
|
+
fail(e.message, jsonFlag)
|
|
120
154
|
})
|
|
@@ -2,8 +2,70 @@
|
|
|
2
2
|
* meddle route - Route rule subcommand router
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
const output = require('../../lib/output')
|
|
6
|
+
|
|
5
7
|
const args = process.argv.slice(3)
|
|
6
8
|
const subcommand = args[0]
|
|
9
|
+
const jsonFlag = args.includes('--json')
|
|
10
|
+
|
|
11
|
+
const ROUTE_HELP = `
|
|
12
|
+
Route Commands:
|
|
13
|
+
meddle route list [--json] List all route files
|
|
14
|
+
meddle route show <file> [--json] Show rules in a file
|
|
15
|
+
meddle route preview <url> [--file <name>] [--json] Preview route target for a URL
|
|
16
|
+
meddle route active Show active route files
|
|
17
|
+
meddle route active set <file> Set active route file
|
|
18
|
+
meddle route create <name> [--content <text>] Create a route file
|
|
19
|
+
meddle route add <file> <pattern> <target> [--exclusions=<list>]
|
|
20
|
+
Add a rule to file
|
|
21
|
+
meddle route update <file> <pattern> [<target>] [--exclusions=<list>]
|
|
22
|
+
Update a rule in file
|
|
23
|
+
meddle route delete <file> <pattern> Delete a rule from file
|
|
24
|
+
|
|
25
|
+
Read-only route files:
|
|
26
|
+
Files locked in the Meddle web UI reject add/update/delete and active set
|
|
27
|
+
(error RULESET_READONLY, exit code 3). Lock/unlock is only available in the web UI.
|
|
28
|
+
|
|
29
|
+
Examples:
|
|
30
|
+
meddle route list
|
|
31
|
+
meddle route show dev-rules
|
|
32
|
+
meddle route preview "https://api.example.com/v1/users"
|
|
33
|
+
meddle route preview "https://cdn.com/assets/js/app.js" --file dev-rules
|
|
34
|
+
meddle route preview "https://api.test.com/data" --json
|
|
35
|
+
meddle route active set beta-rules
|
|
36
|
+
meddle route create staging --content "example.com localhost:3000"
|
|
37
|
+
meddle route add dev-rules "api.test.com" "localhost:8080"
|
|
38
|
+
meddle route update dev-rules "api.test.com" "localhost:9000"
|
|
39
|
+
meddle route update dev-rules "api.test.com" --exclusions=
|
|
40
|
+
meddle route delete dev-rules "api.test.com"
|
|
41
|
+
|
|
42
|
+
Exclusions:
|
|
43
|
+
Omit --exclusions and any !path in the pattern to keep the rule's exclusions.
|
|
44
|
+
--exclusions= Clear exclusions (that line loses every !path).
|
|
45
|
+
--exclusions=/a,/b Replace exclusions. A leading ! is optional.
|
|
46
|
+
"pattern !/a" Replace exclusions. A bare "!" is an error.
|
|
47
|
+
A bare --exclusions, an unknown flag, or both forms at once exits 1
|
|
48
|
+
and does not change the file. meddle route update --help has the details.
|
|
49
|
+
|
|
50
|
+
A "#" comments out a whole line only when it is the first non-space
|
|
51
|
+
character. A "#" later on a rule line is a pattern, not a trailing comment.
|
|
52
|
+
`
|
|
53
|
+
|
|
54
|
+
function printHelp(stream = process.stdout) {
|
|
55
|
+
stream.write(ROUTE_HELP.trimStart())
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function failUnknown(name) {
|
|
59
|
+
const message = `Unknown command: route ${name}`
|
|
60
|
+
if (jsonFlag) {
|
|
61
|
+
output.setJsonMode(true)
|
|
62
|
+
output.error(message)
|
|
63
|
+
} else {
|
|
64
|
+
console.error(message)
|
|
65
|
+
printHelp(process.stderr)
|
|
66
|
+
}
|
|
67
|
+
process.exit(1)
|
|
68
|
+
}
|
|
7
69
|
|
|
8
70
|
switch (subcommand) {
|
|
9
71
|
case 'list':
|
|
@@ -30,30 +92,16 @@ switch (subcommand) {
|
|
|
30
92
|
case 'delete':
|
|
31
93
|
require('./delete.js')
|
|
32
94
|
break
|
|
95
|
+
case '--help':
|
|
96
|
+
case '-h':
|
|
97
|
+
case 'help':
|
|
98
|
+
printHelp()
|
|
99
|
+
process.exit(0)
|
|
100
|
+
break
|
|
33
101
|
default:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
meddle route active set <file> Set active route file
|
|
41
|
-
meddle route create <name> [--content <text>] Create a route file
|
|
42
|
-
meddle route add <file> <pattern> <target> Add a rule to file
|
|
43
|
-
meddle route update <file> <pattern> <target> Update a rule in file
|
|
44
|
-
meddle route delete <file> <pattern> Delete a rule from file
|
|
45
|
-
|
|
46
|
-
Examples:
|
|
47
|
-
meddle route list
|
|
48
|
-
meddle route show dev-rules
|
|
49
|
-
meddle route preview "https://api.example.com/v1/users"
|
|
50
|
-
meddle route preview "https://cdn.com/assets/js/app.js" --file dev-rules
|
|
51
|
-
meddle route preview "https://api.test.com/data" --json
|
|
52
|
-
meddle route active set beta-rules
|
|
53
|
-
meddle route create staging --content "example.com localhost:3000"
|
|
54
|
-
meddle route add dev-rules "api.test.com" "localhost:8080"
|
|
55
|
-
meddle route update dev-rules "api.test.com" "localhost:9000"
|
|
56
|
-
meddle route delete dev-rules "api.test.com"
|
|
57
|
-
`)
|
|
58
|
-
process.exit(1)
|
|
59
|
-
}
|
|
102
|
+
if (!subcommand) {
|
|
103
|
+
printHelp()
|
|
104
|
+
process.exit(1)
|
|
105
|
+
}
|
|
106
|
+
failUnknown(subcommand)
|
|
107
|
+
}
|
|
@@ -48,7 +48,7 @@ async function run() {
|
|
|
48
48
|
output.section('Active Files')
|
|
49
49
|
active.forEach(f => {
|
|
50
50
|
const excludeInfo = f.excludeCount > 0 ? `, ${f.excludeCount} exclusions` : ''
|
|
51
|
-
output.success(`${f.name} (${f.ruleCount} rules${excludeInfo})`)
|
|
51
|
+
output.success(`${f.name} (${f.ruleCount} rules${excludeInfo})${f.readonly ? ' [read-only]' : ''}`)
|
|
52
52
|
})
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -56,7 +56,7 @@ async function run() {
|
|
|
56
56
|
output.section('Inactive Files')
|
|
57
57
|
inactive.forEach(f => {
|
|
58
58
|
const excludeInfo = f.excludeCount > 0 ? `, ${f.excludeCount} exclusions` : ''
|
|
59
|
-
output.bullet(`${f.name} (${f.ruleCount} rules${excludeInfo})`, false)
|
|
59
|
+
output.bullet(`${f.name} (${f.ruleCount} rules${excludeInfo})${f.readonly ? ' [read-only]' : ''}`, false)
|
|
60
60
|
})
|
|
61
61
|
}
|
|
62
62
|
}
|