imxc 0.5.4 → 0.6.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/dist/index.js CHANGED
@@ -4,11 +4,31 @@ import * as path from 'node:path';
4
4
  import { initProject, addToProject } from './init.js';
5
5
  import { compile } from './compile.js';
6
6
  import { startWatch } from './watch.js';
7
- // Handle `imxc init [dir]` subcommand
7
+ import { promptProjectName, promptTemplateName, TEMPLATES } from './templates/index.js';
8
+ // Handle `imxc templates` — list available templates
9
+ if (process.argv[2] === 'templates') {
10
+ console.log('Available templates:\n');
11
+ TEMPLATES.forEach(t => {
12
+ console.log(` ${t.name} — ${t.description}`);
13
+ });
14
+ console.log('\nUsage: imxc init <project-name> --template=<name>');
15
+ console.log('Combine: imxc init <project-name> --template=async,persistence');
16
+ process.exit(0);
17
+ }
18
+ // Handle `imxc init [dir] [--template=<name>]` subcommand
8
19
  if (process.argv[2] === 'init') {
9
- const dir = process.argv[3] ?? '.';
20
+ const { values, positionals } = parseArgs({
21
+ args: process.argv.slice(3),
22
+ allowPositionals: true,
23
+ options: { template: { type: 'string', short: 't' } },
24
+ });
25
+ let dir = positionals[0];
26
+ if (!dir) {
27
+ dir = await promptProjectName();
28
+ }
10
29
  const absDir = path.resolve(dir);
11
- initProject(absDir, path.basename(absDir));
30
+ const templateName = values.template ?? await promptTemplateName();
31
+ initProject(absDir, path.basename(absDir), templateName);
12
32
  process.exit(0);
13
33
  }
14
34
  // Handle `imxc add [dir]` subcommand
@@ -28,10 +48,13 @@ if (process.argv[2] === 'watch') {
28
48
  const { values } = parseArgs({
29
49
  args: process.argv.slice(4),
30
50
  allowPositionals: false,
31
- options: { output: { type: 'string', short: 'o' } },
51
+ options: {
52
+ output: { type: 'string', short: 'o' },
53
+ build: { type: 'string', short: 'b' },
54
+ },
32
55
  });
33
56
  const outputDir = values.output ?? '.';
34
- startWatch(path.resolve(watchDir), path.resolve(outputDir));
57
+ startWatch(path.resolve(watchDir), path.resolve(outputDir), values.build);
35
58
  }
36
59
  else {
37
60
  // Default: build command
@@ -41,13 +64,17 @@ else {
41
64
  });
42
65
  if (positionals.length === 0) {
43
66
  console.error('Usage: imxc <input.tsx ...> -o <output-dir>');
44
- console.error(' imxc init [project-dir]');
67
+ console.error(' imxc init [project-dir] [--template=<name[,name,...]>]');
68
+ console.error(' imxc templates');
45
69
  console.error(' imxc add [project-dir]');
46
- console.error(' imxc watch <dir> -o <output-dir>');
70
+ console.error(' imxc watch <dir> -o <output-dir> [--build <cmd>]');
47
71
  process.exit(1);
48
72
  }
49
73
  const outputDir = values.output ?? '.';
50
74
  const result = compile(positionals, outputDir);
75
+ if (result.warnings.length > 0) {
76
+ result.warnings.forEach(w => console.warn(w));
77
+ }
51
78
  if (!result.success) {
52
79
  result.errors.forEach(e => console.error(e));
53
80
  process.exit(1);
package/dist/init.d.ts CHANGED
@@ -1,2 +1,8 @@
1
+ import './templates/minimal.js';
2
+ import './templates/async.js';
3
+ import './templates/persistence.js';
4
+ import './templates/networking.js';
5
+ import './templates/hotreload.js';
6
+ import './templates/filedialog.js';
1
7
  export declare function addToProject(projectDir: string): void;
2
- export declare function initProject(projectDir: string, projectName?: string): void;
8
+ export declare function initProject(projectDir: string, projectName?: string, templateName?: string): void;
package/dist/init.js CHANGED
@@ -1,428 +1,13 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
- const GITIGNORE = `build/
4
- node_modules/
5
- *.ini
6
- .cache/
7
- `;
8
- const APPSTATE_H = `#pragma once
9
- #include <functional>
10
-
11
- struct AppState {
12
- int count = 0;
13
- float speed = 5.0f;
14
- std::function<void()> onIncrement;
15
- };
16
- `;
17
- const MAIN_CPP = `#include <imx/runtime.h>
18
- #include <imx/renderer.h>
19
-
20
- #include <imgui.h>
21
- #include <imgui_impl_glfw.h>
22
- #include <imgui_impl_opengl3.h>
23
- #include <GLFW/glfw3.h>
24
-
25
- #include "AppState.h"
26
-
27
- struct App {
28
- GLFWwindow* window = nullptr;
29
- ImGuiIO* io = nullptr;
30
- imx::Runtime runtime;
31
- AppState state;
32
- };
33
-
34
- static void render_frame(App& app) {
35
- glfwMakeContextCurrent(app.window);
36
- if (glfwGetWindowAttrib(app.window, GLFW_ICONIFIED) != 0) return;
37
-
38
- int fb_w = 0, fb_h = 0;
39
- glfwGetFramebufferSize(app.window, &fb_w, &fb_h);
40
- if (fb_w <= 0 || fb_h <= 0) return;
41
-
42
- ImGui_ImplOpenGL3_NewFrame();
43
- ImGui_ImplGlfw_NewFrame();
44
- ImGui::NewFrame();
45
-
46
- imx::render_root(app.runtime, app.state);
47
-
48
- ImGui::Render();
49
- glViewport(0, 0, fb_w, fb_h);
50
- glClearColor(0.12F, 0.12F, 0.15F, 1.0F);
51
- glClear(GL_COLOR_BUFFER_BIT);
52
- ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
53
-
54
- if ((app.io->ConfigFlags & ImGuiConfigFlags_ViewportsEnable) != 0) {
55
- ImGui::UpdatePlatformWindows();
56
- ImGui::RenderPlatformWindowsDefault();
57
- }
58
-
59
- glfwMakeContextCurrent(app.window);
60
- glfwSwapBuffers(app.window);
61
- }
62
-
63
- static void window_size_callback(GLFWwindow* window, int, int) {
64
- auto* app = static_cast<App*>(glfwGetWindowUserPointer(window));
65
- if (app) render_frame(*app);
66
- }
67
-
68
- int main() {
69
- if (glfwInit() == 0) return 1;
70
-
71
- const char* glsl_version = "#version 150";
72
- glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
73
- glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
74
- glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
75
- #ifdef __APPLE__
76
- glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
77
- #endif
78
-
79
- GLFWwindow* window = glfwCreateWindow(800, 600, "APP_NAME", nullptr, nullptr);
80
- if (!window) { glfwTerminate(); return 1; }
81
- glfwMakeContextCurrent(window);
82
- glfwSwapInterval(1);
83
-
84
- IMGUI_CHECKVERSION();
85
- ImGui::CreateContext();
86
- ImGuiIO& io = ImGui::GetIO();
87
- io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
88
- io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
89
-
90
- ImGui::StyleColorsDark();
91
- ImGuiStyle& style = ImGui::GetStyle();
92
- if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
93
- style.WindowRounding = 0.0F;
94
- style.Colors[ImGuiCol_WindowBg].w = 1.0F;
95
- }
96
-
97
- ImGui_ImplGlfw_InitForOpenGL(window, true);
98
- ImGui_ImplOpenGL3_Init(glsl_version);
99
-
100
- App app;
101
- app.window = window;
102
- app.io = &io;
103
- glfwSetWindowUserPointer(window, &app);
104
- glfwSetWindowSizeCallback(window, window_size_callback);
105
- app.state.onIncrement = [&]() { app.state.count++; };
106
-
107
- while (glfwWindowShouldClose(window) == 0) {
108
- if (app.runtime.needs_frame()) {
109
- glfwPollEvents();
110
- } else {
111
- glfwWaitEventsTimeout(0.1);
112
- }
113
- render_frame(app);
114
- app.runtime.frame_rendered(ImGui::IsAnyItemActive());
115
- }
116
-
117
- ImGui_ImplOpenGL3_Shutdown();
118
- ImGui_ImplGlfw_Shutdown();
119
- ImGui::DestroyContext();
120
- glfwDestroyWindow(window);
121
- glfwTerminate();
122
- return 0;
123
- }
124
-
125
- #ifdef _WIN32
126
- #include <windows.h>
127
- int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { return main(); }
128
- #endif
129
- `;
130
- const APP_TSX = `export default function App(props: AppState) {
131
- const [showAbout, setShowAbout] = useState(false);
132
-
133
- return (
134
- <DockSpace>
135
- <Window title="Controls">
136
- <Column gap={8}>
137
- <Text>Count: {props.count}</Text>
138
- <Button title="Increment" onPress={props.onIncrement} />
139
- <SliderFloat label="Speed" value={props.speed} min={0} max={100} />
140
- <Separator />
141
- <Button title="About" onPress={() => setShowAbout(!showAbout)} />
142
- </Column>
143
- </Window>
144
- {showAbout && <Window title="About">
145
- <Text>Built with IMX</Text>
146
- <Button title="Close" onPress={() => setShowAbout(false)} />
147
- </Window>}
148
- </DockSpace>
149
- );
150
- }
151
- `;
152
- const IMX_DTS = `// imx.d.ts — Type definitions for IMX components
153
-
154
- interface Style {
155
- padding?: number;
156
- paddingHorizontal?: number;
157
- paddingVertical?: number;
158
- gap?: number;
159
- width?: number;
160
- height?: number;
161
- minWidth?: number;
162
- minHeight?: number;
163
- backgroundColor?: [number, number, number, number];
164
- textColor?: [number, number, number, number];
165
- fontSize?: number;
166
- }
167
-
168
- declare function useState<T>(initial: T): [T, (value: T) => void];
169
-
170
- interface AppState {
171
- count: number;
172
- speed: number;
173
- onIncrement: () => void;
174
- }
175
-
176
- interface WindowProps { title: string; open?: boolean; onClose?: () => void; noTitleBar?: boolean; noResize?: boolean; noMove?: boolean; noCollapse?: boolean; noDocking?: boolean; noScrollbar?: boolean; style?: Style; children?: any; }
177
- interface ViewProps { style?: Style; children?: any; }
178
- interface RowProps { gap?: number; style?: Style; children?: any; }
179
- interface ColumnProps { gap?: number; style?: Style; children?: any; }
180
- interface TextProps { style?: Style; children?: any; }
181
- interface ButtonProps { title: string; onPress: () => void; disabled?: boolean; style?: Style; }
182
- interface TextInputProps { value: string; onChange?: (v: string) => void; label?: string; placeholder?: string; style?: Style; }
183
- interface CheckboxProps { value: boolean; onChange?: (v: boolean) => void; label?: string; style?: Style; }
184
- interface SeparatorProps {}
185
- interface PopupProps { id: string; style?: Style; children?: any; }
186
- interface DockSpaceProps { style?: Style; children?: any; }
187
- interface DockLayoutProps { children?: any; }
188
- interface DockSplitProps { direction: "horizontal" | "vertical"; size: number; children?: any; }
189
- interface DockPanelProps { children?: any; }
190
- interface ThemeProps {
191
- preset: string;
192
- accentColor?: [number, number, number, number];
193
- backgroundColor?: [number, number, number, number];
194
- textColor?: [number, number, number, number];
195
- borderColor?: [number, number, number, number];
196
- surfaceColor?: [number, number, number, number];
197
- rounding?: number;
198
- borderSize?: number;
199
- spacing?: number;
200
- children?: any;
201
- }
202
- interface MenuBarProps { children?: any; }
203
- interface MenuProps { label: string; children?: any; }
204
- interface MenuItemProps { label: string; onPress?: () => void; shortcut?: string; }
205
- interface TableProps { columns: string[]; scrollY?: boolean; noBorders?: boolean; noRowBg?: boolean; style?: Style; children?: any; }
206
- interface TableRowProps { key?: number | string; children?: any; }
207
- interface TabBarProps { style?: Style; children?: any; }
208
- interface TabItemProps { label: string; children?: any; }
209
- interface TreeNodeProps { label: string; children?: any; }
210
- interface CollapsingHeaderProps { label: string; children?: any; }
211
- interface SliderFloatProps { label: string; value: number; onChange?: (v: number) => void; min: number; max: number; style?: Style; }
212
- interface SliderIntProps { label: string; value: number; onChange?: (v: number) => void; min: number; max: number; style?: Style; }
213
- interface DragFloatProps { label: string; value: number; onChange?: (v: number) => void; speed?: number; style?: Style; }
214
- interface DragIntProps { label: string; value: number; onChange?: (v: number) => void; speed?: number; style?: Style; }
215
- interface ComboProps { label: string; value: number; onChange?: (v: number) => void; items: string[]; style?: Style; }
216
- interface InputIntProps { label: string; value: number; onChange?: (v: number) => void; style?: Style; }
217
- interface InputFloatProps { label: string; value: number; onChange?: (v: number) => void; style?: Style; }
218
- interface ColorEditProps { label: string; value: number[]; onChange: (v: number[]) => void; style?: Style; }
219
- interface ListBoxProps { label: string; value: number; onChange?: (v: number) => void; items: string[]; style?: Style; }
220
- interface ProgressBarProps { value: number; overlay?: string; style?: Style; }
221
- interface TooltipProps { text: string; }
222
- interface BulletTextProps { style?: Style; children?: any; }
223
- interface LabelTextProps { label: string; value: string; }
224
- interface SelectableProps { label: string; selected?: boolean; onSelect?: () => void; style?: Style; }
225
- interface RadioProps { label: string; value: number; index: number; onChange?: (v: number) => void; style?: Style; }
226
- interface InputTextMultilineProps { label: string; value: string; style?: Style; }
227
- interface ColorPickerProps { label: string; value: number[]; style?: Style; }
228
- interface PlotLinesProps { label: string; values: number[]; overlay?: string; style?: Style; }
229
- interface PlotHistogramProps { label: string; values: number[]; overlay?: string; style?: Style; }
230
- interface ModalProps { title: string; open?: boolean; onClose?: () => void; style?: Style; children?: any; }
231
- interface ImageProps { src: string; embed?: boolean; width?: number; height?: number; }
232
- interface GroupProps { style?: Style; children?: any; }
233
- interface IDProps { scope: string | number; children?: any; }
234
- interface DragDropSourceProps { type: string; payload: number | string; children?: any; }
235
- interface DragDropTargetProps { type: string; onDrop: (payload: any) => void; children?: any; }
236
- interface CanvasProps { width: number; height: number; style?: Style; children?: any; }
237
- interface DrawLineProps { p1: [number, number]; p2: [number, number]; color: [number, number, number, number]; thickness?: number; }
238
- interface DrawRectProps { min: [number, number]; max: [number, number]; color: [number, number, number, number]; filled?: boolean; thickness?: number; rounding?: number; }
239
- interface DrawCircleProps { center: [number, number]; radius: number; color: [number, number, number, number]; filled?: boolean; thickness?: number; }
240
- interface DrawTextProps { pos: [number, number]; text: string; color: [number, number, number, number]; }
241
- interface StyleColorProps {
242
- text?: [number, number, number, number];
243
- textDisabled?: [number, number, number, number];
244
- windowBg?: [number, number, number, number];
245
- frameBg?: [number, number, number, number];
246
- frameBgHovered?: [number, number, number, number];
247
- frameBgActive?: [number, number, number, number];
248
- titleBg?: [number, number, number, number];
249
- titleBgActive?: [number, number, number, number];
250
- button?: [number, number, number, number];
251
- buttonHovered?: [number, number, number, number];
252
- buttonActive?: [number, number, number, number];
253
- header?: [number, number, number, number];
254
- headerHovered?: [number, number, number, number];
255
- headerActive?: [number, number, number, number];
256
- separator?: [number, number, number, number];
257
- checkMark?: [number, number, number, number];
258
- sliderGrab?: [number, number, number, number];
259
- border?: [number, number, number, number];
260
- popupBg?: [number, number, number, number];
261
- tab?: [number, number, number, number];
262
- children?: any;
263
- }
264
-
265
- declare function Window(props: WindowProps): any;
266
- declare function View(props: ViewProps): any;
267
- declare function Row(props: RowProps): any;
268
- declare function Column(props: ColumnProps): any;
269
- declare function Text(props: TextProps): any;
270
- declare function Button(props: ButtonProps): any;
271
- declare function TextInput(props: TextInputProps): any;
272
- declare function Checkbox(props: CheckboxProps): any;
273
- declare function Separator(props: SeparatorProps): any;
274
- declare function Popup(props: PopupProps): any;
275
- declare function DockSpace(props: DockSpaceProps): any;
276
- declare function DockLayout(props: DockLayoutProps): any;
277
- declare function DockSplit(props: DockSplitProps): any;
278
- declare function DockPanel(props: DockPanelProps): any;
279
- declare function Theme(props: ThemeProps): any;
280
- declare function MenuBar(props: MenuBarProps): any;
281
- declare function Menu(props: MenuProps): any;
282
- declare function MenuItem(props: MenuItemProps): any;
283
- declare function Table(props: TableProps): any;
284
- declare function TableRow(props: TableRowProps): any;
285
- declare function TabBar(props: TabBarProps): any;
286
- declare function TabItem(props: TabItemProps): any;
287
- declare function TreeNode(props: TreeNodeProps): any;
288
- declare function CollapsingHeader(props: CollapsingHeaderProps): any;
289
- declare function SliderFloat(props: SliderFloatProps): any;
290
- declare function SliderInt(props: SliderIntProps): any;
291
- declare function DragFloat(props: DragFloatProps): any;
292
- declare function DragInt(props: DragIntProps): any;
293
- declare function Combo(props: ComboProps): any;
294
- declare function InputInt(props: InputIntProps): any;
295
- declare function InputFloat(props: InputFloatProps): any;
296
- declare function ColorEdit(props: ColorEditProps): any;
297
- declare function ListBox(props: ListBoxProps): any;
298
- declare function ProgressBar(props: ProgressBarProps): any;
299
- declare function Tooltip(props: TooltipProps): any;
300
- declare function BulletText(props: BulletTextProps): any;
301
- declare function LabelText(props: LabelTextProps): any;
302
- declare function Selectable(props: SelectableProps): any;
303
- declare function Radio(props: RadioProps): any;
304
- declare function InputTextMultiline(props: InputTextMultilineProps): any;
305
- declare function ColorPicker(props: ColorPickerProps): any;
306
- declare function PlotLines(props: PlotLinesProps): any;
307
- declare function PlotHistogram(props: PlotHistogramProps): any;
308
- declare function Modal(props: ModalProps): any;
309
- declare function Image(props: ImageProps): any;
310
- declare function Group(props: GroupProps): any;
311
- declare function ID(props: IDProps): any;
312
- declare function StyleColor(props: StyleColorProps): any;
313
- declare function Canvas(props: CanvasProps): any;
314
- declare function DrawLine(props: DrawLineProps): any;
315
- declare function DrawRect(props: DrawRectProps): any;
316
- declare function DrawCircle(props: DrawCircleProps): any;
317
- declare function DrawText(props: DrawTextProps): any;
318
-
319
- interface StyleVarProps {
320
- alpha?: number;
321
- windowPadding?: [number, number];
322
- windowRounding?: number;
323
- framePadding?: [number, number];
324
- frameRounding?: number;
325
- frameBorderSize?: number;
326
- itemSpacing?: [number, number];
327
- itemInnerSpacing?: [number, number];
328
- indentSpacing?: number;
329
- cellPadding?: [number, number];
330
- tabRounding?: number;
331
- children?: any;
332
- }
333
- declare function StyleVar(props: StyleVarProps): any;
334
- declare function DragDropSource(props: DragDropSourceProps): any;
335
- declare function DragDropTarget(props: DragDropTargetProps): any;
336
- interface DisabledProps { disabled?: boolean; children?: any; }
337
- interface ChildProps { id: string; width?: number; height?: number; border?: boolean; style?: Style; children?: any; }
338
- declare function Disabled(props: DisabledProps): any;
339
- declare function Child(props: ChildProps): any;
340
-
341
- declare function resetLayout(): void;
342
-
343
- // --- Custom native widgets ---
344
- // Declare your C++ registered widgets here for type checking:
345
- //
346
- // interface KnobProps {
347
- // value: number;
348
- // onChange: (value: number) => void;
349
- // min: number;
350
- // max: number;
351
- // width?: number;
352
- // height?: number;
353
- // }
354
- // declare function Knob(props: KnobProps): any;
355
-
356
- declare module "imx/jsx-runtime" {
357
- export namespace JSX {
358
- type Element = any;
359
- interface IntrinsicElements { [tag: string]: any; }
360
- interface ElementChildrenAttribute { children: {}; }
361
- }
362
- export function jsx(type: any, props: any, key?: any): any;
363
- export function jsxs(type: any, props: any, key?: any): any;
364
- export function Fragment(props: any): any;
365
- }
366
- `;
367
- const TSCONFIG = `{
368
- "compilerOptions": {
369
- "target": "ES2022",
370
- "lib": ["ES2022"],
371
- "module": "ES2022",
372
- "moduleResolution": "bundler",
373
- "jsx": "react-jsx",
374
- "jsxImportSource": "imx",
375
- "strict": false,
376
- "noEmit": true,
377
- "skipLibCheck": true
378
- },
379
- "include": ["src/**/*.tsx", "src/imx.d.ts"]
380
- }
381
- `;
382
- function cmakeTemplate(projectName, repoUrl) {
383
- return `cmake_minimum_required(VERSION 3.25)
384
- project(${projectName} LANGUAGES CXX)
385
-
386
- set(CMAKE_CXX_STANDARD 20)
387
- set(CMAKE_CXX_STANDARD_REQUIRED ON)
388
-
389
- include(FetchContent)
390
- set(FETCHCONTENT_QUIET OFF)
391
-
392
- FetchContent_Declare(
393
- imx
394
- GIT_REPOSITORY ${repoUrl}
395
- GIT_TAG main
396
- GIT_SHALLOW TRUE
397
- GIT_PROGRESS TRUE
398
- )
399
- message(STATUS "Fetching IMX (includes ImGui + GLFW)...")
400
- FetchContent_MakeAvailable(imx)
401
-
402
- include(ImxCompile)
403
-
404
- imx_compile_tsx(GENERATED
405
- SOURCES src/App.tsx
406
- OUTPUT_DIR \${CMAKE_BINARY_DIR}/generated
407
- )
408
-
409
- add_executable(${projectName}
410
- src/main.cpp
411
- \${GENERATED}
412
- )
413
- set_target_properties(${projectName} PROPERTIES WIN32_EXECUTABLE $<CONFIG:Release>)
414
- target_link_libraries(${projectName} PRIVATE imx::renderer)
415
- target_include_directories(${projectName} PRIVATE \${CMAKE_BINARY_DIR}/generated \${CMAKE_CURRENT_SOURCE_DIR}/src)
416
-
417
- # Copy public/ assets to output directory
418
- add_custom_command(TARGET ${projectName} POST_BUILD
419
- COMMAND \${CMAKE_COMMAND} -E copy_directory
420
- \${CMAKE_CURRENT_SOURCE_DIR}/public
421
- $<TARGET_FILE_DIR:${projectName}>
422
- COMMENT "Copying public/ assets"
423
- )
424
- `;
425
- }
3
+ import { TEMPLATES, APP_TSX, buildImxDts, TSCONFIG } from './templates/index.js';
4
+ import { generateCombined, FEATURES } from './templates/custom.js';
5
+ import './templates/minimal.js';
6
+ import './templates/async.js';
7
+ import './templates/persistence.js';
8
+ import './templates/networking.js';
9
+ import './templates/hotreload.js';
10
+ import './templates/filedialog.js';
426
11
  export function addToProject(projectDir) {
427
12
  const srcDir = path.join(projectDir, 'src');
428
13
  if (fs.existsSync(path.join(srcDir, 'App.tsx'))) {
@@ -434,7 +19,11 @@ export function addToProject(projectDir) {
434
19
  fs.mkdirSync(publicDir, { recursive: true });
435
20
  // Write TSX source files only — no CMakeLists.txt or main.cpp
436
21
  fs.writeFileSync(path.join(srcDir, 'App.tsx'), APP_TSX);
437
- fs.writeFileSync(path.join(srcDir, 'imx.d.ts'), IMX_DTS);
22
+ fs.writeFileSync(path.join(srcDir, 'imx.d.ts'), buildImxDts(`interface AppState {
23
+ count: number;
24
+ speed: number;
25
+ onIncrement: () => void;
26
+ }`));
438
27
  fs.writeFileSync(path.join(projectDir, 'tsconfig.json'), TSCONFIG);
439
28
  console.log(`imxc: added IMX sources to project`);
440
29
  console.log('');
@@ -450,7 +39,7 @@ export function addToProject(projectDir) {
450
39
  console.log(' include(FetchContent)');
451
40
  console.log(' FetchContent_Declare(imx');
452
41
  console.log(' GIT_REPOSITORY https://github.com/bgocumlu/imx.git');
453
- console.log(' GIT_TAG main');
42
+ console.log(' GIT_TAG v0.6.1');
454
43
  console.log(' )');
455
44
  console.log(' FetchContent_MakeAvailable(imx)');
456
45
  console.log(' include(ImxCompile)');
@@ -478,37 +67,36 @@ export function addToProject(projectDir) {
478
67
  console.log(' // In your frame loop, between NewFrame() and Render():');
479
68
  console.log(' imx::render_root(runtime);');
480
69
  }
481
- export function initProject(projectDir, projectName) {
70
+ export function initProject(projectDir, projectName, templateName) {
482
71
  const name = projectName ?? path.basename(projectDir);
483
- const srcDir = path.join(projectDir, 'src');
484
- if (fs.existsSync(path.join(srcDir, 'App.tsx'))) {
485
- console.error(`Error: ${srcDir}/App.tsx already exists. Aborting.`);
72
+ const tpl = templateName ?? 'minimal';
73
+ // Check if directory already has content
74
+ if (fs.existsSync(projectDir)) {
75
+ const entries = fs.readdirSync(projectDir).filter(e => e !== '.git');
76
+ if (entries.length > 0) {
77
+ console.error('Error: directory is not empty.');
78
+ process.exit(1);
79
+ }
80
+ }
81
+ // Check for comma-separated (combined template)
82
+ const parts = tpl.split(',').map(s => s.trim());
83
+ if (parts.length > 1) {
84
+ // Validate all parts are known features
85
+ const featureNames = FEATURES.map(f => f.name);
86
+ for (const p of parts) {
87
+ if (!featureNames.includes(p)) {
88
+ console.error(`Error: unknown feature "${p}". Combinable features: ${featureNames.join(', ')}`);
89
+ process.exit(1);
90
+ }
91
+ }
92
+ generateCombined(parts, projectDir, name);
93
+ return;
94
+ }
95
+ // Single template — use existing generator
96
+ const entry = TEMPLATES.find(t => t.name === tpl);
97
+ if (!entry) {
98
+ console.error(`Error: unknown template "${tpl}". Available: ${TEMPLATES.map(t => t.name).join(', ')}`);
486
99
  process.exit(1);
487
100
  }
488
- fs.mkdirSync(srcDir, { recursive: true });
489
- const publicDir = path.join(projectDir, 'public');
490
- fs.mkdirSync(publicDir, { recursive: true });
491
- // Write files
492
- fs.writeFileSync(path.join(srcDir, 'main.cpp'), MAIN_CPP.replace('APP_NAME', name));
493
- fs.writeFileSync(path.join(srcDir, 'AppState.h'), APPSTATE_H);
494
- fs.writeFileSync(path.join(srcDir, 'App.tsx'), APP_TSX);
495
- fs.writeFileSync(path.join(srcDir, 'imx.d.ts'), IMX_DTS);
496
- fs.writeFileSync(path.join(projectDir, 'tsconfig.json'), TSCONFIG);
497
- fs.writeFileSync(path.join(projectDir, 'CMakeLists.txt'), cmakeTemplate(name, 'https://github.com/bgocumlu/imx.git'));
498
- fs.writeFileSync(path.join(projectDir, '.gitignore'), GITIGNORE);
499
- console.log(`imxc: initialized project "${name}"`);
500
- console.log('');
501
- console.log(' Created:');
502
- console.log(` src/main.cpp — app shell (GLFW + OpenGL + ImGui)`);
503
- console.log(` src/AppState.h — C++ state struct (bound to TSX props)`);
504
- console.log(` src/App.tsx — your root component`);
505
- console.log(` src/imx.d.ts — type definitions for IDE support`);
506
- console.log(` tsconfig.json — TypeScript config`);
507
- console.log(` CMakeLists.txt — build config with FetchContent`);
508
- console.log(` .gitignore — ignores build/, node_modules/, *.ini`);
509
- console.log(` public/ — static assets (copied to exe directory)`);
510
- console.log('');
511
- console.log(' Next steps:');
512
- console.log(` cmake -B build`);
513
- console.log(` cmake --build build`);
101
+ entry.generate(projectDir, name);
514
102
  }