dsh-perm-gate 2.1.2 → 2.6.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/CHANGELOG.ja.md +108 -0
- package/CHANGELOG.ko.md +104 -0
- package/CHANGELOG.md +239 -0
- package/INSTALL.ja.md +41 -21
- package/INSTALL.ko.md +37 -19
- package/INSTALL.md +46 -23
- package/INSTALL.zh.md +33 -17
- package/README.ja.md +52 -12
- package/README.ko.md +53 -13
- package/README.md +68 -10
- package/README.zh.md +41 -10
- package/assets/allow1.png +0 -0
- package/assets/allow2.png +0 -0
- package/assets/gateoff.png +0 -0
- package/assets/llms.png +0 -0
- package/assets/permlist.png +0 -0
- package/assets/setting-head.png +0 -0
- package/assets/setting-rules.png +0 -0
- package/cordis.patch.yml +11 -8
- package/docs/baseline-guard.md +56 -0
- package/docs/plan-rules-to-settings.md +217 -0
- package/docs/rules-format.en.md +294 -0
- package/docs/rules-format.md +279 -0
- package/docs/ui-gap-analysis-and-branch-permissions.md +338 -0
- package/dsh.plugin.json +2 -2
- package/examples/permissions.example.yaml +16 -1
- package/lib/allowlist.d.ts +27 -0
- package/lib/allowlist.js +103 -1
- package/lib/cli.js +11 -14
- package/lib/client.js +484 -6
- package/lib/client.js.map +1 -1
- package/lib/command-dispatcher.d.ts +22 -0
- package/lib/command-dispatcher.js +97 -0
- package/lib/command-semantics.d.ts +62 -0
- package/lib/command-semantics.js +6 -0
- package/lib/compiler.d.ts +11 -0
- package/lib/compiler.js +16 -0
- package/lib/config.d.ts +73 -12
- package/lib/config.js +56 -14
- package/lib/dry-run.d.ts +87 -0
- package/lib/dry-run.js +110 -0
- package/lib/engine.js +52 -2
- package/lib/evaluate.js +76 -2
- package/lib/events.d.ts +56 -2
- package/lib/events.js +92 -0
- package/lib/index.js +106 -26
- package/lib/parsers/git.d.ts +43 -0
- package/lib/parsers/git.js +404 -0
- package/lib/parsers/shell-cmds.d.ts +32 -0
- package/lib/parsers/shell-cmds.js +293 -0
- package/lib/preset.d.ts +13 -0
- package/lib/preset.js +17 -0
- package/lib/risk.d.ts +17 -3
- package/lib/risk.js +17 -3
- package/lib/rule-chain.js +13 -22
- package/lib/rule-dims.d.ts +35 -0
- package/lib/rule-dims.js +36 -0
- package/lib/rule.d.ts +36 -1
- package/lib/rule.js +56 -8
- package/lib/rules-view.d.ts +52 -0
- package/lib/rules-view.js +126 -0
- package/lib/runtime.d.ts +121 -7
- package/lib/runtime.js +204 -25
- package/lib/shell.d.ts +14 -0
- package/lib/shell.js +17 -0
- package/package.json +19 -2
- package/scripts/patch-permission-glyph.mjs +225 -42
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Give this plugin's own permission tiers the composer glyphs the host will not
|
|
4
|
+
* draw for them.
|
|
4
5
|
*
|
|
5
6
|
* WHY THIS EXISTS
|
|
6
7
|
* ---------------
|
|
@@ -9,22 +10,40 @@
|
|
|
9
10
|
*
|
|
10
11
|
* const permissionGlyphs = new Map([
|
|
11
12
|
* ["read-only", <svg …/>],
|
|
12
|
-
* ["permissive", <svg …/>], // the 自动审查 tier's shield+eye
|
|
13
13
|
* ["workspace-write", <svg …/>],
|
|
14
|
-
* [
|
|
14
|
+
* [FULL_ACCESS, <svg …/>], // const FULL_ACCESS = "danger-full-access"
|
|
15
15
|
* ]);
|
|
16
16
|
* function permissionGlyph(value) { return permissionGlyphs.get(value); }
|
|
17
17
|
*
|
|
18
18
|
* The source comment states the rule: "host-configured names outside the design
|
|
19
19
|
* set get none." There is no plugin-facing seam — the option objects the host
|
|
20
20
|
* supplies carry only `{value, name, description}`, and the map itself is a
|
|
21
|
-
* closure variable. So a plugin-contributed tier
|
|
22
|
-
*
|
|
23
|
-
* trigger, while the built-in `permissive` tier shows the shield+eye.
|
|
21
|
+
* closure variable. So a plugin-contributed tier renders with no icon in the
|
|
22
|
+
* dropdown and falls back to plain text in the trigger.
|
|
24
23
|
*
|
|
25
|
-
* The only way to give our
|
|
26
|
-
* This script does exactly that
|
|
27
|
-
*
|
|
24
|
+
* The only way to give our tiers the same glyph is to add entries to that map.
|
|
25
|
+
* This script does exactly that — one glyph per tier, copied from the built-in
|
|
26
|
+
* tier whose file sandbox the plugin tier shares:
|
|
27
|
+
*
|
|
28
|
+
* permissive sandbox: workspace-write <- the ["workspace-write",] entry
|
|
29
|
+
* permissive-full sandbox: danger-full-access <- the [FULL_ACCESS,] entry
|
|
30
|
+
*
|
|
31
|
+
* WHY THE SOURCES ARE DECLARED AND THE ANCHOR IS RESOLVED
|
|
32
|
+
* -------------------------------------------------------
|
|
33
|
+
* DSH 0.1.2 shipped four glyphs, `permissive` among them; 0.1.5-rc.2 ships three
|
|
34
|
+
* (`read-only` / `workspace-write` / `danger-full-access`) and the string
|
|
35
|
+
* `permissive` no longer occurs anywhere in the bundle — so a splice anchored on
|
|
36
|
+
* `["permissive",` dies with "anchor not found" on the current host. Two
|
|
37
|
+
* properties keep this working across such a design-set change:
|
|
38
|
+
*
|
|
39
|
+
* 1. the source key is a declaration (`GLYPH_TARGETS`) rather than a literal
|
|
40
|
+
* buried in the splice, so re-pointing a tier is a one-line edit; and
|
|
41
|
+
* 2. the anchor for a key is RESOLVED, not assumed to be a string literal:
|
|
42
|
+
* `["<key>",` first, then the constant alias the bundle may use instead
|
|
43
|
+
* (`[FULL_ACCESS,` for `"danger-full-access"`).
|
|
44
|
+
*
|
|
45
|
+
* When a declared source key is gone, the refusal names the keys the map DOES
|
|
46
|
+
* carry, so the next drift is diagnosed from the error text alone.
|
|
28
47
|
*
|
|
29
48
|
* VOLUNTARY, NOT AUTOMATIC
|
|
30
49
|
* ------------------------
|
|
@@ -42,8 +61,11 @@
|
|
|
42
61
|
* npx dsh-perm-gate-patch-glyph --check # report only; exit 1 if missing
|
|
43
62
|
* node scripts/patch-permission-glyph.mjs <path/to/client.js>
|
|
44
63
|
*
|
|
45
|
-
* With no argument it probes the
|
|
46
|
-
*
|
|
64
|
+
* With no argument it probes the profile scopes first and the CLI install second,
|
|
65
|
+
* and lists any other DSH install that carries the same bundle — on a host with
|
|
66
|
+
* both, only one of them serves the UI, so the explicit-path form is the answer
|
|
67
|
+
* when the probe picks the wrong one. The script is idempotent (a second run is a
|
|
68
|
+
* no-op), backs the file up once, applies every target or none, and refuses to
|
|
47
69
|
* write a bundle it cannot slice correctly, so it can never half-apply.
|
|
48
70
|
*/
|
|
49
71
|
import { existsSync, readFileSync, writeFileSync, copyFileSync } from 'node:fs'
|
|
@@ -52,13 +74,23 @@ import { homedir } from 'node:os'
|
|
|
52
74
|
import { dirname, join, resolve } from 'node:path'
|
|
53
75
|
import { pathToFileURL } from 'node:url'
|
|
54
76
|
|
|
55
|
-
/** The map key whose entry is cloned, and the key it is cloned onto. */
|
|
56
|
-
export const SOURCE_KEY = 'permissive'
|
|
57
|
-
export const TARGET_KEY = 'permissive-full'
|
|
58
|
-
|
|
59
77
|
/** Relative path from a package root to the bundle holding the map. */
|
|
60
78
|
const BUNDLE = join('@deepseek-ai', 'dsh-client-ui-conversation', 'lib', 'client.js')
|
|
61
79
|
|
|
80
|
+
/**
|
|
81
|
+
* The glyphs to add: each plugin tier, and the built-in tier it copies.
|
|
82
|
+
*
|
|
83
|
+
* The pairing is by file-sandbox meaning, not by name similarity — `permissive`
|
|
84
|
+
* runs in the same sandbox as `workspace-write`, `permissive-full` in the same
|
|
85
|
+
* sandbox as `danger-full-access` — so the icon a user sees matches the access
|
|
86
|
+
* the tier actually grants. A tier listed here gets that built-in's entry
|
|
87
|
+
* verbatim apart from its key.
|
|
88
|
+
*/
|
|
89
|
+
export const GLYPH_TARGETS = [
|
|
90
|
+
{ target: 'permissive', source: 'workspace-write' },
|
|
91
|
+
{ target: 'permissive-full', source: 'danger-full-access' },
|
|
92
|
+
]
|
|
93
|
+
|
|
62
94
|
/** Thrown for every refusal; the CLI turns it into an exit code 1. */
|
|
63
95
|
export class GlyphPatchError extends Error {}
|
|
64
96
|
|
|
@@ -66,6 +98,15 @@ export class GlyphPatchError extends Error {}
|
|
|
66
98
|
* Candidate bundle paths, most specific first. Pure: no filesystem access, so
|
|
67
99
|
* the probe order is unit-testable.
|
|
68
100
|
*
|
|
101
|
+
* Profile scope before CLI-install scope, and that order is load-bearing on a
|
|
102
|
+
* machine with more than one DSH: a profile's UI is served by the profile's own
|
|
103
|
+
* dependency scope, not by whichever global CLI install happens to sit beside the
|
|
104
|
+
* running `node`. Measured on this project's own host — a global install under
|
|
105
|
+
* `dirname(node)` existed and shadowed the profile's bundle under an older probe
|
|
106
|
+
* order, so the patch reported success while the picker stayed icon-free. On a
|
|
107
|
+
* stock single-install machine every candidate is one inode, so the order decides
|
|
108
|
+
* nothing there; it only matters exactly when guessing wrong is silent.
|
|
109
|
+
*
|
|
69
110
|
* `execDir` is `dirname(process.execPath)`. Deriving from the running node
|
|
70
111
|
* binary — never from a hardcoded nvm version or install path — is what keeps
|
|
71
112
|
* the script working across node upgrades and re-pointed install symlinks.
|
|
@@ -75,19 +116,111 @@ export class GlyphPatchError extends Error {}
|
|
|
75
116
|
*/
|
|
76
117
|
export function candidateBundles({ execDir, cwd, dshHome }) {
|
|
77
118
|
return [
|
|
78
|
-
// 1.
|
|
79
|
-
join(execDir, 'node_modules', BUNDLE),
|
|
80
|
-
// 2. The same scope nested inside the `dsh` package itself.
|
|
81
|
-
join(execDir, 'node_modules', '@deepseek-ai', 'dsh', 'node_modules', BUNDLE),
|
|
82
|
-
// 3. Whatever profile the caller is standing in.
|
|
119
|
+
// 1. Whatever profile the caller is standing in.
|
|
83
120
|
join(cwd, 'node_modules', BUNDLE),
|
|
84
|
-
//
|
|
121
|
+
// 2. The hoisted scope every profile under DSH_HOME shares.
|
|
85
122
|
join(dshHome, 'profiles', 'node_modules', BUNDLE),
|
|
123
|
+
// 3. `@deepseek-ai` scope hoisted beside the running node binary.
|
|
124
|
+
join(execDir, 'node_modules', BUNDLE),
|
|
125
|
+
// 4. The same scope nested inside the `dsh` package itself.
|
|
126
|
+
join(execDir, 'node_modules', '@deepseek-ai', 'dsh', 'node_modules', BUNDLE),
|
|
86
127
|
]
|
|
87
128
|
}
|
|
88
129
|
|
|
89
130
|
/**
|
|
90
|
-
*
|
|
131
|
+
* In-page string constants (`const FULL_ACCESS = "danger-full-access"`), as
|
|
132
|
+
* name -> value. First declaration wins, so a later reassignment cannot shadow
|
|
133
|
+
* the spelling the map was built with above it.
|
|
134
|
+
*
|
|
135
|
+
* The bundle may key a map entry by such a name rather than by a literal, which
|
|
136
|
+
* is the whole reason the anchor is resolved instead of concatenated.
|
|
137
|
+
*
|
|
138
|
+
* @param {string} source
|
|
139
|
+
* @returns {Map<string, string>} constant name -> its string value
|
|
140
|
+
*/
|
|
141
|
+
export function pageConstants(source) {
|
|
142
|
+
const constants = new Map()
|
|
143
|
+
for (const match of source.matchAll(/(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*"([^"]*)"/g)) {
|
|
144
|
+
if (!constants.has(match[1])) constants.set(match[1], match[2])
|
|
145
|
+
}
|
|
146
|
+
return constants
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The `permissionGlyphs` map's text, brackets included.
|
|
151
|
+
*
|
|
152
|
+
* Parsed rather than assumed: the map is a `new Map([ … ])` closure variable, so
|
|
153
|
+
* its extent is found by bracket depth from its `[`. Reading the map (instead of
|
|
154
|
+
* only probing for one anchor inside it) is what lets a refusal report the keys
|
|
155
|
+
* the host actually renders.
|
|
156
|
+
*
|
|
157
|
+
* @param {string} source
|
|
158
|
+
* @returns {string} the map array text, from `[` to the matching `]`
|
|
159
|
+
*/
|
|
160
|
+
export function glyphMapText(source) {
|
|
161
|
+
const start = source.search(/permissionGlyphs\s*=\s*new Map\(\s*\[/)
|
|
162
|
+
if (start === -1) throw new GlyphPatchError('permissionGlyphs map not found in this bundle')
|
|
163
|
+
const open = source.indexOf('[', start)
|
|
164
|
+
let depth = 0
|
|
165
|
+
for (let i = open; i < source.length; i += 1) {
|
|
166
|
+
const ch = source[i]
|
|
167
|
+
if (ch === '[' || ch === '(' || ch === '{') depth += 1
|
|
168
|
+
else if (ch === ']' || ch === ')' || ch === '}') {
|
|
169
|
+
depth -= 1
|
|
170
|
+
if (depth === 0) return source.slice(open, i + 1)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
throw new GlyphPatchError('unterminated permissionGlyphs map')
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* The preset values the map renders a glyph for — literals as-is, and entries
|
|
178
|
+
* keyed by an in-page constant resolved to that constant's value.
|
|
179
|
+
*
|
|
180
|
+
* @param {string} source
|
|
181
|
+
* @returns {string[]} e.g. `['read-only', 'workspace-write', 'danger-full-access']`
|
|
182
|
+
*/
|
|
183
|
+
export function glyphKeys(source) {
|
|
184
|
+
const constants = pageConstants(source)
|
|
185
|
+
const keys = []
|
|
186
|
+
for (const match of glyphMapText(source).matchAll(/\[\s*(?:"([^"]+)"|([A-Za-z_$][\w$]*))\s*,/g)) {
|
|
187
|
+
if (match[1] !== undefined) keys.push(match[1])
|
|
188
|
+
else keys.push(constants.get(match[2]) ?? match[2])
|
|
189
|
+
}
|
|
190
|
+
return keys
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Resolve the anchor text of one map entry, literal first, constant alias second.
|
|
195
|
+
*
|
|
196
|
+
* @param {string} source the bundle's text
|
|
197
|
+
* @param {string} key the preset value, e.g. `danger-full-access`
|
|
198
|
+
* @returns {{ anchor: string, index: number }}
|
|
199
|
+
* @throws {GlyphPatchError} when the host renders no glyph for `key`
|
|
200
|
+
*/
|
|
201
|
+
export function resolveAnchor(source, key) {
|
|
202
|
+
for (const anchor of [`["${key}",`, `['${key}',`]) {
|
|
203
|
+
const index = source.indexOf(anchor)
|
|
204
|
+
if (index !== -1) return { anchor, index }
|
|
205
|
+
}
|
|
206
|
+
const name = [...pageConstants(source)].find(([, value]) => value === key)?.[0]
|
|
207
|
+
if (name !== undefined) {
|
|
208
|
+
const anchor = `[${name},`
|
|
209
|
+
const index = source.indexOf(anchor)
|
|
210
|
+
if (index !== -1) return { anchor, index }
|
|
211
|
+
throw new GlyphPatchError(
|
|
212
|
+
`anchor not found: ${anchor} (the alias for "${key}")`,
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
throw new GlyphPatchError(
|
|
216
|
+
`this host renders no glyph for "${key}" — the map carries: ` +
|
|
217
|
+
`${glyphKeys(source).map((k) => `"${k}"`).join(', ')}\n` +
|
|
218
|
+
' Re-point GLYPH_TARGETS at a key the map does carry.',
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Extract the full `["<key>", …]` (or `[CONST, …]`) map entry, by bracket depth.
|
|
91
224
|
*
|
|
92
225
|
* The scan starts ON the entry's opening `[` (the one immediately before the
|
|
93
226
|
* key), so depth returns to zero exactly at that entry's closing `]`. Starting
|
|
@@ -98,12 +231,12 @@ export function candidateBundles({ execDir, cwd, dshHome }) {
|
|
|
98
231
|
* contain no brackets.
|
|
99
232
|
*
|
|
100
233
|
* @param {string} source
|
|
101
|
-
* @param {string}
|
|
234
|
+
* @param {string} anchor the text returned by {@link resolveAnchor}
|
|
102
235
|
* @returns {string} the entry, brackets included
|
|
103
236
|
*/
|
|
104
|
-
export function sliceEntry(source,
|
|
105
|
-
const start = source.indexOf(
|
|
106
|
-
if (start === -1) throw new GlyphPatchError(`anchor not found:
|
|
237
|
+
export function sliceEntry(source, anchor) {
|
|
238
|
+
const start = source.indexOf(anchor)
|
|
239
|
+
if (start === -1) throw new GlyphPatchError(`anchor not found: ${anchor}`)
|
|
107
240
|
let depth = 0
|
|
108
241
|
for (let i = start; i < source.length; i += 1) {
|
|
109
242
|
const ch = source[i]
|
|
@@ -113,24 +246,41 @@ export function sliceEntry(source, key) {
|
|
|
113
246
|
if (depth === 0) return source.slice(start, i + 1)
|
|
114
247
|
}
|
|
115
248
|
}
|
|
116
|
-
throw new GlyphPatchError(`unterminated entry for
|
|
249
|
+
throw new GlyphPatchError(`unterminated entry for ${anchor}`)
|
|
117
250
|
}
|
|
118
251
|
|
|
119
252
|
/**
|
|
120
|
-
*
|
|
253
|
+
* Splice every declared glyph into the map.
|
|
254
|
+
*
|
|
255
|
+
* All or nothing: a source key this host does not carry throws before the caller
|
|
256
|
+
* can write anything, so a partially-glyphed bundle is impossible. Idempotent
|
|
257
|
+
* per target — a target already present is left untouched and reported as such.
|
|
121
258
|
*
|
|
122
259
|
* @param {string} source the bundle's text
|
|
123
|
-
* @returns {{ changed: boolean, source: string,
|
|
124
|
-
* `changed: false` means the target key was already present (idempotent no-op).
|
|
260
|
+
* @returns {{ changed: boolean, source: string, added: string[], already: string[] }}
|
|
125
261
|
*/
|
|
126
262
|
export function applyGlyphPatch(source) {
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
263
|
+
let next = source
|
|
264
|
+
const added = []
|
|
265
|
+
const already = []
|
|
266
|
+
|
|
267
|
+
for (const { target, source: sourceKey } of GLYPH_TARGETS) {
|
|
268
|
+
if (next.includes(`["${target}",`)) {
|
|
269
|
+
already.push(target)
|
|
270
|
+
continue
|
|
271
|
+
}
|
|
272
|
+
const { anchor } = resolveAnchor(next, sourceKey)
|
|
273
|
+
const sourceEntry = sliceEntry(next, anchor)
|
|
274
|
+
if (!sourceEntry.includes('svg')) {
|
|
275
|
+
throw new GlyphPatchError(`the ${anchor} entry does not look like a glyph`)
|
|
276
|
+
}
|
|
277
|
+
const clonedEntry = sourceEntry.replace(anchor, `["${target}",`)
|
|
278
|
+
// A function replacement: the entry is spliced verbatim, `$` never expanded.
|
|
279
|
+
next = next.replace(sourceEntry, () => `${sourceEntry},\n\t\t\t${clonedEntry}`)
|
|
280
|
+
added.push(target)
|
|
131
281
|
}
|
|
132
|
-
|
|
133
|
-
return { changed:
|
|
282
|
+
|
|
283
|
+
return { changed: added.length > 0, source: next, added, already }
|
|
134
284
|
}
|
|
135
285
|
|
|
136
286
|
/** Resolve the bundle to patch, or throw with an explanation. */
|
|
@@ -151,8 +301,29 @@ export function findBundle(argv, where) {
|
|
|
151
301
|
)
|
|
152
302
|
}
|
|
153
303
|
|
|
304
|
+
/**
|
|
305
|
+
* A warning block naming the other DSH installs that carry this bundle.
|
|
306
|
+
*
|
|
307
|
+
* Reported rather than patched: on a host with a global CLI install and a
|
|
308
|
+
* launcher-managed runtime, only one of them serves the UI in front of the user,
|
|
309
|
+
* and picking the wrong one is invisible — the patch succeeds and the picker
|
|
310
|
+
* stays icon-free. Naming the others turns that into a one-line fix (pass the
|
|
311
|
+
* path explicitly).
|
|
312
|
+
*
|
|
313
|
+
* @param {string[]} others bundle paths that exist but were not chosen
|
|
314
|
+
* @returns {string} an empty string when there is nothing to report
|
|
315
|
+
*/
|
|
316
|
+
function describeOtherInstalls(others) {
|
|
317
|
+
if (others.length === 0) return ''
|
|
318
|
+
return ` note: ${others.length} other DSH install(s) carry this bundle — NOT patched:\n` +
|
|
319
|
+
others.map((path) => ` ${path}\n`).join('') +
|
|
320
|
+
' If the tier still has no icon, the UI is served by one of those; re-run with\n' +
|
|
321
|
+
' that path as the argument.\n'
|
|
322
|
+
}
|
|
323
|
+
|
|
154
324
|
function main(argv) {
|
|
155
325
|
const check = argv.includes('--check')
|
|
326
|
+
const explicit = argv.slice(2).find((arg) => !arg.startsWith('-'))
|
|
156
327
|
const where = {
|
|
157
328
|
execDir: dirname(process.execPath),
|
|
158
329
|
cwd: process.cwd(),
|
|
@@ -160,17 +331,27 @@ function main(argv) {
|
|
|
160
331
|
}
|
|
161
332
|
|
|
162
333
|
const bundle = findBundle(argv, where)
|
|
334
|
+
const others = explicit === undefined
|
|
335
|
+
? candidateBundles(where).filter((candidate) => candidate !== bundle && existsSync(candidate))
|
|
336
|
+
: []
|
|
337
|
+
const otherNote = describeOtherInstalls(others)
|
|
163
338
|
const original = readFileSync(bundle, 'utf8')
|
|
164
|
-
const { changed, source: patched } = applyGlyphPatch(original)
|
|
339
|
+
const { changed, source: patched, added, already } = applyGlyphPatch(original)
|
|
165
340
|
|
|
166
341
|
if (!changed) {
|
|
167
|
-
process.stdout.write(
|
|
342
|
+
process.stdout.write(
|
|
343
|
+
`patch-permission-glyph: all glyphs already present in\n ${bundle}\n` +
|
|
344
|
+
` present: ${already.map((key) => `["${key}",]`).join(' ')}\n` +
|
|
345
|
+
otherNote,
|
|
346
|
+
)
|
|
168
347
|
return 0
|
|
169
348
|
}
|
|
170
349
|
if (check) {
|
|
171
350
|
process.stdout.write(
|
|
172
|
-
`patch-permission-glyph: ["${
|
|
173
|
-
|
|
351
|
+
`patch-permission-glyph: MISSING ${added.map((key) => `["${key}",]`).join(' ')} from\n ${bundle}\n` +
|
|
352
|
+
` present: ${already.length > 0 ? already.map((key) => `["${key}",]`).join(' ') : '(none)'}\n` +
|
|
353
|
+
' (--check: nothing written; re-run without --check to apply)\n' +
|
|
354
|
+
otherNote,
|
|
174
355
|
)
|
|
175
356
|
return 1
|
|
176
357
|
}
|
|
@@ -189,9 +370,11 @@ function main(argv) {
|
|
|
189
370
|
}
|
|
190
371
|
|
|
191
372
|
process.stdout.write(
|
|
192
|
-
`patch-permission-glyph: added ["${
|
|
373
|
+
`patch-permission-glyph: added ${added.map((key) => `["${key}",]`).join(' ')} to\n ${bundle}\n` +
|
|
374
|
+
already.map((key) => ` already present: ["${key}",]\n`).join('') +
|
|
193
375
|
` backup: ${backup}\n` +
|
|
194
|
-
' NOTE: this edits a DSH package — re-run after any DSH upgrade or reinstall.\n'
|
|
376
|
+
' NOTE: this edits a DSH package — re-run after any DSH upgrade or reinstall.\n' +
|
|
377
|
+
otherNote,
|
|
195
378
|
)
|
|
196
379
|
return 0
|
|
197
380
|
}
|