plusui-native 0.2.5 → 0.2.8

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "plusui-native",
3
- "version": "0.2.5",
4
- "description": "PlusUI CLI - Build C++ desktop apps with web tech",
3
+ "version": "0.2.8",
4
+ "description": "PlusUI CLI - Build C++ desktop apps modern UI ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
7
7
  "bin": {
@@ -27,11 +27,11 @@
27
27
  "semver": "^7.6.0",
28
28
  "which": "^4.0.0",
29
29
  "execa": "^8.0.1",
30
- "plusui-native-builder": "^0.1.5",
31
- "plusui-native-bindgen": "^0.1.5"
30
+ "plusui-native-builder": "^0.1.8",
31
+ "plusui-native-bindgen": "^0.1.8"
32
32
  },
33
33
  "peerDependencies": {
34
- "plusui-native-bindgen": "^0.1.5"
34
+ "plusui-native-bindgen": "^0.1.8"
35
35
  },
36
36
  "publishConfig": {
37
37
  "access": "public"
package/src/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { mkdir, readFile, stat, rm, readdir, writeFile } from 'fs/promises';
4
- import { existsSync, watch, statSync } from 'fs';
3
+ import { mkdir, readFile, stat, rm, readdir, writeFile, copyFile } from 'fs/promises';
4
+ import { existsSync, watch, statSync, mkdirSync } from 'fs';
5
5
  import { exec, spawn, execSync } from 'child_process';
6
6
  import { join, dirname, basename, resolve } from 'path';
7
7
  import { fileURLToPath } from 'url';
@@ -199,9 +199,97 @@ function getAppBindgenPaths() {
199
199
  return {
200
200
  featuresDir: join(process.cwd(), 'src', 'features'),
201
201
  outputDir: join(process.cwd(), 'src', 'Bindings_Generated'),
202
+ frontendOutputDir: join(process.cwd(), 'frontend', 'src', 'Bindings_Generated'),
202
203
  };
203
204
  }
204
205
 
206
+ function findLikelyProjectDirs(baseDir) {
207
+ try {
208
+ const entries = execSync('npm pkg get name', { cwd: baseDir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
209
+ if (entries) {
210
+ // noop; just to ensure cwd is a Node project when possible
211
+ }
212
+ } catch {}
213
+
214
+ const candidates = [];
215
+ try {
216
+ const dirs = execSync(process.platform === 'win32' ? 'dir /b /ad' : 'ls -1 -d */', {
217
+ cwd: baseDir,
218
+ encoding: 'utf8',
219
+ stdio: ['ignore', 'pipe', 'ignore'],
220
+ shell: true,
221
+ })
222
+ .split(/\r?\n/)
223
+ .map(s => s.trim().replace(/[\\/]$/, ''))
224
+ .filter(Boolean);
225
+
226
+ for (const dirName of dirs) {
227
+ const fullDir = join(baseDir, dirName);
228
+ if (existsSync(join(fullDir, 'CMakeLists.txt')) && existsSync(join(fullDir, 'package.json'))) {
229
+ candidates.push(dirName);
230
+ }
231
+ }
232
+ } catch {}
233
+
234
+ return candidates;
235
+ }
236
+
237
+ function ensureProjectRoot(commandName) {
238
+ const hasCMake = existsSync(join(process.cwd(), 'CMakeLists.txt'));
239
+ const hasPackage = existsSync(join(process.cwd(), 'package.json'));
240
+
241
+ if (hasCMake && hasPackage) {
242
+ return;
243
+ }
244
+
245
+ const likelyDirs = findLikelyProjectDirs(process.cwd());
246
+ let hint = '';
247
+
248
+ if (likelyDirs.length === 1) {
249
+ hint = `\n\nHint: you may be in a parent folder. Try:\n cd ${likelyDirs[0]}\n plusui ${commandName}`;
250
+ } else if (likelyDirs.length > 1) {
251
+ hint = `\n\nHint: run this command from your app folder (one containing CMakeLists.txt and package.json).`;
252
+ }
253
+
254
+ error(`This command must be run from a PlusUI project root (missing CMakeLists.txt and/or package.json in ${process.cwd()}).${hint}`);
255
+ }
256
+
257
+ function ensureBuildLayout() {
258
+ const buildRoot = join(process.cwd(), 'build');
259
+ for (const platform of Object.values(PLATFORMS)) {
260
+ mkdirSync(join(buildRoot, platform.folder), { recursive: true });
261
+ }
262
+ }
263
+
264
+ function resolveBindgenScriptPath() {
265
+ const candidates = [
266
+ resolve(__dirname, '../../plusui-bindgen/src/index.js'),
267
+ resolve(__dirname, '../../plusui-native-bindgen/src/index.js'),
268
+ resolve(__dirname, '../../../plusui-native-bindgen/src/index.js'),
269
+ resolve(process.cwd(), 'node_modules', 'plusui-native-bindgen', 'src', 'index.js'),
270
+ ];
271
+
272
+ for (const candidate of candidates) {
273
+ if (existsSync(candidate)) {
274
+ return candidate;
275
+ }
276
+ }
277
+
278
+ return null;
279
+ }
280
+
281
+ async function syncGeneratedTsBindings(backendOutputDir, frontendOutputDir) {
282
+ const generatedTsPath = join(backendOutputDir, 'bindings.gen.ts');
283
+ if (!existsSync(generatedTsPath)) {
284
+ return;
285
+ }
286
+
287
+ await mkdir(frontendOutputDir, { recursive: true });
288
+ const frontendTsPath = join(frontendOutputDir, 'bindings.gen.ts');
289
+ await copyFile(generatedTsPath, frontendTsPath);
290
+ log(`Synced TS bindings: ${frontendTsPath}`, 'dim');
291
+ }
292
+
205
293
  function promptTemplateSelection() {
206
294
  return new Promise((resolve) => {
207
295
  const rl = createInterface({
@@ -238,6 +326,7 @@ async function createProject(name, options = {}) {
238
326
  // ============================================================
239
327
 
240
328
  function buildFrontend() {
329
+ ensureProjectRoot('build:frontend');
241
330
  logSection('Building Frontend');
242
331
 
243
332
  if (existsSync('frontend')) {
@@ -250,6 +339,7 @@ function buildFrontend() {
250
339
  }
251
340
 
252
341
  function buildBackend(platform = null, devMode = false) {
342
+ ensureProjectRoot(devMode ? 'dev:backend' : 'build:backend');
253
343
  const targetPlatform = platform || process.platform;
254
344
  const platformConfig = PLATFORMS[targetPlatform] || PLATFORMS[Object.keys(PLATFORMS).find(k => PLATFORMS[k].folder.toLowerCase() === targetPlatform?.toLowerCase())];
255
345
 
@@ -259,9 +349,10 @@ function buildBackend(platform = null, devMode = false) {
259
349
 
260
350
  logSection(`Building Backend (${platformConfig.name})`);
261
351
 
262
- const projectName = getProjectName();
263
352
  const buildDir = `build/${platformConfig.folder}`;
264
353
 
354
+ ensureBuildLayout();
355
+
265
356
  // Create build directory
266
357
  if (!existsSync(buildDir)) {
267
358
  execSync(`mkdir -p "${buildDir}"`, { stdio: 'ignore', shell: true });
@@ -291,6 +382,7 @@ function buildBackend(platform = null, devMode = false) {
291
382
  }
292
383
 
293
384
  async function generateIcons(inputPath = null) {
385
+ ensureProjectRoot('icons');
294
386
  logSection('Generating Platform Icons');
295
387
 
296
388
  const { IconGenerator } = await import('./assets/icon-generator.js');
@@ -309,6 +401,7 @@ async function generateIcons(inputPath = null) {
309
401
  }
310
402
 
311
403
  async function embedResources(platform = null) {
404
+ ensureProjectRoot('embed');
312
405
  const targetPlatform = platform || process.platform;
313
406
  logSection(`Embedding Resources (${targetPlatform})`);
314
407
 
@@ -331,6 +424,7 @@ async function embedResources(platform = null) {
331
424
  }
332
425
 
333
426
  async function embedAssets() {
427
+ ensureProjectRoot('build');
334
428
  const assetsDir = join(process.cwd(), 'assets');
335
429
  if (!existsSync(assetsDir)) {
336
430
  try {
@@ -379,6 +473,7 @@ async function embedAssets() {
379
473
 
380
474
 
381
475
  async function build(production = true) {
476
+ ensureProjectRoot('build');
382
477
  logSection('Building PlusUI Application');
383
478
 
384
479
  // Embed assets
@@ -397,8 +492,11 @@ async function build(production = true) {
397
492
  }
398
493
 
399
494
  function buildAll() {
495
+ ensureProjectRoot('build:all');
400
496
  logSection('Building for All Platforms');
401
497
 
498
+ ensureBuildLayout();
499
+
402
500
  buildFrontend();
403
501
 
404
502
  const supportedPlatforms = ['win32', 'darwin', 'linux'];
@@ -416,6 +514,8 @@ function buildAll() {
416
514
  log(' build/Windows/', 'cyan');
417
515
  log(' build/MacOS/', 'cyan');
418
516
  log(' build/Linux/', 'cyan');
517
+ log(' build/Android/', 'cyan');
518
+ log(' build/iOS/', 'cyan');
419
519
  }
420
520
 
421
521
  function buildPlatform(platform) {
@@ -432,6 +532,7 @@ let viteServer = null;
432
532
  let cppProcess = null;
433
533
 
434
534
  async function startViteServer() {
535
+ ensureProjectRoot('dev:frontend');
435
536
  log('Starting Vite dev server...', 'blue');
436
537
 
437
538
  viteServer = await createViteServer({
@@ -449,12 +550,14 @@ async function startViteServer() {
449
550
  }
450
551
 
451
552
  async function startBackend() {
553
+ ensureProjectRoot('dev');
452
554
  logSection('Building C++ Backend (Dev Mode)');
453
555
 
454
556
  const projectName = getProjectName();
455
557
  killProcessByName(projectName);
456
558
 
457
- const buildDir = 'build/dev';
559
+ const platformFolder = PLATFORMS[process.platform]?.folder || 'Windows';
560
+ const buildDir = join('build', platformFolder, 'dev');
458
561
 
459
562
  // Configure with dev mode if not configured
460
563
  if (!existsSync(join(buildDir, 'CMakeCache.txt'))) {
@@ -555,6 +658,7 @@ function killProcessByName(name) {
555
658
  }
556
659
 
557
660
  async function dev() {
661
+ ensureProjectRoot('dev');
558
662
  logSection('PlusUI Development Mode');
559
663
 
560
664
  const toolCheck = checkTools();
@@ -600,6 +704,7 @@ async function dev() {
600
704
  }
601
705
 
602
706
  async function devFrontend() {
707
+ ensureProjectRoot('dev:frontend');
603
708
  logSection('Frontend Development Mode');
604
709
 
605
710
  // specific port cleaning
@@ -622,12 +727,14 @@ async function devFrontend() {
622
727
  }
623
728
 
624
729
  function devBackend() {
730
+ ensureProjectRoot('dev:backend');
625
731
  logSection('Backend Development Mode');
626
732
 
627
733
  const projectName = getProjectName();
628
734
  killProcessByName(projectName);
629
735
 
630
- const buildDir = 'build/dev';
736
+ const platformFolder = PLATFORMS[process.platform]?.folder || 'Windows';
737
+ const buildDir = join('build', platformFolder, 'dev');
631
738
 
632
739
  if (!existsSync(join(buildDir, 'CMakeCache.txt'))) {
633
740
  log('Configuring CMake...', 'blue');
@@ -698,9 +805,10 @@ function devBackend() {
698
805
  // ============================================================
699
806
 
700
807
  function run() {
808
+ ensureProjectRoot('run');
701
809
  const projectName = getProjectName();
702
810
  const platform = PLATFORMS[process.platform];
703
- const buildDir = `build/${platform?.folder || 'dev'}`;
811
+ const buildDir = `build/${platform?.folder || 'Windows'}`;
704
812
 
705
813
  let exePath;
706
814
  if (process.platform === 'win32') {
@@ -730,6 +838,7 @@ function run() {
730
838
  // ============================================================
731
839
 
732
840
  async function clean() {
841
+ ensureProjectRoot('clean');
733
842
  logSection('Cleaning Build Artifacts');
734
843
 
735
844
  const dirs = ['build', 'frontend/dist'];
@@ -749,18 +858,14 @@ async function clean() {
749
858
  // ============================================================
750
859
 
751
860
  async function runBindgen(providedArgs = null, options = {}) {
861
+ ensureProjectRoot('bindgen');
752
862
  logSection('Running Binding Generator');
753
863
 
754
864
  const { skipIfNoInput = false, source = 'manual' } = options;
755
865
 
756
- // Try to find the bindgen script
757
- let scriptPath = resolve(__dirname, '../../plusui-bindgen/src/index.js');
758
- if (!existsSync(scriptPath)) {
759
- // Try installed location (node_modules/plusui-native-bindgen)
760
- scriptPath = resolve(__dirname, '../../../plusui-native-bindgen/src/index.js');
761
- }
866
+ const scriptPath = resolveBindgenScriptPath();
762
867
 
763
- if (!existsSync(scriptPath)) {
868
+ if (!scriptPath) {
764
869
  error(`Bindgen script not found. Please ensure plusui-native-bindgen is installed.`);
765
870
  }
766
871
 
@@ -771,11 +876,18 @@ async function runBindgen(providedArgs = null, options = {}) {
771
876
  const args = providedArgs ?? process.argv.slice(3);
772
877
  let bindgenArgs = [...args];
773
878
 
879
+ let usedDefaultAppMode = false;
880
+ let defaultOutputDir = null;
881
+ let defaultFrontendOutputDir = null;
882
+
774
883
  if (bindgenArgs.length === 0) {
775
- const { featuresDir: appFeaturesDir, outputDir: appOutputDir } = getAppBindgenPaths();
884
+ const { featuresDir: appFeaturesDir, outputDir: appOutputDir, frontendOutputDir } = getAppBindgenPaths();
776
885
 
777
886
  if (existsSync(appFeaturesDir)) {
778
887
  bindgenArgs = [appFeaturesDir, appOutputDir];
888
+ usedDefaultAppMode = true;
889
+ defaultOutputDir = appOutputDir;
890
+ defaultFrontendOutputDir = frontendOutputDir;
779
891
  log(`App mode: ${appFeaturesDir} -> ${appOutputDir}`, 'dim');
780
892
  } else {
781
893
  if (skipIfNoInput) {
@@ -794,10 +906,17 @@ async function runBindgen(providedArgs = null, options = {}) {
794
906
  });
795
907
 
796
908
  return new Promise((resolve, reject) => {
797
- proc.on('close', (code) => {
909
+ proc.on('close', async (code) => {
798
910
  if (code === 0) {
799
- log('\nBindgen complete!', 'green');
800
- resolve();
911
+ try {
912
+ if (usedDefaultAppMode && defaultOutputDir && defaultFrontendOutputDir) {
913
+ await syncGeneratedTsBindings(defaultOutputDir, defaultFrontendOutputDir);
914
+ }
915
+ log('\nBindgen complete!', 'green');
916
+ resolve();
917
+ } catch (syncErr) {
918
+ reject(syncErr);
919
+ }
801
920
  } else {
802
921
  reject(new Error(`Bindgen failed with code ${code}`));
803
922
  }
@@ -89,9 +89,11 @@ npm run build:all
89
89
  This creates platform-specific builds in:
90
90
  ```
91
91
  build/
92
- ├── Windows/ # Windows executable
93
- ├── MacOS/ # macOS app bundle
94
- └── Linux/ # Linux binary
92
+ ├── Windows/ # Windows builds
93
+ ├── MacOS/ # macOS builds
94
+ ├── Linux/ # Linux builds
95
+ ├── Android/ # Android builds
96
+ └── iOS/ # iOS builds
95
97
  ```
96
98
 
97
99
  ### Platform-specific builds
@@ -117,13 +117,7 @@ if(WIN32)
117
117
  endif()
118
118
 
119
119
  target_link_libraries({{PROJECT_NAME}} PRIVATE ole32 shell32 shlwapi user32 version)
120
-
121
- # Set subsystem to Windows for release builds (no console window)
122
- if(NOT PLUSUI_DEV_MODE)
123
- set_target_properties({{PROJECT_NAME}} PROPERTIES
124
- WIN32_EXECUTABLE TRUE
125
- )
126
- endif()
120
+ # Keep default console subsystem so standard int main() works in all build modes.
127
121
  elseif(APPLE)
128
122
  # macOS: WebKit
129
123
  find_library(WEBKIT_LIBRARY WebKit REQUIRED)
@@ -117,13 +117,7 @@ if(WIN32)
117
117
  endif()
118
118
 
119
119
  target_link_libraries({{PROJECT_NAME}} PRIVATE ole32 shell32 shlwapi user32 version)
120
-
121
- # Set subsystem to Windows for release builds (no console window)
122
- if(NOT PLUSUI_DEV_MODE)
123
- set_target_properties({{PROJECT_NAME}} PROPERTIES
124
- WIN32_EXECUTABLE TRUE
125
- )
126
- endif()
120
+ # Keep default console subsystem so standard int main() works in all build modes.
127
121
  elseif(APPLE)
128
122
  # macOS: WebKit
129
123
  find_library(WEBKIT_LIBRARY WebKit REQUIRED)