@vibe-x/dsh-html-workbench 0.2.0 → 0.3.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/src/index.js CHANGED
@@ -8,12 +8,76 @@
8
8
  * resolved relative to THIS module (`import.meta.url`) — never hard-coded to a
9
9
  * specific machine — and handed to the shared body as `config.script`.
10
10
  */
11
- import { readFileSync } from 'node:fs'
12
- import { dirname, resolve } from 'node:path'
11
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
12
+ import { tmpdir } from 'node:os'
13
+ import { dirname, join, resolve } from 'node:path'
13
14
  import { fileURLToPath } from 'node:url'
14
15
 
15
16
  const here = dirname(fileURLToPath(import.meta.url))
16
17
  const scriptPath = resolve(here, '..', 'scripts', 'workbench.py')
18
+ // The panel shows this next to the diagnostics so a pasted report says WHICH
19
+ // build produced it — without it, every bug report needs a follow-up question.
20
+ const version = JSON.parse(readFileSync(resolve(here, '..', 'package.json'), 'utf8')).version
21
+
22
+ // Must match RUNTIME_DIR_NAME in scripts/workbench.py, so the host's first guess
23
+ // and the service's own in-process fallback land in the SAME folder instead of
24
+ // scattering two differently named directories through the user's workspace.
25
+ const RUNTIME_DIR_NAME = '.html-workbench'
26
+
27
+ /**
28
+ * Whether this process can really create files in `dir` (mkdir + write + delete).
29
+ *
30
+ * A writable-looking DACL proves nothing: DSH runs the Python service as a
31
+ * sandboxed child whose restricted token may write only the session workspace and
32
+ * a private temp directory it hands the child through TEMP/TMP — never the
33
+ * ambient %TEMP% root the parent sees. Probing by an actual create is the only
34
+ * test that reflects what a child can do, and the probe must fail fast (see
35
+ * `make_temp_file` in workbench.py for why `tempfile.mkstemp` is unusable here).
36
+ *
37
+ * A successful probe leaves the directory behind, so it is marked ignored right
38
+ * away: the runtime root normally sits in the user's own repository, and probing
39
+ * must not be what dirties their `git status` (see `self_ignore` in workbench.py,
40
+ * which does the same for the directory the service settles on).
41
+ */
42
+ const probeWritableDir = (dir) => {
43
+ try {
44
+ mkdirSync(dir, { recursive: true })
45
+ const probe = join(dir, '.wb-write-probe-' + process.pid)
46
+ writeFileSync(probe, '')
47
+ rmSync(probe, { force: true })
48
+ if (!existsSync(join(dir, '.gitignore'))) {
49
+ writeFileSync(join(dir, '.gitignore'), '# Created by html-workbench: disposable logs and cache.\n*\n')
50
+ }
51
+ return true
52
+ } catch (e) {
53
+ return false
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Choose the runtime directory to ASK the service for.
59
+ *
60
+ * The workspace comes first: it is the one tree the sandbox reliably grants and it
61
+ * keeps the re-downloadable GrapesJS cache across restarts. The historical temp
62
+ * location stays as a fallback for deployments with no writable cwd.
63
+ *
64
+ * This is a request, not a guarantee — the probe runs with the parent's rights,
65
+ * which are wider than the child's. The service re-probes in-process, falls back
66
+ * on its own, and reports the directories it really used, so a wrong guess here
67
+ * costs a different folder, never a failed start.
68
+ */
69
+ const resolveRuntimeDir = () => {
70
+ const candidates = [
71
+ process.cwd() ? join(process.cwd(), RUNTIME_DIR_NAME) : null,
72
+ join(tmpdir(), RUNTIME_DIR_NAME),
73
+ ]
74
+ for (const candidate of candidates) {
75
+ if (candidate && probeWritableDir(candidate)) return candidate
76
+ }
77
+ return candidates[0] || join(tmpdir(), RUNTIME_DIR_NAME)
78
+ }
79
+
80
+ const runtimeDir = resolveRuntimeDir()
17
81
 
18
82
  // The body is a `return { ... }` statement; wrap it in a function and call it.
19
83
  // eslint-disable-next-line no-new-func
@@ -22,6 +86,11 @@ const plugin = makePlugin()
22
86
 
23
87
  export const name = 'html-workbench'
24
88
  export const inject = plugin.inject ?? []
25
- // Inject the portably-resolved script path; a deployment-provided config may
26
- // still override `script` / `editorRoot` / `port`.
27
- export const apply = (ctx, config) => plugin.apply(ctx, { script: scriptPath, ...(config || {}) })
89
+ // Inject paths resolved by Node rather than assuming POSIX separators. A
90
+ // deployment-provided config may still override every value.
91
+ export const apply = (ctx, config) => plugin.apply(ctx, {
92
+ script: scriptPath,
93
+ runtimeDir,
94
+ version,
95
+ ...(config || {}),
96
+ })