dsh-console-utf8 0.1.2 → 0.1.3

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.md CHANGED
@@ -4,6 +4,11 @@ All notable changes to this project are documented in this file.
4
4
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## 0.1.3 (2026-09-16)
8
+
9
+ - **A hook file this plugin does not own is never overwritten.** `BASH_ENV` ownership is now decided *before* anything is written — it used to stand down only after `writeShimIfChanged` had already run — and `writeShimIfChanged` refuses to replace a file that exists but does not carry this plugin's marker, raising `ForeignShimError` instead. Following the old warning's advice ("set this plugin's `shimPath` to adopt it") replaced that user's script with no backup. The warning no longer suggests it, and a foreign hook path stands the plugin down with reason `hook path holds a foreign file`.
10
+ - `SHIM_MARKER` and `ForeignShimError` are exported, and five unit tests pin the behaviour: a free path is written, this plugin's own hook follows a code-page change, a foreign file is refused and left byte-identical, ownership is decided before any write, and the marker is exactly what `shimScript()` emits.
11
+
7
12
  ## 0.1.2 (2026-09-13)
8
13
 
9
14
  - Documentation only, no code change. The READMEs gain a CI badge, the release section records that publishing goes through npm trusted publishing (OIDC) with no stored token, and the duplicated publishing heading is merged into one section.
package/dsh-plugin.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifestVersion": "0.15",
4
4
  "id": "com.dsh-tui-ecosystem.dsh-console-utf8",
5
5
  "name": "dsh-console-utf8",
6
- "version": "0.1.2",
6
+ "version": "0.1.3",
7
7
  "facets": {
8
8
  "host": {
9
9
  "entry": "lib/index.js",
package/lib/plugin.js CHANGED
@@ -120,6 +120,24 @@ export function restoreBashEnv(env, owned) {
120
120
  return 'restored'
121
121
  }
122
122
 
123
+ /**
124
+ * Substring that identifies a hook file as THIS plugin's own. Any file at the
125
+ * configured `shimPath` that does not carry it belongs to somebody else and is
126
+ * never overwritten.
127
+ */
128
+ export const SHIM_MARKER = 'dsh-console-utf8'
129
+
130
+ /** Raised instead of replacing a hook file this plugin does not own. */
131
+ export class ForeignShimError extends Error {
132
+ /**
133
+ * @param {string} path - The offending hook path.
134
+ */
135
+ constructor(path) {
136
+ super(`refusing to overwrite ${path}: the file exists and is not this plugin's hook (move it aside, or point shimPath at a free path)`)
137
+ this.name = 'ForeignShimError'
138
+ }
139
+ }
140
+
123
141
  /**
124
142
  * Write the hook when its content differs, creating the directory if needed.
125
143
  *
@@ -127,9 +145,16 @@ export function restoreBashEnv(env, owned) {
127
145
  * the fix silently do the wrong thing, so the file is compared rather than
128
146
  * assumed.
129
147
  *
148
+ * The file is only ever replaced when it is missing, already identical, or
149
+ * recognisably this plugin's own (it carries {@link SHIM_MARKER}). Following
150
+ * the old warning's advice — "set shimPath to adopt an existing BASH_ENV" —
151
+ * used to overwrite that user's script with no backup; a foreign file now
152
+ * raises {@link ForeignShimError} instead, and the plugin stands down.
153
+ *
130
154
  * @param {string} path - Hook path.
131
155
  * @param {string} content - Desired content.
132
156
  * @returns {boolean} True when the file was (re)written.
157
+ * @throws {ForeignShimError} When `path` holds a file this plugin does not own.
133
158
  */
134
159
  export function writeShimIfChanged(path, content) {
135
160
  let current
@@ -139,6 +164,7 @@ export function writeShimIfChanged(path, content) {
139
164
  current = undefined
140
165
  }
141
166
  if (current === content) return false
167
+ if (current !== undefined && !current.includes(SHIM_MARKER)) throw new ForeignShimError(path)
142
168
  mkdirSync(dirname(path), { recursive: true })
143
169
  // No BOM, LF endings: the file is sourced by `sh`, never parsed as JSON.
144
170
  writeFileSync(path, content, 'utf8')
@@ -190,24 +216,29 @@ export function setup(deps, config) {
190
216
  if (config.shellHook) {
191
217
  const shimPath = config.shimPath === '' ? defaultShimPath(deps.home) : config.shimPath
192
218
  const value = toPosixPath(shimPath)
193
- let written = false
194
- try {
195
- written = deps.ensureShim(shimPath, shimScript(config.codePage))
196
- } catch (error) {
197
- deps.log.warn(`could not write the shell hook: ${error instanceof Error ? error.message : String(error)}`)
198
- result.shim = { path: shimPath, applied: false, reason: 'hook not writable' }
199
- return result
200
- }
201
219
 
220
+ // Ownership is decided BEFORE anything is written. Standing down after a
221
+ // write would already have replaced a file this plugin does not own, so
222
+ // pointing `shimPath` at somebody else's hook must never destroy it.
202
223
  const existing = typeof deps.env.BASH_ENV === 'string' && deps.env.BASH_ENV !== '' ? deps.env.BASH_ENV : undefined
203
224
  if (existing !== undefined && existing !== value) {
204
225
  // Someone else owns BASH_ENV. Clobbering it would break their hook, so
205
- // this plugin stands down and says why; the config can point elsewhere.
206
- deps.log.warn(`BASH_ENV is already ${existing}; left untouched (set this plugin's shimPath to adopt it)`)
226
+ // this plugin stands down and says why.
227
+ deps.log.warn(`BASH_ENV is already ${existing}; left untouched. This plugin only maintains its own hook file (shimPath), so that hook wins — remove it first if this plugin should own BASH_ENV`)
207
228
  result.shim = { path: shimPath, applied: false, reason: 'BASH_ENV owned by someone else' }
208
229
  return result
209
230
  }
210
231
 
232
+ let written = false
233
+ try {
234
+ written = deps.ensureShim(shimPath, shimScript(config.codePage))
235
+ } catch (error) {
236
+ const reason = error instanceof ForeignShimError ? 'hook path holds a foreign file' : 'hook not writable'
237
+ deps.log.warn(`could not write the shell hook: ${error instanceof Error ? error.message : String(error)}`)
238
+ result.shim = { path: shimPath, applied: false, reason }
239
+ return result
240
+ }
241
+
211
242
  deps.env.BASH_ENV = value
212
243
  result.shim = {
213
244
  path: shimPath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-console-utf8",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Keep the Windows console on code page 65001 (UTF-8) for the dsh host and its bash tool commands, so output from Windows-native child processes stops being decoded as mojibake",
5
5
  "packageManager": "pnpm@11.22.0",
6
6
  "license": "MIT",