create-mendix-widget-gleam 3.0.1 → 4.0.0
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/README.md +8 -9
- package/package.json +1 -1
- package/src/i18n.mjs +12 -15
- package/src/index.mjs +20 -31
- package/src/templates/claude_md.mjs +10 -8
- package/src/templates/readme_md.mjs +60 -69
- package/src/templates/widgets_readme.mjs +15 -15
- package/template/docs/glendix_guide.md +243 -137
- package/template/docs/mendraw_guide.md +671 -0
- package/template/gleam.toml +3 -1
- package/template/package.json +5 -6
- package/template/src/__widget_name__.gleam +1 -1
- package/template/src/editor_config.gleam +1 -1
- package/template/src/editor_preview.gleam +1 -1
- package/template/rollup.config.mjs +0 -10
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# glendix
|
|
1
|
+
# glendix v4.0.2 — Agent Reference Guide
|
|
2
2
|
|
|
3
3
|
> 이 문서는 AI 에이전트(LLM)가 glendix 코드를 작성할 때 참조하는 가이드입니다. 각 섹션은 독립적으로 읽을 수 있습니다.
|
|
4
4
|
|
|
@@ -14,9 +14,10 @@ glendix는 Gleam으로 Mendix Pluggable Widget을 작성하는 FFI 라이브러
|
|
|
14
14
|
|--------|------------|-------------|
|
|
15
15
|
| React 바인딩 (엘리먼트, 훅, 이벤트, HTML/SVG) | `redraw`, `redraw_dom` | 사용하지 않음 — 직접 import |
|
|
16
16
|
| TEA 패턴 (Model-Update-View) | `lustre` | `glendix/lustre` 브릿지 제공 |
|
|
17
|
-
| Mendix API (JsProps, EditableValue, ListValue 등) | `
|
|
18
|
-
| 외부 JS 컴포넌트 (widget, binding) → React | `
|
|
19
|
-
|
|
|
17
|
+
| Mendix API (JsProps, EditableValue, ListValue 등) | `mendraw` | 핵심 담당 |
|
|
18
|
+
| 외부 JS 컴포넌트 (widget, binding) → React | `mendraw/interop` | 브릿지 제공 |
|
|
19
|
+
| Mendix 위젯 (.mpk, Classic, 마켓플레이스) | `mendraw` | 핵심 담당 |
|
|
20
|
+
| 빌드/설치 | `glendix` | 핵심 담당 |
|
|
20
21
|
|
|
21
22
|
**의존성 구조:**
|
|
22
23
|
|
|
@@ -25,13 +26,15 @@ glendix는 Gleam으로 Mendix Pluggable Widget을 작성하는 FFI 라이브러
|
|
|
25
26
|
├── redraw ← React 훅, 컴포넌트, fragment 등
|
|
26
27
|
├── redraw_dom ← HTML/SVG 태그, 속성, 이벤트
|
|
27
28
|
├── lustre ← TEA update/view (선택)
|
|
29
|
+
├── mendraw
|
|
30
|
+
│ ├── mendix ← Mendix API 타입 + props 접근
|
|
31
|
+
│ ├── interop ← 외부 JS 컴포넌트 → redraw.Element
|
|
32
|
+
│ ├── widget ← .mpk 위젯 컴포넌트 (gleam.toml 자동 다운로드)
|
|
33
|
+
│ ├── classic ← Classic (Dojo) 위젯
|
|
34
|
+
│ └── marketplace ← Mendix Marketplace 검색/다운로드
|
|
28
35
|
└── glendix
|
|
29
|
-
├── mendix ← Mendix API 타입 + props 접근
|
|
30
|
-
├── interop ← 외부 JS 컴포넌트 → redraw.Element
|
|
31
36
|
├── lustre ← Lustre Element → redraw.Element 브릿지
|
|
32
|
-
├──
|
|
33
|
-
├── binding ← bindings.json 외부 React 컴포넌트
|
|
34
|
-
├── classic ← Classic (Dojo) 위젯
|
|
37
|
+
├── binding ← 외부 React 컴포넌트 (gleam.toml [tools.glendix.bindings])
|
|
35
38
|
└── js/* ← JS interop escape hatch
|
|
36
39
|
```
|
|
37
40
|
|
|
@@ -43,23 +46,25 @@ glendix는 Gleam으로 Mendix Pluggable Widget을 작성하는 FFI 라이브러
|
|
|
43
46
|
|
|
44
47
|
```toml
|
|
45
48
|
[dependencies]
|
|
46
|
-
glendix = ">=
|
|
49
|
+
glendix = ">= 4.0.2 and < 5.0.0"
|
|
50
|
+
mendraw = ">= 1.1.10 and < 2.0.0"
|
|
47
51
|
```
|
|
48
52
|
|
|
49
53
|
Peer dependency (위젯 프로젝트 `package.json`):
|
|
50
54
|
|
|
51
55
|
```json
|
|
52
56
|
{
|
|
53
|
-
"dependencies": { "big.js": "^6.0.0" },
|
|
54
|
-
"overrides": { "@types/react": "19.0.0", "@types/react-dom": "19.0.0" },
|
|
55
|
-
"resolutions": { "@types/react": "19.0.0", "@types/react-dom": "19.0.0" }
|
|
57
|
+
"dependencies": { "big.js": "^6.0.0" }, // decimal 속성 사용 시
|
|
58
|
+
"overrides": { "react": "19.0.0", "react-dom": "19.0.0", "@types/react": "19.0.0", "@types/react-dom": "19.0.0" },
|
|
59
|
+
"resolutions": { "react": "19.0.0", "react-dom": "19.0.0", "@types/react": "19.0.0", "@types/react-dom": "19.0.0" }
|
|
56
60
|
}
|
|
57
61
|
```
|
|
58
62
|
|
|
59
|
-
> `react`/`react-dom`은 `dependencies`에 넣지 않는다. `pluggable-widgets-tools`가 자동 제공하며, 직접 선언하면
|
|
63
|
+
> - `react`/`react-dom`은 `dependencies`에 넣지 않는다. `pluggable-widgets-tools`가 자동 제공하며, 직접 선언하면 번들 충돌이 발생한다.
|
|
64
|
+
> - `overrides`/`resolutions`에서 반드시 **캐럿(`^`) 없이 정확한 버전**을 지정한다. `^19.0.0`은 react와 react-dom이 서로 다른 19.x.x로 해석되어 런타임 버전 불일치 에러를 일으킨다.
|
|
60
65
|
|
|
61
66
|
```bash
|
|
62
|
-
gleam run -m glendix/install # 의존성 설치 + 바인딩 생성
|
|
67
|
+
gleam run -m glendix/install # 의존성 설치 + TOML 위젯 다운로드 + 바인딩 생성
|
|
63
68
|
gleam build # 컴파일 확인
|
|
64
69
|
```
|
|
65
70
|
|
|
@@ -70,18 +75,18 @@ gleam build # 컴파일 확인
|
|
|
70
75
|
모든 Mendix Pluggable Widget은 이 시그니처를 따릅니다:
|
|
71
76
|
|
|
72
77
|
```gleam
|
|
73
|
-
import
|
|
78
|
+
import mendraw/mendix.{type JsProps}
|
|
74
79
|
import redraw.{type Element}
|
|
75
80
|
|
|
76
81
|
pub fn widget(props: JsProps) -> Element
|
|
77
82
|
```
|
|
78
83
|
|
|
79
|
-
- `JsProps` — Mendix가 전달하는 props 객체 (opaque). `
|
|
84
|
+
- `JsProps` — Mendix가 전달하는 props 객체 (opaque). `mendraw/mendix` 모듈의 접근자로만 읽는다.
|
|
80
85
|
- `Element` — redraw의 React 엘리먼트 타입. `redraw/dom/html`, `redraw.fragment()` 등으로 생성한다.
|
|
81
86
|
|
|
82
87
|
---
|
|
83
88
|
|
|
84
|
-
##
|
|
89
|
+
## 4. 렌더링 경로 선택
|
|
85
90
|
|
|
86
91
|
glendix는 두 가지 렌더링 경로를 지원합니다. 둘 다 `redraw.Element`를 반환하므로 자유롭게 합성 가능합니다.
|
|
87
92
|
|
|
@@ -97,19 +102,19 @@ glendix는 두 가지 렌더링 경로를 지원합니다. 둘 다 `redraw.Eleme
|
|
|
97
102
|
|
|
98
103
|
---
|
|
99
104
|
|
|
100
|
-
##
|
|
105
|
+
## 5. redraw 렌더링 경로 — 레퍼런스
|
|
101
106
|
|
|
102
|
-
###
|
|
107
|
+
### 5.1 필수 import 패턴
|
|
103
108
|
|
|
104
109
|
```gleam
|
|
105
|
-
import
|
|
110
|
+
import mendraw/mendix.{type JsProps} // Mendix props 타입
|
|
106
111
|
import redraw.{type Element} // 반환 타입
|
|
107
112
|
import redraw/dom/html // HTML 태그 함수
|
|
108
113
|
import redraw/dom/attribute // HTML 속성
|
|
109
114
|
import redraw/dom/events // 이벤트 핸들러
|
|
110
115
|
```
|
|
111
116
|
|
|
112
|
-
###
|
|
117
|
+
### 5.2 HTML 엘리먼트 생성
|
|
113
118
|
|
|
114
119
|
```gleam
|
|
115
120
|
// 속성 + 자식
|
|
@@ -124,7 +129,7 @@ html.img([attribute.src("image.png"), attribute.alt("설명")])
|
|
|
124
129
|
html.br([])
|
|
125
130
|
```
|
|
126
131
|
|
|
127
|
-
###
|
|
132
|
+
### 5.3 텍스트, 빈 렌더링, Fragment
|
|
128
133
|
|
|
129
134
|
```gleam
|
|
130
135
|
html.text("안녕하세요") // 텍스트 노드
|
|
@@ -135,7 +140,7 @@ html.none() // 아무것도 렌더링하지 않음 (Rea
|
|
|
135
140
|
redraw.fragment([child1, child2]) // Fragment
|
|
136
141
|
```
|
|
137
142
|
|
|
138
|
-
###
|
|
143
|
+
### 5.4 조건부 렌더링
|
|
139
144
|
|
|
140
145
|
v3.0에서는 Gleam `case` 표현식을 직접 사용합니다:
|
|
141
146
|
|
|
@@ -160,7 +165,7 @@ case mendix.get_status(value) {
|
|
|
160
165
|
}
|
|
161
166
|
```
|
|
162
167
|
|
|
163
|
-
###
|
|
168
|
+
### 5.5 리스트 렌더링
|
|
164
169
|
|
|
165
170
|
```gleam
|
|
166
171
|
import gleam/list
|
|
@@ -174,7 +179,7 @@ html.ul([], list.map(items, fn(item) {
|
|
|
174
179
|
|
|
175
180
|
> 리스트 렌더링 시 `attribute.key()`를 항상 설정해야 합니다. React reconciliation에 필요합니다.
|
|
176
181
|
|
|
177
|
-
###
|
|
182
|
+
### 5.6 속성
|
|
178
183
|
|
|
179
184
|
```gleam
|
|
180
185
|
import redraw/dom/attribute
|
|
@@ -199,7 +204,7 @@ attribute.attribute("data-custom", "value")
|
|
|
199
204
|
attribute.ref(my_ref)
|
|
200
205
|
```
|
|
201
206
|
|
|
202
|
-
###
|
|
207
|
+
### 5.7 이벤트 핸들러
|
|
203
208
|
|
|
204
209
|
```gleam
|
|
205
210
|
import redraw/dom/events
|
|
@@ -216,7 +221,7 @@ events.on_blur(fn(e) { Nil })
|
|
|
216
221
|
events.on_click_capture(fn(e) { Nil })
|
|
217
222
|
```
|
|
218
223
|
|
|
219
|
-
###
|
|
224
|
+
### 5.8 Hooks
|
|
220
225
|
|
|
221
226
|
모든 훅은 `redraw` 메인 모듈에 있습니다:
|
|
222
227
|
|
|
@@ -233,8 +238,11 @@ redraw.use_effect(fn() { Nil }, deps) // 의존성 지정
|
|
|
233
238
|
redraw.use_effect_(fn() { fn() { cleanup() } }, deps) // 클린업 포함
|
|
234
239
|
|
|
235
240
|
// Ref
|
|
236
|
-
|
|
237
|
-
let my_ref = redraw.
|
|
241
|
+
import redraw/ref
|
|
242
|
+
let my_ref = redraw.use_ref() // Ref(Option(a))
|
|
243
|
+
let my_ref = redraw.use_ref_(initial) // Ref(a) — 초기값 지정
|
|
244
|
+
ref.current(my_ref) // 현재 값 읽기
|
|
245
|
+
ref.assign(my_ref, new_value) // 값 쓰기
|
|
238
246
|
|
|
239
247
|
// 메모이제이션
|
|
240
248
|
let result = redraw.use_memo(fn() { expensive(data) }, data)
|
|
@@ -252,7 +260,7 @@ let #(is_pending, start) = redraw.use_transition()
|
|
|
252
260
|
let deferred = redraw.use_deferred_value(value)
|
|
253
261
|
```
|
|
254
262
|
|
|
255
|
-
###
|
|
263
|
+
### 5.9 컴포넌트 정의
|
|
256
264
|
|
|
257
265
|
```gleam
|
|
258
266
|
import redraw
|
|
@@ -266,7 +274,7 @@ let my_comp = redraw.component_("MyComponent", fn(props) {
|
|
|
266
274
|
let memoized = redraw.memoize_(my_comp)
|
|
267
275
|
```
|
|
268
276
|
|
|
269
|
-
###
|
|
277
|
+
### 5.10 Context API
|
|
270
278
|
|
|
271
279
|
```gleam
|
|
272
280
|
import redraw
|
|
@@ -280,7 +288,7 @@ redraw.provider(theme_ctx, "dark", [child_elements])
|
|
|
280
288
|
let theme = redraw.use_context(theme_ctx)
|
|
281
289
|
```
|
|
282
290
|
|
|
283
|
-
###
|
|
291
|
+
### 5.11 SVG
|
|
284
292
|
|
|
285
293
|
```gleam
|
|
286
294
|
import redraw/dom/svg
|
|
@@ -298,16 +306,16 @@ svg.svg([attribute.attribute("viewBox", "0 0 100 100")], [
|
|
|
298
306
|
|
|
299
307
|
---
|
|
300
308
|
|
|
301
|
-
##
|
|
309
|
+
## 6. lustre 렌더링 경로 — 레퍼런스
|
|
302
310
|
|
|
303
|
-
###
|
|
311
|
+
### 6.1 TEA 패턴 (use_tea)
|
|
304
312
|
|
|
305
313
|
`update`와 `view`는 표준 lustre 코드와 100% 동일합니다. 진입점만 `glendix/lustre.use_tea()`를 사용합니다.
|
|
306
314
|
|
|
307
315
|
```gleam
|
|
308
316
|
import gleam/int
|
|
309
317
|
import glendix/lustre as gl
|
|
310
|
-
import
|
|
318
|
+
import mendraw/mendix.{type JsProps}
|
|
311
319
|
import lustre/effect
|
|
312
320
|
import lustre/element/html
|
|
313
321
|
import lustre/event
|
|
@@ -347,7 +355,7 @@ pub fn widget(_props: JsProps) -> Element {
|
|
|
347
355
|
}
|
|
348
356
|
```
|
|
349
357
|
|
|
350
|
-
###
|
|
358
|
+
### 6.2 Simple TEA (use_simple) — Effect 없음
|
|
351
359
|
|
|
352
360
|
```gleam
|
|
353
361
|
import glendix/lustre as gl
|
|
@@ -364,7 +372,7 @@ fn update_simple(model: Model, msg: Msg) -> Model {
|
|
|
364
372
|
}
|
|
365
373
|
```
|
|
366
374
|
|
|
367
|
-
###
|
|
375
|
+
### 6.3 Lustre Element를 수동으로 변환 (render)
|
|
368
376
|
|
|
369
377
|
lustre 뷰를 React 트리 안에 삽입할 때 사용합니다:
|
|
370
378
|
|
|
@@ -374,7 +382,7 @@ import glendix/lustre as gl
|
|
|
374
382
|
let react_element = gl.render(lustre_element, dispatch_fn)
|
|
375
383
|
```
|
|
376
384
|
|
|
377
|
-
###
|
|
385
|
+
### 6.4 redraw Element를 lustre 트리에 삽입 (embed)
|
|
378
386
|
|
|
379
387
|
lustre view 안에서 redraw 컴포넌트를 사용할 때 호출합니다:
|
|
380
388
|
|
|
@@ -401,33 +409,30 @@ fn view(model: Model) {
|
|
|
401
409
|
|
|
402
410
|
---
|
|
403
411
|
|
|
404
|
-
##
|
|
412
|
+
## 7. 외부 컴포넌트 통합
|
|
405
413
|
|
|
406
|
-
###
|
|
414
|
+
### 7.1 모듈 선택 가이드
|
|
407
415
|
|
|
408
416
|
| 컴포넌트 출처 | 사용 모듈 | 예시 |
|
|
409
417
|
|--------------|----------|------|
|
|
410
|
-
| npm 패키지 (React 컴포넌트) | `glendix/binding` + `
|
|
411
|
-
| `.mpk` Pluggable 위젯 | `
|
|
412
|
-
| `.mpk` Classic (Dojo) 위젯 | `
|
|
418
|
+
| npm 패키지 (React 컴포넌트) | `glendix/binding` + `mendraw/interop` | recharts, @mui |
|
|
419
|
+
| `.mpk` Pluggable 위젯 (gleam.toml) | `mendraw/widget` + `mendraw/interop` | Switch, Badge |
|
|
420
|
+
| `.mpk` Classic (Dojo) 위젯 (gleam.toml) | `mendraw/classic` | CameraWidget |
|
|
413
421
|
|
|
414
|
-
###
|
|
422
|
+
### 7.2 외부 React 컴포넌트 (binding + interop)
|
|
415
423
|
|
|
416
|
-
**설정:** `
|
|
424
|
+
**설정:** `gleam.toml`에 바인딩 추가 → `npm install 패키지명` → `gleam run -m glendix/install`
|
|
417
425
|
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
"components": ["PieChart", "Pie", "Cell", "Tooltip"]
|
|
422
|
-
}
|
|
423
|
-
}
|
|
426
|
+
```toml
|
|
427
|
+
[tools.glendix.bindings]
|
|
428
|
+
recharts = ["PieChart", "Pie", "Cell", "Tooltip"]
|
|
424
429
|
```
|
|
425
430
|
|
|
426
431
|
**Gleam 래퍼 작성:**
|
|
427
432
|
|
|
428
433
|
```gleam
|
|
429
434
|
import glendix/binding
|
|
430
|
-
import
|
|
435
|
+
import mendraw/interop
|
|
431
436
|
import redraw.{type Element}
|
|
432
437
|
import redraw/dom/attribute.{type Attribute}
|
|
433
438
|
|
|
@@ -450,17 +455,26 @@ pub fn tooltip(attrs: List(Attribute)) -> Element {
|
|
|
450
455
|
| `interop.component_el_(comp, children)` | 자식만 |
|
|
451
456
|
| `interop.void_component_el(comp, attrs)` | self-closing (자식 없음) |
|
|
452
457
|
|
|
453
|
-
###
|
|
458
|
+
### 7.3 .mpk Pluggable 위젯 (widget + interop)
|
|
459
|
+
|
|
460
|
+
`gleam.toml`에 위젯을 등록하고 `gleam run -m glendix/install`로 자동 다운로드합니다:
|
|
454
461
|
|
|
455
|
-
|
|
462
|
+
```toml
|
|
463
|
+
[tools.mendraw.widgets.Charts]
|
|
464
|
+
version = "3.0.0"
|
|
465
|
+
# s3_id = "com/..." ← 있으면 인증 없이 직접 다운로드
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
`build/widgets/`에 캐시하고 바인딩을 자동 생성합니다.
|
|
469
|
+
Marketplace TUI(`gleam run -m mendraw/marketplace`)에서 다운로드하면 gleam.toml에 자동 추가됩니다.
|
|
456
470
|
|
|
457
471
|
자동 생성되는 `src/widgets/*.gleam`:
|
|
458
472
|
|
|
459
473
|
```gleam
|
|
460
|
-
import
|
|
461
|
-
import
|
|
462
|
-
import
|
|
463
|
-
import
|
|
474
|
+
import mendraw/interop
|
|
475
|
+
import mendraw/mendix
|
|
476
|
+
import mendraw/mendix.{type JsProps}
|
|
477
|
+
import mendraw/widget
|
|
464
478
|
import redraw.{type Element}
|
|
465
479
|
import redraw/dom/attribute
|
|
466
480
|
|
|
@@ -482,8 +496,8 @@ pub fn render(props: JsProps) -> Element {
|
|
|
482
496
|
| `widget.action_prop(key, handler)` | ActionValue | 액션 콜백 (onClick 등) |
|
|
483
497
|
|
|
484
498
|
```gleam
|
|
485
|
-
import
|
|
486
|
-
import
|
|
499
|
+
import mendraw/widget
|
|
500
|
+
import mendraw/interop
|
|
487
501
|
|
|
488
502
|
let comp = widget.component("Badge button")
|
|
489
503
|
interop.component_el(comp, [
|
|
@@ -495,11 +509,11 @@ interop.component_el(comp, [
|
|
|
495
509
|
|
|
496
510
|
> Mendix에서 받은 prop (JsProps에서 꺼낸 값)은 이미 올바른 형식이므로 `attribute.attribute(key, value)`로 그대로 전달합니다.
|
|
497
511
|
|
|
498
|
-
###
|
|
512
|
+
### 7.4 Classic (Dojo) 위젯
|
|
499
513
|
|
|
500
514
|
```gleam
|
|
501
515
|
import gleam/dynamic
|
|
502
|
-
import
|
|
516
|
+
import mendraw/classic
|
|
503
517
|
|
|
504
518
|
classic.render("CameraWidget.widget.CameraWidget", [
|
|
505
519
|
#("mfToExecute", classic.to_dynamic(mf_value)),
|
|
@@ -511,14 +525,14 @@ classic.render("CameraWidget.widget.CameraWidget", [
|
|
|
511
525
|
|
|
512
526
|
---
|
|
513
527
|
|
|
514
|
-
##
|
|
528
|
+
## 8. Mendix API 레퍼런스
|
|
515
529
|
|
|
516
|
-
###
|
|
530
|
+
### 8.1 Props 접근 (`mendraw/mendix`)
|
|
517
531
|
|
|
518
532
|
`JsProps`는 opaque 타입입니다. 접근자 함수로만 읽습니다.
|
|
519
533
|
|
|
520
534
|
```gleam
|
|
521
|
-
import
|
|
535
|
+
import mendraw/mendix
|
|
522
536
|
|
|
523
537
|
// Option 반환 (undefined → None)
|
|
524
538
|
mendix.get_prop(props, "myAttr") // Option(a)
|
|
@@ -533,12 +547,12 @@ mendix.get_string_prop(props, "caption") // String
|
|
|
533
547
|
mendix.has_prop(props, "onClick") // Bool
|
|
534
548
|
```
|
|
535
549
|
|
|
536
|
-
###
|
|
550
|
+
### 8.2 ValueStatus 확인
|
|
537
551
|
|
|
538
552
|
Mendix의 모든 동적 값은 상태를 가집니다:
|
|
539
553
|
|
|
540
554
|
```gleam
|
|
541
|
-
import
|
|
555
|
+
import mendraw/mendix.{Available, Loading, Unavailable}
|
|
542
556
|
|
|
543
557
|
case mendix.get_status(some_value) {
|
|
544
558
|
Available -> // 값 사용 가능
|
|
@@ -547,12 +561,12 @@ case mendix.get_status(some_value) {
|
|
|
547
561
|
}
|
|
548
562
|
```
|
|
549
563
|
|
|
550
|
-
###
|
|
564
|
+
### 8.3 EditableValue (`mendraw/mendix/editable_value`)
|
|
551
565
|
|
|
552
566
|
텍스트, 숫자, 날짜 등 편집 가능한 Mendix 속성:
|
|
553
567
|
|
|
554
568
|
```gleam
|
|
555
|
-
import
|
|
569
|
+
import mendraw/mendix/editable_value as ev
|
|
556
570
|
|
|
557
571
|
// 읽기
|
|
558
572
|
ev.value(attr) // Option(a)
|
|
@@ -577,12 +591,12 @@ ev.set_validator(attr, Some(fn(value) {
|
|
|
577
591
|
ev.universe(attr) // Option(List(a))
|
|
578
592
|
```
|
|
579
593
|
|
|
580
|
-
###
|
|
594
|
+
### 8.4 ActionValue (`mendraw/mendix/action`)
|
|
581
595
|
|
|
582
596
|
Mendix 마이크로플로우/나노플로우 실행:
|
|
583
597
|
|
|
584
598
|
```gleam
|
|
585
|
-
import
|
|
599
|
+
import mendraw/mendix/action
|
|
586
600
|
|
|
587
601
|
action.execute(my_action) // 직접 실행
|
|
588
602
|
action.execute_if_can(my_action) // can_execute가 True일 때만
|
|
@@ -592,24 +606,24 @@ action.can_execute(my_action) // Bool
|
|
|
592
606
|
action.is_executing(my_action) // Bool
|
|
593
607
|
```
|
|
594
608
|
|
|
595
|
-
###
|
|
609
|
+
### 8.5 DynamicValue (`mendraw/mendix/dynamic_value`)
|
|
596
610
|
|
|
597
611
|
읽기 전용 표현식 속성:
|
|
598
612
|
|
|
599
613
|
```gleam
|
|
600
|
-
import
|
|
614
|
+
import mendraw/mendix/dynamic_value as dv
|
|
601
615
|
|
|
602
616
|
dv.value(expr) // Option(a)
|
|
603
617
|
dv.status(expr) // String
|
|
604
618
|
dv.is_available(expr) // Bool
|
|
605
619
|
```
|
|
606
620
|
|
|
607
|
-
###
|
|
621
|
+
### 8.6 ListValue (`mendraw/mendix/list_value`)
|
|
608
622
|
|
|
609
623
|
Mendix 데이터 소스 리스트:
|
|
610
624
|
|
|
611
625
|
```gleam
|
|
612
|
-
import
|
|
626
|
+
import mendraw/mendix/list_value as lv
|
|
613
627
|
|
|
614
628
|
// 아이템 접근
|
|
615
629
|
lv.items(list_val) // Option(List(ObjectItem))
|
|
@@ -637,12 +651,12 @@ lv.set_filter(list_val, None) // 필터 해제
|
|
|
637
651
|
lv.reload(list_val)
|
|
638
652
|
```
|
|
639
653
|
|
|
640
|
-
###
|
|
654
|
+
### 8.7 ListAttribute (`mendraw/mendix/list_attribute`)
|
|
641
655
|
|
|
642
656
|
리스트의 각 아이템에서 속성/액션/위젯 추출:
|
|
643
657
|
|
|
644
658
|
```gleam
|
|
645
|
-
import
|
|
659
|
+
import mendraw/mendix/list_attribute as la
|
|
646
660
|
|
|
647
661
|
la.get_attribute(attr, item) // EditableValue 반환
|
|
648
662
|
la.get_action(action, item) // Option(ActionValue)
|
|
@@ -657,10 +671,10 @@ la.attr_type(attr) // "String", "Integer" 등
|
|
|
657
671
|
la.attr_formatter(attr) // ValueFormatter
|
|
658
672
|
```
|
|
659
673
|
|
|
660
|
-
###
|
|
674
|
+
### 8.8 Selection (`mendraw/mendix/selection`)
|
|
661
675
|
|
|
662
676
|
```gleam
|
|
663
|
-
import
|
|
677
|
+
import mendraw/mendix/selection
|
|
664
678
|
|
|
665
679
|
// 단일 선택
|
|
666
680
|
selection.selection(single_sel) // Option(ObjectItem)
|
|
@@ -672,11 +686,11 @@ selection.selections(multi_sel) // List(ObjectItem)
|
|
|
672
686
|
selection.set_selections(multi_sel, [item1, item2])
|
|
673
687
|
```
|
|
674
688
|
|
|
675
|
-
###
|
|
689
|
+
### 8.9 Reference / ReferenceSet
|
|
676
690
|
|
|
677
691
|
```gleam
|
|
678
|
-
import
|
|
679
|
-
import
|
|
692
|
+
import mendraw/mendix/reference as ref
|
|
693
|
+
import mendraw/mendix/reference_set as ref_set
|
|
680
694
|
|
|
681
695
|
// 단일 참조
|
|
682
696
|
ref.value(my_ref) // Option(a)
|
|
@@ -689,10 +703,10 @@ ref_set.value(my_ref_set) // Option(List(a))
|
|
|
689
703
|
ref_set.set_value(my_ref_set, Some([item1, item2]))
|
|
690
704
|
```
|
|
691
705
|
|
|
692
|
-
###
|
|
706
|
+
### 8.10 Filter (`mendraw/mendix/filter`)
|
|
693
707
|
|
|
694
708
|
```gleam
|
|
695
|
-
import
|
|
709
|
+
import mendraw/mendix/filter
|
|
696
710
|
|
|
697
711
|
// 비교 연산
|
|
698
712
|
filter.equals(filter.attribute("Status"), filter.literal("Active"))
|
|
@@ -715,12 +729,12 @@ filter.literal(value) // 상수 값
|
|
|
715
729
|
filter.empty() // null 비교용
|
|
716
730
|
```
|
|
717
731
|
|
|
718
|
-
###
|
|
732
|
+
### 8.11 날짜 (`mendraw/mendix/date`)
|
|
719
733
|
|
|
720
734
|
> Gleam month는 1-based (1~12), JS는 0-based. glendix가 자동 변환합니다.
|
|
721
735
|
|
|
722
736
|
```gleam
|
|
723
|
-
import
|
|
737
|
+
import mendraw/mendix/date
|
|
724
738
|
|
|
725
739
|
date.now()
|
|
726
740
|
date.from_iso("2024-03-15T10:30:00Z")
|
|
@@ -737,52 +751,81 @@ date.to_input_value(d) // "2024-03-15" (input[type="date"]용)
|
|
|
737
751
|
date.from_input_value(s) // Option(JsDate)
|
|
738
752
|
```
|
|
739
753
|
|
|
740
|
-
###
|
|
754
|
+
### 8.12 Decimal (`mendraw/mendix/decimal`)
|
|
741
755
|
|
|
742
|
-
Big.js
|
|
756
|
+
Mendix Decimal 속성의 경계 변환 전용. Big.js 객체 ↔ Gleam 값 변환만 담당합니다.
|
|
757
|
+
산술/비교 연산이 필요하면 위젯 프로젝트에서 [dee](https://hexdocs.pm/dee/) 패키지를 추가하세요.
|
|
743
758
|
|
|
744
759
|
```gleam
|
|
745
|
-
import
|
|
760
|
+
import mendraw/mendix/decimal
|
|
761
|
+
|
|
762
|
+
// 생성 (Gleam → Mendix Big.js)
|
|
763
|
+
decimal.from_string("123.456")
|
|
764
|
+
decimal.from_int(100)
|
|
765
|
+
decimal.from_float(3.14) // 정밀도 손실 주의
|
|
766
|
+
|
|
767
|
+
// 변환 (Mendix Big.js → Gleam)
|
|
768
|
+
decimal.to_string(d) // "123.456"
|
|
769
|
+
decimal.to_float(d) // 123.456 (정밀도 손실 가능)
|
|
770
|
+
decimal.to_int(d) // 123 (소수점 이하 버림)
|
|
771
|
+
decimal.to_fixed(d, 2) // "123.46"
|
|
772
|
+
```
|
|
746
773
|
|
|
747
|
-
|
|
748
|
-
big.from_int(100)
|
|
774
|
+
**dee와 함께 사용하는 패턴:**
|
|
749
775
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
776
|
+
```gleam
|
|
777
|
+
import dee
|
|
778
|
+
import mendraw/mendix/decimal
|
|
779
|
+
|
|
780
|
+
// Mendix → dee (연산) → Mendix
|
|
781
|
+
let value = decimal.to_string(mendix_decimal) // Big.js → String
|
|
782
|
+
let result = dee.from_string(value) // String → dee
|
|
783
|
+
|> result.map(fn(d) { dee.add(d, dee.from_int(1)) })
|
|
784
|
+
|> result.map(dee.to_string) // dee → String
|
|
785
|
+
|> result.unwrap("0")
|
|
786
|
+
decimal.from_string(result) // String → Big.js
|
|
787
|
+
```
|
|
753
788
|
|
|
754
|
-
|
|
755
|
-
big.equal(a, b) // Bool
|
|
789
|
+
### 8.12.1 조건부 CSS 클래스 (`cx`)
|
|
756
790
|
|
|
757
|
-
|
|
758
|
-
|
|
791
|
+
`mendraw/mendix` 모듈의 `cx` 함수로 CSS 클래스를 조건부 조합합니다:
|
|
792
|
+
|
|
793
|
+
```gleam
|
|
794
|
+
import mendraw/mendix
|
|
795
|
+
|
|
796
|
+
mendix.cx([
|
|
797
|
+
#("widget-container", True),
|
|
798
|
+
#("active", is_active),
|
|
799
|
+
#("disabled", !is_editable),
|
|
800
|
+
])
|
|
801
|
+
// → "widget-container active" (is_active=True, is_editable=True일 때)
|
|
759
802
|
```
|
|
760
803
|
|
|
761
|
-
###
|
|
804
|
+
### 8.13 File, Icon, Formatter
|
|
762
805
|
|
|
763
806
|
```gleam
|
|
764
807
|
// FileValue / WebImage
|
|
765
|
-
import
|
|
808
|
+
import mendraw/mendix/file
|
|
766
809
|
file.uri(file_val) // String
|
|
767
810
|
file.name(file_val) // Option(String)
|
|
768
811
|
file.image_uri(img) // String
|
|
769
812
|
file.alt_text(img) // Option(String)
|
|
770
813
|
|
|
771
814
|
// WebIcon
|
|
772
|
-
import
|
|
815
|
+
import mendraw/mendix/icon
|
|
773
816
|
icon.icon_type(i) // Glyph | Image | IconFont
|
|
774
817
|
icon.icon_class(i) // String
|
|
775
818
|
icon.icon_url(i) // String
|
|
776
819
|
|
|
777
820
|
// ValueFormatter
|
|
778
|
-
import
|
|
821
|
+
import mendraw/mendix/formatter
|
|
779
822
|
formatter.format(fmt, Some(value)) // String
|
|
780
823
|
formatter.parse(fmt, "123.45") // Result(Option(a), Nil)
|
|
781
824
|
```
|
|
782
825
|
|
|
783
826
|
---
|
|
784
827
|
|
|
785
|
-
##
|
|
828
|
+
## 9. Editor Configuration (`glendix/editor_config`)
|
|
786
829
|
|
|
787
830
|
Studio Pro의 editorConfig 로직을 Gleam으로 작성합니다.
|
|
788
831
|
|
|
@@ -790,8 +833,8 @@ Studio Pro의 editorConfig 로직을 Gleam으로 작성합니다.
|
|
|
790
833
|
|
|
791
834
|
```gleam
|
|
792
835
|
import glendix/editor_config.{type Properties}
|
|
793
|
-
import
|
|
794
|
-
import
|
|
836
|
+
import mendraw/mendix
|
|
837
|
+
import mendraw/mendix.{type JsProps}
|
|
795
838
|
|
|
796
839
|
const bar_keys = "barWidth,barColor"
|
|
797
840
|
const line_keys = "lineStyle,lineCurve"
|
|
@@ -829,7 +872,7 @@ pub fn get_properties(
|
|
|
829
872
|
|
|
830
873
|
---
|
|
831
874
|
|
|
832
|
-
##
|
|
875
|
+
## 10. JS Interop Escape Hatch (`glendix/js/*`)
|
|
833
876
|
|
|
834
877
|
외부 JS 라이브러리(SpreadJS, Chart.js 등)와 직접 상호작용할 때 사용합니다. 모든 값은 `Dynamic` 타입. 가능하면 `glendix/binding`을 먼저 고려하세요.
|
|
835
878
|
|
|
@@ -841,14 +884,14 @@ array.to_list(js_arr) // JS Array → Gleam List
|
|
|
841
884
|
|
|
842
885
|
// 객체
|
|
843
886
|
import glendix/js/object
|
|
844
|
-
object.object([#("width", dynamic.
|
|
887
|
+
object.object([#("width", dynamic.int(800))])
|
|
845
888
|
object.get(obj, "key")
|
|
846
|
-
object.set(obj, "key", dynamic.
|
|
889
|
+
object.set(obj, "key", dynamic.string(val))
|
|
847
890
|
object.call_method(obj, "method", [arg1, arg2])
|
|
848
891
|
|
|
849
892
|
// JSON
|
|
850
893
|
import glendix/js/json
|
|
851
|
-
json.stringify(
|
|
894
|
+
json.stringify(data) // String
|
|
852
895
|
json.parse("{\"k\":\"v\"}") // Result(Dynamic, String)
|
|
853
896
|
|
|
854
897
|
// Promise
|
|
@@ -876,35 +919,98 @@ timer.clear_interval(id)
|
|
|
876
919
|
|
|
877
920
|
---
|
|
878
921
|
|
|
879
|
-
##
|
|
922
|
+
## 11. 빌드 & 도구
|
|
880
923
|
|
|
881
924
|
| 명령어 | 설명 |
|
|
882
925
|
|--------|------|
|
|
883
926
|
| `gleam build` | 컴파일 |
|
|
884
|
-
| `gleam run -m glendix/install` | 의존성 + 바인딩 + 위젯 .gleam 생성 |
|
|
927
|
+
| `gleam run -m glendix/install` | 의존성 + mendraw 위젯 다운로드 + 바인딩 + 위젯 .gleam 생성 |
|
|
885
928
|
| `gleam run -m glendix/dev` | 개발 서버 (HMR) |
|
|
886
929
|
| `gleam run -m glendix/build` | 프로덕션 빌드 (.mpk) |
|
|
887
930
|
| `gleam run -m glendix/start` | Mendix 테스트 프로젝트 연동 |
|
|
888
931
|
| `gleam run -m glendix/release` | 릴리즈 빌드 |
|
|
889
932
|
| `gleam run -m glendix/lint` | ESLint 검사 |
|
|
890
933
|
| `gleam run -m glendix/lint_fix` | ESLint 자동 수정 |
|
|
891
|
-
| `gleam run -m
|
|
934
|
+
| `gleam run -m mendraw/marketplace` | Marketplace 위젯 다운로드 (인터랙티브, gleam.toml 자동 기록) |
|
|
892
935
|
| `gleam run -m glendix/define` | 위젯 프로퍼티 정의 TUI 에디터 |
|
|
893
936
|
|
|
894
|
-
**PM
|
|
937
|
+
**PM 감지:** `gleam.toml`의 `[tools.glendix] pm = "pnpm"` 오버라이드 우선. 없으면 lock 파일 기반: `pnpm-lock.yaml` → pnpm / `bun.lockb`·`bun.lock` → bun / 기본값 → npm
|
|
938
|
+
|
|
939
|
+
---
|
|
940
|
+
|
|
941
|
+
## 12. 멀티 위젯 컴포넌트
|
|
942
|
+
|
|
943
|
+
하나의 `.mpk` 파일에 여러 위젯 컴포넌트를 묶어서 개발할 수 있습니다 (예: Charts.mpk).
|
|
944
|
+
|
|
945
|
+
### 12.1 설정
|
|
946
|
+
|
|
947
|
+
`package.json`에 `widgets` 맵을 추가합니다:
|
|
948
|
+
|
|
949
|
+
```json
|
|
950
|
+
{
|
|
951
|
+
"widgetName": "AreaChart",
|
|
952
|
+
"widgets": {
|
|
953
|
+
"AreaChart": "area_chart",
|
|
954
|
+
"BarChart": "bar_chart",
|
|
955
|
+
"PieChart": "pie_chart"
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
- `widgetName`: 기본 위젯 이름 (`pluggable-widgets-tools` 진입점)
|
|
961
|
+
- `widgets`: 컴포넌트명(PascalCase) → Gleam 함수명(snake_case) 맵
|
|
962
|
+
- `widgets` 미지정 시 기존 단일 위젯 동작 유지 (하위 호환)
|
|
963
|
+
|
|
964
|
+
### 12.2 Gleam 코드
|
|
965
|
+
|
|
966
|
+
메인 모듈에서 모든 위젯 함수를 export합니다:
|
|
967
|
+
|
|
968
|
+
```gleam
|
|
969
|
+
import mendraw/mendix.{type JsProps}
|
|
970
|
+
import redraw.{type Element}
|
|
971
|
+
|
|
972
|
+
pub fn area_chart(props: JsProps) -> Element { ... }
|
|
973
|
+
pub fn bar_chart(props: JsProps) -> Element { ... }
|
|
974
|
+
pub fn pie_chart(props: JsProps) -> Element { ... }
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
### 12.3 CSS 전략
|
|
978
|
+
|
|
979
|
+
위젯별 CSS 파일이 있으면 우선 사용, 없으면 공유 CSS 폴백:
|
|
980
|
+
|
|
981
|
+
1. `src/ui/{ComponentName}.css` 존재 → 해당 파일 import
|
|
982
|
+
2. 미존재 + `src/ui/{widgetName}.css` 존재 → 공유 CSS import
|
|
983
|
+
3. 둘 다 미존재 → CSS import 생략
|
|
984
|
+
|
|
985
|
+
### 12.4 Editor Config / Preview
|
|
986
|
+
|
|
987
|
+
위젯별 파일 → 공유 파일 폴백:
|
|
988
|
+
|
|
989
|
+
1. `src/{snake_fn}_editor_config.gleam` 존재 → 위젯별 editor config
|
|
990
|
+
2. 미존재 + `src/editor_config.gleam` 존재 → 공유 editor config
|
|
991
|
+
3. Preview도 동일 패턴 (`{snake_fn}_editor_preview.gleam`)
|
|
992
|
+
|
|
993
|
+
### 12.5 빌드
|
|
994
|
+
|
|
995
|
+
`gleam run -m glendix/build` 실행 시 자동으로:
|
|
996
|
+
|
|
997
|
+
- 각 위젯별 브릿지 파일 (`src/{ComponentName}.js`) 생성
|
|
998
|
+
- 기본 위젯은 `pluggable-widgets-tools`가 처리
|
|
999
|
+
- 추가 위젯은 rollup config에서 엔트리 자동 추가
|
|
1000
|
+
- 각 위젯의 XML에서 `id`를 읽어 출력 경로 결정
|
|
895
1001
|
|
|
896
1002
|
---
|
|
897
1003
|
|
|
898
|
-
##
|
|
1004
|
+
## 13. 실전 패턴
|
|
899
1005
|
|
|
900
|
-
###
|
|
1006
|
+
### 12.1 폼 입력 위젯
|
|
901
1007
|
|
|
902
1008
|
```gleam
|
|
903
1009
|
import gleam/option.{None, Some}
|
|
904
|
-
import
|
|
905
|
-
import
|
|
906
|
-
import
|
|
907
|
-
import
|
|
1010
|
+
import mendraw/mendix
|
|
1011
|
+
import mendraw/mendix.{type JsProps}
|
|
1012
|
+
import mendraw/mendix/action
|
|
1013
|
+
import mendraw/mendix/editable_value as ev
|
|
908
1014
|
import redraw.{type Element}
|
|
909
1015
|
import redraw/dom/attribute
|
|
910
1016
|
import redraw/dom/events
|
|
@@ -948,16 +1054,16 @@ pub fn text_input_widget(props: JsProps) -> Element {
|
|
|
948
1054
|
}
|
|
949
1055
|
```
|
|
950
1056
|
|
|
951
|
-
###
|
|
1057
|
+
### 12.2 데이터 테이블 위젯
|
|
952
1058
|
|
|
953
1059
|
```gleam
|
|
954
1060
|
import gleam/list
|
|
955
1061
|
import gleam/option.{None, Some}
|
|
956
|
-
import
|
|
957
|
-
import
|
|
958
|
-
import
|
|
959
|
-
import
|
|
960
|
-
import
|
|
1062
|
+
import mendraw/mendix
|
|
1063
|
+
import mendraw/mendix.{type JsProps}
|
|
1064
|
+
import mendraw/mendix/editable_value as ev
|
|
1065
|
+
import mendraw/mendix/list_attribute as la
|
|
1066
|
+
import mendraw/mendix/list_value as lv
|
|
961
1067
|
import redraw.{type Element}
|
|
962
1068
|
import redraw/dom/attribute
|
|
963
1069
|
import redraw/dom/html
|
|
@@ -982,14 +1088,14 @@ pub fn data_table(props: JsProps) -> Element {
|
|
|
982
1088
|
}
|
|
983
1089
|
```
|
|
984
1090
|
|
|
985
|
-
###
|
|
1091
|
+
### 12.3 검색 가능한 리스트
|
|
986
1092
|
|
|
987
1093
|
```gleam
|
|
988
1094
|
import gleam/option.{None, Some}
|
|
989
|
-
import
|
|
990
|
-
import
|
|
991
|
-
import
|
|
992
|
-
import
|
|
1095
|
+
import mendraw/mendix
|
|
1096
|
+
import mendraw/mendix.{type JsProps}
|
|
1097
|
+
import mendraw/mendix/filter
|
|
1098
|
+
import mendraw/mendix/list_value as lv
|
|
993
1099
|
import redraw.{type Element}
|
|
994
1100
|
import redraw/dom/attribute
|
|
995
1101
|
import redraw/dom/events
|
|
@@ -1024,23 +1130,24 @@ pub fn searchable_list(props: JsProps) -> Element {
|
|
|
1024
1130
|
|
|
1025
1131
|
---
|
|
1026
1132
|
|
|
1027
|
-
##
|
|
1133
|
+
## 14. 절대 하지 말 것
|
|
1028
1134
|
|
|
1029
1135
|
| 실수 | 올바른 방법 |
|
|
1030
1136
|
|------|------------|
|
|
1031
1137
|
| `import glendix/react` | **삭제됨.** `import redraw` 사용 |
|
|
1138
|
+
| `react`/`react-dom`을 `dependencies`에 추가 | `pluggable-widgets-tools`가 제공. 직접 넣으면 버전 충돌 |
|
|
1032
1139
|
| 조건 안에서 Hook 호출 | Hook은 항상 함수 최상위에서 호출 |
|
|
1033
1140
|
| `html.text("")`로 빈 렌더링 | `html.none()` 사용 |
|
|
1034
1141
|
| `binding.resolve(m(), "pie_chart")` | JS 원본 이름 유지: `"PieChart"` |
|
|
1035
|
-
| 외부 React 컴포넌트용 `.mjs` 직접 작성 | `bindings
|
|
1036
|
-
| `.mpk` 위젯용 `.mjs` 직접 작성 | `widgets
|
|
1142
|
+
| 외부 React 컴포넌트용 `.mjs` 직접 작성 | `gleam.toml [tools.glendix.bindings]` + `glendix/binding` 사용 |
|
|
1143
|
+
| `.mpk` 위젯용 `.mjs` 직접 작성 | `gleam.toml [tools.mendraw.widgets.*]` + `mendraw/widget` 사용 |
|
|
1037
1144
|
| `date.month()`에 0-based 값 전달 | glendix가 1↔0 자동 변환 |
|
|
1038
1145
|
| Editor config에서 Gleam List 사용 | 콤마 구분 String 사용 (Jint 호환) |
|
|
1039
1146
|
| FFI `.mjs`에 비즈니스 로직 | `.gleam`에 작성. `.mjs`는 JS 런타임 접근만 |
|
|
1040
1147
|
|
|
1041
1148
|
---
|
|
1042
1149
|
|
|
1043
|
-
##
|
|
1150
|
+
## 15. 트러블슈팅
|
|
1044
1151
|
|
|
1045
1152
|
| 문제 | 원인 | 해결 |
|
|
1046
1153
|
|------|------|------|
|
|
@@ -1048,9 +1155,8 @@ pub fn searchable_list(props: JsProps) -> Element {
|
|
|
1048
1155
|
| `Cannot read property of undefined` | 없는 prop 접근 | `get_prop` (Option) 사용, prop 이름 확인 |
|
|
1049
1156
|
| Hook 순서 에러 | 조건부 Hook 호출 | 항상 동일 순서로 호출 (React Rules) |
|
|
1050
1157
|
| 바인딩 미생성 | `binding_ffi.mjs` 스텁 상태 | `gleam run -m glendix/install` |
|
|
1051
|
-
| 위젯 바인딩 미생성 | `widget_ffi.mjs` 스텁 상태 | `widgets
|
|
1158
|
+
| 위젯 바인딩 미생성 | `widget_ffi.mjs` 스텁 상태 | `gleam.toml [tools.mendraw.widgets.*]` 설정 후 install |
|
|
1052
1159
|
| `could not be resolved` | npm 패키지 미설치 | `npm install <패키지명>` |
|
|
1053
1160
|
| `.env` PAT 오류 | marketplace 인증 실패 | [Developer Settings](https://user-settings.mendix.com/link/developersettings)에서 PAT 재발급 |
|
|
1054
|
-
| Playwright 오류 | chromium 미설치 | `npx playwright install chromium` |
|
|
1055
1161
|
|
|
1056
1162
|
---
|