@dimina-kit/devtools 0.3.2-dev.20260525152421 → 0.3.2-dev.20260601115343

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.
Files changed (160) hide show
  1. package/README.md +20 -7
  2. package/dist/main/api.d.ts +1 -1
  3. package/dist/main/app/app.js +17 -0
  4. package/dist/main/index.bundle.js +1006 -584
  5. package/dist/main/ipc/project-fs.d.ts +57 -0
  6. package/dist/main/ipc/project-fs.js +448 -0
  7. package/dist/main/ipc/simulator-module.js +2 -0
  8. package/dist/main/ipc/views.d.ts +19 -0
  9. package/dist/main/ipc/views.js +27 -0
  10. package/dist/main/services/layout/index.d.ts +1 -1
  11. package/dist/main/services/layout/index.js +3 -3
  12. package/dist/main/services/simulator-temp-files/fs-channels.js +1 -1
  13. package/dist/main/services/simulator-temp-files/request-handler.js +1 -1
  14. package/dist/main/services/toolbar/toolbar-store.d.ts +3 -3
  15. package/dist/main/services/toolbar/toolbar-store.js +18 -1
  16. package/dist/main/services/update/update-manager.d.ts +10 -0
  17. package/dist/main/services/update/update-manager.js +16 -3
  18. package/dist/main/services/views/view-manager.d.ts +17 -4
  19. package/dist/main/services/views/view-manager.js +71 -14
  20. package/dist/main/services/workbench-context.d.ts +6 -1
  21. package/dist/main/services/workspace/workspace-service.d.ts +8 -0
  22. package/dist/main/services/workspace/workspace-service.js +22 -4
  23. package/dist/main/utils/ipc-registry.d.ts +10 -1
  24. package/dist/main/utils/ipc-registry.js +43 -0
  25. package/dist/preload/shared/expose.js +2 -2
  26. package/dist/preload/windows/main.cjs +4 -0
  27. package/dist/preload/windows/main.cjs.map +2 -2
  28. package/dist/preload/windows/simulator.js +2 -2
  29. package/dist/renderer/assets/abap-DiwvWnMr.js +2 -0
  30. package/dist/renderer/assets/apex-CmtZjKlf.js +2 -0
  31. package/dist/renderer/assets/azcli-DL2My_i-.js +2 -0
  32. package/dist/renderer/assets/bat-B-nC98wG.js +2 -0
  33. package/dist/renderer/assets/bicep-Ju5MwOgh.js +3 -0
  34. package/dist/renderer/assets/cameligo-8Eu1TyBr.js +2 -0
  35. package/dist/renderer/assets/chunk-DPI0Y_ll.js +1 -0
  36. package/dist/renderer/assets/clojure-u-RpMkH3.js +2 -0
  37. package/dist/renderer/assets/codicon-ngg6Pgfi.ttf +0 -0
  38. package/dist/renderer/assets/coffee-CdA7bbTe.js +2 -0
  39. package/dist/renderer/assets/constants-CLAQX6VZ.js +2 -0
  40. package/dist/renderer/assets/cpp-CzNFP8ks.js +2 -0
  41. package/dist/renderer/assets/csharp-j1LThmcE.js +2 -0
  42. package/dist/renderer/assets/csp-CLRC61y6.js +2 -0
  43. package/dist/renderer/assets/css-r6rC_7P2.js +4 -0
  44. package/dist/renderer/assets/css.worker-DyDu5ynT.js +90 -0
  45. package/dist/renderer/assets/cssMode-BCBgxrnA.js +2 -0
  46. package/dist/renderer/assets/cypher-CW08XVUh.js +2 -0
  47. package/dist/renderer/assets/dart-Cs9aL5T_.js +2 -0
  48. package/dist/renderer/assets/dockerfile-BWM0M184.js +2 -0
  49. package/dist/renderer/assets/ecl-MJJuer5P.js +2 -0
  50. package/dist/renderer/assets/editor-CWI9TnMM.css +1 -0
  51. package/dist/renderer/assets/editor.api2-BTaYmRq7.js +873 -0
  52. package/dist/renderer/assets/editor.worker-Dpt_xPrs.js +27 -0
  53. package/dist/renderer/assets/elixir-D2AIuXqn.js +2 -0
  54. package/dist/renderer/assets/flow9-B2H24giC.js +2 -0
  55. package/dist/renderer/assets/freemarker2-DGDGzimW.js +4 -0
  56. package/dist/renderer/assets/fsharp-CMk2OIJN.js +2 -0
  57. package/dist/renderer/assets/go-BrMkuJg0.js +2 -0
  58. package/dist/renderer/assets/graphql-PSR1UKGv.js +2 -0
  59. package/dist/renderer/assets/handlebars-bOHhw3EG.js +2 -0
  60. package/dist/renderer/assets/hcl-DAQrbDOW.js +2 -0
  61. package/dist/renderer/assets/html-BMyuX0_c.js +2 -0
  62. package/dist/renderer/assets/html.worker-IWNXhnIC.js +503 -0
  63. package/dist/renderer/assets/htmlMode-DR4kXJ7l.js +2 -0
  64. package/dist/renderer/assets/index-BDMxXUfn.js +50 -0
  65. package/dist/renderer/assets/ini-0TG5BxW0.js +2 -0
  66. package/dist/renderer/assets/input-CCYS8Ave.js +2 -0
  67. package/dist/renderer/assets/{ipc-transport-BzPzxPwf.js → ipc-transport-Bn4KXvhu.js} +5 -5
  68. package/dist/renderer/assets/ipc-transport-Cu7tubyg.css +1 -0
  69. package/dist/renderer/assets/java-rgorz17v.js +2 -0
  70. package/dist/renderer/assets/javascript-Bu-tl1P7.js +2 -0
  71. package/dist/renderer/assets/json.worker-c4h5s80L.js +59 -0
  72. package/dist/renderer/assets/jsonMode-MQV_ffLR.js +8 -0
  73. package/dist/renderer/assets/julia-C8VMdHm8.js +2 -0
  74. package/dist/renderer/assets/kotlin-AhhC4Qhm.js +2 -0
  75. package/dist/renderer/assets/less-Cgca25AP.js +3 -0
  76. package/dist/renderer/assets/lexon-D0GHdBaw.js +2 -0
  77. package/dist/renderer/assets/liquid-ClkfOvhB.js +2 -0
  78. package/dist/renderer/assets/lspLanguageFeatures-Bke_4Ie9.js +5 -0
  79. package/dist/renderer/assets/lua-DmRsNG-P.js +2 -0
  80. package/dist/renderer/assets/m3-BgL5dNKT.js +2 -0
  81. package/dist/renderer/assets/markdown-BuJfycGS.js +2 -0
  82. package/dist/renderer/assets/mdx-RZdUskPu.js +2 -0
  83. package/dist/renderer/assets/mips-C9m_93PR.js +2 -0
  84. package/dist/renderer/assets/monaco.contribution-B7rUMaEv.js +3 -0
  85. package/dist/renderer/assets/msdax-CpFHC9OI.js +2 -0
  86. package/dist/renderer/assets/mysql-qFvltsqN.js +2 -0
  87. package/dist/renderer/assets/objective-c-Bnmr858J.js +2 -0
  88. package/dist/renderer/assets/pascal-WP0_D5AO.js +2 -0
  89. package/dist/renderer/assets/pascaligo-Blom4Rij.js +2 -0
  90. package/dist/renderer/assets/perl-B-vk8g64.js +2 -0
  91. package/dist/renderer/assets/pgsql-Cgvz6v67.js +2 -0
  92. package/dist/renderer/assets/php-8a3Lrw9m.js +2 -0
  93. package/dist/renderer/assets/pla-DuFqEZ8V.js +2 -0
  94. package/dist/renderer/assets/popover-CH4rwpdt.js +2 -0
  95. package/dist/renderer/assets/postiats-DkLtSgkp.js +2 -0
  96. package/dist/renderer/assets/powerquery-BJ1aNepW.js +2 -0
  97. package/dist/renderer/assets/powershell-rE98k687.js +2 -0
  98. package/dist/renderer/assets/protobuf-CUheFacr.js +3 -0
  99. package/dist/renderer/assets/pug-LDcAMD8w.js +2 -0
  100. package/dist/renderer/assets/python-Cobrm_-8.js +2 -0
  101. package/dist/renderer/assets/qsharp-DUKSQoR1.js +2 -0
  102. package/dist/renderer/assets/r-D-QApv87.js +2 -0
  103. package/dist/renderer/assets/razor-BEUIQFNJ.js +2 -0
  104. package/dist/renderer/assets/redis-SXdDyWR9.js +2 -0
  105. package/dist/renderer/assets/redshift-Y6lsCryn.js +2 -0
  106. package/dist/renderer/assets/restructuredtext-edObr9a8.js +2 -0
  107. package/dist/renderer/assets/ruby-CNnUfF-8.js +2 -0
  108. package/dist/renderer/assets/rust-IHUZWzBr.js +2 -0
  109. package/dist/renderer/assets/sb-DrUvY44N.js +2 -0
  110. package/dist/renderer/assets/scala-B4hbXGLM.js +2 -0
  111. package/dist/renderer/assets/scheme-BGrd12j3.js +2 -0
  112. package/dist/renderer/assets/scss-x5G1ES4U.js +4 -0
  113. package/dist/renderer/assets/select-Ct4-sijm.js +2 -0
  114. package/dist/renderer/assets/settings-CMTB-t0x.js +2 -0
  115. package/dist/renderer/assets/{settings-api-BXCw9vOZ.js → settings-api-Csdngf9Z.js} +2 -2
  116. package/dist/renderer/assets/shell-DOehe2Y8.js +2 -0
  117. package/dist/renderer/assets/solidity-BeRvcwWV.js +2 -0
  118. package/dist/renderer/assets/sophia-DZbkUNjy.js +2 -0
  119. package/dist/renderer/assets/sparql-B7_oi5-h.js +2 -0
  120. package/dist/renderer/assets/sql-CTlsFWVE.js +2 -0
  121. package/dist/renderer/assets/st-DJVEJdPE.js +2 -0
  122. package/dist/renderer/assets/swift-CwhT3fYa.js +2 -0
  123. package/dist/renderer/assets/systemverilog-BQN63pkN.js +2 -0
  124. package/dist/renderer/assets/tcl-DqwfpskA.js +2 -0
  125. package/dist/renderer/assets/ts.worker-BwM5Ha3L.js +67720 -0
  126. package/dist/renderer/assets/tsMode-BHa7dl9v.js +12 -0
  127. package/dist/renderer/assets/twig-BiyenUgc.js +2 -0
  128. package/dist/renderer/assets/typescript-15aT0Yj8.js +2 -0
  129. package/dist/renderer/assets/typespec-CWOJribt.js +2 -0
  130. package/dist/renderer/assets/vb-Cq5F87m3.js +2 -0
  131. package/dist/renderer/assets/wgsl-BAvW2lVr.js +299 -0
  132. package/dist/renderer/assets/workbenchSettings-CODFCzOg.js +8 -0
  133. package/dist/renderer/assets/workers-CRBS0W-v.js +2 -0
  134. package/dist/renderer/assets/xml-bR2Zh-HV.js +2 -0
  135. package/dist/renderer/assets/yaml-Bdk54Qrb.js +2 -0
  136. package/dist/renderer/entries/main/index.html +29 -6
  137. package/dist/renderer/entries/popover/index.html +7 -6
  138. package/dist/renderer/entries/settings/index.html +6 -5
  139. package/dist/renderer/entries/workbench-settings/index.html +6 -5
  140. package/dist/shared/constants.d.ts +0 -4
  141. package/dist/shared/constants.js +0 -4
  142. package/dist/shared/ipc-channels.d.ts +24 -0
  143. package/dist/shared/ipc-channels.js +37 -0
  144. package/dist/shared/ipc-schemas.d.ts +6 -0
  145. package/dist/shared/ipc-schemas.js +15 -0
  146. package/dist/shared/types.d.ts +37 -0
  147. package/dist/shared/vpath.d.ts +42 -0
  148. package/dist/shared/vpath.js +105 -0
  149. package/dist/simulator/assets/simulator-B4e3c6_O.js +9 -0
  150. package/dist/simulator/simulator.html +1 -1
  151. package/package.json +3 -2
  152. package/dist/renderer/assets/constants-DI382mTP.js +0 -2
  153. package/dist/renderer/assets/index-4lkyF644.js +0 -46
  154. package/dist/renderer/assets/input-D4byFOLS.js +0 -2
  155. package/dist/renderer/assets/ipc-transport-DqbRdZNO.css +0 -1
  156. package/dist/renderer/assets/popover-cxKrg_os.js +0 -2
  157. package/dist/renderer/assets/select-BgSkkdHW.js +0 -2
  158. package/dist/renderer/assets/settings-oD2K1jdT.js +0 -2
  159. package/dist/renderer/assets/workbenchSettings-CTNBpTft.js +0 -8
  160. package/dist/simulator/assets/simulator-CVOFTpSj.js +0 -9
@@ -96,6 +96,20 @@ export declare const ProjectChannel: {
96
96
  readonly CaptureThumbnail: "project:captureThumbnail";
97
97
  readonly GetThumbnail: "project:getThumbnail";
98
98
  };
99
+ export declare const ProjectFsChannel: {
100
+ readonly GetRoot: "project:fs:getRoot";
101
+ readonly ReadFile: "project:fs:readFile";
102
+ readonly WriteFile: "project:fs:writeFile";
103
+ /**
104
+ * Synchronous (blocking) write, used ONLY by the editor's `beforeunload`
105
+ * flush: a hard window/app close tears the renderer down before an async
106
+ * `WriteFile` IPC can round-trip, so the last in-debounce-window edit would
107
+ * be lost. `sendSync` blocks page teardown until the bytes hit disk. Runs the
108
+ * SAME sandbox as `WriteFile` (see project-fs `writeFileSync`).
109
+ */
110
+ readonly WriteFileSync: "project:fs:writeFileSync";
111
+ readonly ListFiles: "project:fs:listFiles";
112
+ };
99
113
  export declare const ProjectsChannel: {
100
114
  readonly List: "projects:list";
101
115
  readonly Add: "projects:add";
@@ -123,6 +137,16 @@ export declare const PanelChannel: {
123
137
  readonly Select: "panel:select";
124
138
  readonly SelectSimulator: "panel:selectSimulator";
125
139
  };
140
+ export declare const ViewChannel: {
141
+ /** Update the simulator DevTools view's bounds (or hide if w/h = 0). */
142
+ readonly SimulatorDevtoolsBounds: "view:simulator:devtools-bounds";
143
+ };
144
+ export interface ViewBounds {
145
+ x: number;
146
+ y: number;
147
+ width: number;
148
+ height: number;
149
+ }
126
150
  export declare const PopoverChannel: {
127
151
  readonly Show: "popover:show";
128
152
  readonly Hide: "popover:hide";
@@ -74,6 +74,27 @@ export const ProjectChannel = {
74
74
  CaptureThumbnail: 'project:captureThumbnail',
75
75
  GetThumbnail: 'project:getThumbnail',
76
76
  };
77
+ // ── Project file system (sandboxed to active project root) ────────────────
78
+ //
79
+ // Read/write access to the active project's files, used by the in-renderer
80
+ // Monaco editor. Every path is verified against the active project root in
81
+ // the main process (see `services/project-fs`). Replaces the OpenSumi
82
+ // editor's `editor:fs:*` bridge — same sandbox, callable from the main
83
+ // renderer instead of a separate WebContentsView.
84
+ export const ProjectFsChannel = {
85
+ GetRoot: 'project:fs:getRoot',
86
+ ReadFile: 'project:fs:readFile',
87
+ WriteFile: 'project:fs:writeFile',
88
+ /**
89
+ * Synchronous (blocking) write, used ONLY by the editor's `beforeunload`
90
+ * flush: a hard window/app close tears the renderer down before an async
91
+ * `WriteFile` IPC can round-trip, so the last in-debounce-window edit would
92
+ * be lost. `sendSync` blocks page teardown until the bytes hit disk. Runs the
93
+ * SAME sandbox as `WriteFile` (see project-fs `writeFileSync`).
94
+ */
95
+ WriteFileSync: 'project:fs:writeFileSync',
96
+ ListFiles: 'project:fs:listFiles',
97
+ };
77
98
  // ── Project list / workspace ─────────────────────────────────────────────
78
99
  export const ProjectsChannel = {
79
100
  List: 'projects:list',
@@ -99,6 +120,22 @@ export const PanelChannel = {
99
120
  Select: 'panel:select',
100
121
  SelectSimulator: 'panel:selectSimulator',
101
122
  };
123
+ // ── Embedded views (renderer → main) ─────────────────────────────────────
124
+ //
125
+ // The main window's React layout owns the *positions* of the editor +
126
+ // simulator-DevTools WebContentsView overlays — each visible placeholder
127
+ // `<div>` measures its client rect via ResizeObserver and pushes the
128
+ // rectangle to the main process. The view manager caches the latest
129
+ // payload per kind and applies it to the overlay; no payload means the
130
+ // overlay is hidden.
131
+ //
132
+ // Payload (after schema validation): `{ x, y, width, height }` in CSS
133
+ // pixels relative to the window's content area (origin = top-left,
134
+ // not including the OS chrome).
135
+ export const ViewChannel = {
136
+ /** Update the simulator DevTools view's bounds (or hide if w/h = 0). */
137
+ SimulatorDevtoolsBounds: 'view:simulator:devtools-bounds',
138
+ };
102
139
  // ── Popover ──────────────────────────────────────────────────────────────
103
140
  export const PopoverChannel = {
104
141
  Show: 'popover:show',
@@ -29,6 +29,12 @@ export declare const PopoverShowSchema: z.ZodTuple<[z.ZodObject<{}, z.core.$loos
29
29
  export declare const SimulatorAttachSchema: z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>;
30
30
  /** simulator:resize — simulator width. */
31
31
  export declare const SimulatorResizeSchema: z.ZodTuple<[z.ZodNumber], null>;
32
+ export declare const ViewBoundsSchema: z.ZodTuple<[z.ZodObject<{
33
+ x: z.ZodNumber;
34
+ y: z.ZodNumber;
35
+ width: z.ZodNumber;
36
+ height: z.ZodNumber;
37
+ }, z.core.$strip>], null>;
32
38
  /** simulator:setVisible — visible flag + simulator width. */
33
39
  export declare const SimulatorSetVisibleSchema: z.ZodTuple<[z.ZodBoolean, z.ZodNumber], null>;
34
40
  /**
@@ -57,6 +57,21 @@ export const SimulatorAttachSchema = z.tuple([
57
57
  ]);
58
58
  /** simulator:resize — simulator width. */
59
59
  export const SimulatorResizeSchema = z.tuple([SimWidth]);
60
+ /**
61
+ * view:*:bounds — renderer-measured CSS pixel rectangle in window content
62
+ * coordinates. Width/height = 0 is the canonical "overlay hidden" signal.
63
+ * No upper bound is enforced (window can be very large); we only refuse
64
+ * negative/NaN values that would break setBounds.
65
+ */
66
+ const NonNegInt = z.number().int().min(0).max(100_000);
67
+ export const ViewBoundsSchema = z.tuple([
68
+ z.object({
69
+ x: NonNegInt,
70
+ y: NonNegInt,
71
+ width: NonNegInt,
72
+ height: NonNegInt,
73
+ }),
74
+ ]);
60
75
  /** simulator:setVisible — visible flag + simulator width. */
61
76
  export const SimulatorSetVisibleSchema = z.tuple([z.boolean(), SimWidth]);
62
77
  /**
@@ -27,9 +27,33 @@ export type BuiltinModuleId = 'projects' | 'session' | 'simulator' | 'popover' |
27
27
  * Cross-IPC / renderer shape of a toolbar action. Carries no `handler` — the
28
28
  * handler is non-serialisable and never crosses the IPC boundary.
29
29
  */
30
+ export type ToolbarActionPlacement = 'leading' | 'primary' | 'trailing';
31
+ export type ToolbarActionKind = 'button' | 'avatar';
32
+ export type BeforeOpenProjectResult = {
33
+ allow: true;
34
+ statusMessage?: string;
35
+ } | {
36
+ allow: false;
37
+ reason: string;
38
+ userMessage?: string;
39
+ };
30
40
  export interface ToolbarAction {
31
41
  id: string;
32
42
  label: string;
43
+ /** Visual treatment. `avatar` keeps an account entry circular even without an image URL. */
44
+ kind?: ToolbarActionKind;
45
+ /**
46
+ * Optional visual placement in the project-window header.
47
+ * Omitted actions default to `primary` so existing hosts remain visible
48
+ * near the compile controls.
49
+ */
50
+ placement?: ToolbarActionPlacement;
51
+ /** Short text glyph rendered before the label for compact host actions. */
52
+ icon?: string;
53
+ /** Explicit fallback text for avatar actions when no image URL is available. */
54
+ displayInitial?: string;
55
+ /** Avatar image URL for account-style actions. Falls back to label initials. */
56
+ avatarUrl?: string;
33
57
  }
34
58
  /**
35
59
  * Host-facing toolbar action passed to `instance.toolbar.set()`. Includes the
@@ -39,6 +63,11 @@ export interface ToolbarAction {
39
63
  export interface ToolbarActionInput {
40
64
  id: string;
41
65
  label: string;
66
+ kind?: ToolbarActionKind;
67
+ placement?: ToolbarActionPlacement;
68
+ icon?: string;
69
+ displayInitial?: string;
70
+ avatarUrl?: string;
42
71
  handler: () => void | Promise<void>;
43
72
  }
44
73
  export interface WorkbenchConfig {
@@ -132,6 +161,10 @@ export interface WorkbenchHostInstance {
132
161
  set(actions: ToolbarActionInput[]): void;
133
162
  };
134
163
  }
164
+ export type BeforeOpenProjectHook = (ctx: {
165
+ projectPath: string;
166
+ instance: WorkbenchHostInstance;
167
+ }) => BeforeOpenProjectResult | Promise<BeforeOpenProjectResult>;
135
168
  /**
136
169
  * Pluggable backend for the project-list panel. Hosts may supply this to
137
170
  * take over project storage from the default `<userData>/dimina-projects.json`.
@@ -149,6 +182,8 @@ export interface ProjectsProvider {
149
182
  updateLastOpened?(dirPath: string): void | Promise<void>;
150
183
  getCompileConfig?(dirPath: string): unknown;
151
184
  saveCompileConfig?(dirPath: string, cfg: unknown): void | Promise<void>;
185
+ saveThumbnail?(dirPath: string, imageDataUrl: string): void | Promise<void>;
186
+ getThumbnail?(dirPath: string): string | null | Promise<string | null>;
152
187
  }
153
188
  export interface ProjectTemplate {
154
189
  id: string;
@@ -182,6 +217,8 @@ export interface WorkbenchAppConfig extends WorkbenchConfig {
182
217
  menuBuilder?: (mainWindow: import('electron').BrowserWindow, menuContext: MenuContext) => void;
183
218
  /** Called after window and context are created but before start() resolves. Use to register custom IPC handlers. */
184
219
  onSetup?: (instance: WorkbenchHostInstance) => void | Promise<void>;
220
+ /** Called after project-directory validation but before an active session is closed and a new project compile starts. */
221
+ onBeforeOpenProject?: BeforeOpenProjectHook;
185
222
  /** Called before window close when a session is active. Session disposal happens automatically after this hook. */
186
223
  onBeforeClose?: (instance: WorkbenchHostInstance) => void | Promise<void>;
187
224
  /** Custom update checker. If provided, enables the check-for-updates feature. */
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Phase 0 vpath resolver.
3
+ *
4
+ * Spec: `docs/file-system.md` §3.3 — single resolver shared by every FSM
5
+ * entry, the renderer-side temp store, and (Phase 1) the main-process
6
+ * protocol.handle / disk reader.
7
+ *
8
+ * resolveVPath(url): { kind, writable, realPath? } | null
9
+ *
10
+ * - `difile://_tmp/<id>` → kind=tmp, writable=false, no realPath
11
+ * - `difile://_store/<id>` → kind=store, writable=false, realPath in base
12
+ * - `difile://<rel>` → kind=usr, writable=true, realPath in base
13
+ *
14
+ * Security invariants (the resolver returns `null` instead of throwing):
15
+ * - any non-`difile://` scheme is rejected outright;
16
+ * - any `..` segment (raw or URL-encoded) is rejected before canonicalize;
17
+ * - after `path.normalize` the resulting `realPath` MUST stay inside the
18
+ * sandbox base; otherwise `null`;
19
+ * - reserved prefixes `_tmp/` and `_store/` are case-sensitive — `_TMP/`,
20
+ * `_Tmp/`, etc. fall through to the user-data namespace.
21
+ */
22
+ export type VPathKind = 'tmp' | 'store' | 'usr';
23
+ export interface ResolvedVPath {
24
+ kind: VPathKind;
25
+ /** `tmp` and `store` are runtime-owned and read-only; `usr` is writable. */
26
+ writable: boolean;
27
+ /**
28
+ * Canonical local path on disk for `store` / `usr` kinds (always within the
29
+ * USER_DATA_PATH base directory after canonicalize). `tmp` kind has no
30
+ * realPath — the bytes live in the renderer-side Blob Map.
31
+ */
32
+ realPath?: string;
33
+ }
34
+ /**
35
+ * USER_DATA_PATH sandbox base. Looked up dynamically on every call so test
36
+ * doubles (e.g. monkey-patching `os.homedir`) and the `DIMINA_HOME` env
37
+ * override apply per invocation. The env override exists for headless test
38
+ * affordance and to keep the renderer/main store sharing one base.
39
+ */
40
+ export declare function sandboxBase(): string;
41
+ export declare function resolveVPath(url: unknown): ResolvedVPath | null;
42
+ //# sourceMappingURL=vpath.d.ts.map
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Phase 0 vpath resolver.
3
+ *
4
+ * Spec: `docs/file-system.md` §3.3 — single resolver shared by every FSM
5
+ * entry, the renderer-side temp store, and (Phase 1) the main-process
6
+ * protocol.handle / disk reader.
7
+ *
8
+ * resolveVPath(url): { kind, writable, realPath? } | null
9
+ *
10
+ * - `difile://_tmp/<id>` → kind=tmp, writable=false, no realPath
11
+ * - `difile://_store/<id>` → kind=store, writable=false, realPath in base
12
+ * - `difile://<rel>` → kind=usr, writable=true, realPath in base
13
+ *
14
+ * Security invariants (the resolver returns `null` instead of throwing):
15
+ * - any non-`difile://` scheme is rejected outright;
16
+ * - any `..` segment (raw or URL-encoded) is rejected before canonicalize;
17
+ * - after `path.normalize` the resulting `realPath` MUST stay inside the
18
+ * sandbox base; otherwise `null`;
19
+ * - reserved prefixes `_tmp/` and `_store/` are case-sensitive — `_TMP/`,
20
+ * `_Tmp/`, etc. fall through to the user-data namespace.
21
+ */
22
+ // Main-process (Node ESM) resolves these natively. Simulator (vite browser
23
+ // bundle) externalizes node builtins as no-op stubs — that's safe here only
24
+ // because the simulator never enters the `store`/`usr` branches below
25
+ // (renderer-side FSM forwards disk-backed reads/writes to main via IPC).
26
+ import os from 'node:os';
27
+ import path from 'node:path';
28
+ const DIFILE_PREFIX = 'difile://';
29
+ /**
30
+ * USER_DATA_PATH sandbox base. Looked up dynamically on every call so test
31
+ * doubles (e.g. monkey-patching `os.homedir`) and the `DIMINA_HOME` env
32
+ * override apply per invocation. The env override exists for headless test
33
+ * affordance and to keep the renderer/main store sharing one base.
34
+ */
35
+ export function sandboxBase() {
36
+ const env = (typeof process !== 'undefined' && process.env && process.env.DIMINA_HOME) || '';
37
+ if (env) {
38
+ return path.join(env, 'files');
39
+ }
40
+ return path.join(os.homedir(), '.dimina', 'files');
41
+ }
42
+ export function resolveVPath(url) {
43
+ if (typeof url !== 'string')
44
+ return null;
45
+ if (!url.startsWith(DIFILE_PREFIX))
46
+ return null;
47
+ const raw = url.slice(DIFILE_PREFIX.length);
48
+ if (raw.length === 0)
49
+ return null;
50
+ // Decode URL-encoded sequences first so `%2e%2e` style escapes are caught
51
+ // by the segment check below.
52
+ let decoded;
53
+ try {
54
+ decoded = decodeURIComponent(raw);
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ // Reject NUL bytes (raw or %00) — Node `fs.*` throws TypeError on a NUL
60
+ // in the path, bypassing the API's `fail` callback and crashing the
61
+ // renderer. Pull the rejection up to the validator.
62
+ if (decoded.includes('\0'))
63
+ return null;
64
+ // Developer convention: `wx.env.USER_DATA_PATH = 'difile://'` so a write
65
+ // like `${USER_DATA_PATH}/foo.txt` produces `difile:///foo.txt`. Strip
66
+ // leading slashes so that path-joins cleanly under the sandbox base.
67
+ decoded = decoded.replace(/^[/\\]+/, '');
68
+ if (decoded.length === 0)
69
+ return null;
70
+ // Reject any '..' or '.' segment after decoding. We split on both '/' and
71
+ // '\\' so Windows-style separators cannot smuggle a traversal segment past
72
+ // the validator on POSIX hosts.
73
+ const segments = decoded.split(/[/\\]+/);
74
+ if (segments.some(s => s === '..' || s === '.'))
75
+ return null;
76
+ // Classify by reserved prefix (case-sensitive — `_TMP/` is a user file).
77
+ let kind;
78
+ let writable;
79
+ if (decoded.startsWith('_tmp/')) {
80
+ kind = 'tmp';
81
+ writable = false;
82
+ }
83
+ else if (decoded.startsWith('_store/')) {
84
+ kind = 'store';
85
+ writable = false;
86
+ }
87
+ else {
88
+ kind = 'usr';
89
+ writable = true;
90
+ }
91
+ // `tmp` lives in the renderer Blob Map — no realPath.
92
+ if (kind === 'tmp') {
93
+ return { kind, writable };
94
+ }
95
+ // `store` / `usr`: anchor under the sandbox base and assert containment
96
+ // after normalization. Defense in depth against any traversal that the
97
+ // segment check above missed (e.g. backslash trickery on POSIX).
98
+ const base = sandboxBase();
99
+ const joined = path.join(base, decoded);
100
+ const normalized = path.normalize(joined);
101
+ if (normalized !== base && !normalized.startsWith(base + path.sep))
102
+ return null;
103
+ return { kind, writable, realPath: normalized };
104
+ }
105
+ //# sourceMappingURL=vpath.js.map