plusui-native 0.2.68 → 0.2.70

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,6 +1,6 @@
1
1
  {
2
2
  "name": "plusui-native",
3
- "version": "0.2.68",
3
+ "version": "0.2.70",
4
4
  "description": "PlusUI CLI - Build C++ desktop apps modern UI ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -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.67",
31
- "plusui-native-connect": "^0.1.67"
30
+ "plusui-native-builder": "^0.1.69",
31
+ "plusui-native-connect": "^0.1.69"
32
32
  },
33
33
  "peerDependencies": {
34
- "plusui-native-connect": "^0.1.67"
34
+ "plusui-native-connect": "^0.1.69"
35
35
  },
36
36
  "publishConfig": {
37
37
  "access": "public"
@@ -19,25 +19,21 @@ async function detectMSVC() {
19
19
  const vswherePath = 'C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\vswhere.exe';
20
20
 
21
21
  if (!existsSync(vswherePath)) {
22
- // Fallback: check common VS paths manually
23
- const vsPaths = [
24
- 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC',
25
- 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Professional\\VC\\Tools\\MSVC',
26
- 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Tools\\MSVC',
27
- 'C:\\Program Files\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC',
28
- 'C:\\Program Files\\Microsoft Visual Studio\\2019\\Professional\\VC\\Tools\\MSVC',
29
- 'C:\\Program Files\\Microsoft Visual Studio\\2019\\Enterprise\\VC\\Tools\\MSVC'
30
- ];
31
-
32
- for (const vsPath of vsPaths) {
33
- if (existsSync(vsPath)) {
34
- const version = vsPath.includes('2022') ? 'Visual Studio 2022' : 'Visual Studio 2019';
35
- return {
36
- found: true,
37
- name: version,
38
- path: vsPath,
39
- valid: true
40
- };
22
+ // Fallback: check common VS paths for years 2019–2026
23
+ const vsYears = ['2026', '2025', '2024', '2023', '2022', '2019'];
24
+ const vsEditions = ['Community', 'Professional', 'Enterprise', 'BuildTools'];
25
+
26
+ for (const year of vsYears) {
27
+ for (const edition of vsEditions) {
28
+ const vsPath = `C:\\Program Files\\Microsoft Visual Studio\\${year}\\${edition}\\VC\\Tools\\MSVC`;
29
+ if (existsSync(vsPath)) {
30
+ return {
31
+ found: true,
32
+ name: `Visual Studio ${year} ${edition}`,
33
+ path: vsPath,
34
+ valid: true
35
+ };
36
+ }
41
37
  }
42
38
  }
43
39
 
@@ -95,7 +91,7 @@ async function detectXcode() {
95
91
 
96
92
  // Parse clang version
97
93
  const versionMatch = clangResult.output.match(/clang version (\d+\.\d+\.\d+)/i) ||
98
- clangResult.output.match(/Apple clang version (\d+\.\d+\.\d+)/i);
94
+ clangResult.output.match(/Apple clang version (\d+\.\d+\.\d+)/i);
99
95
  const version = versionMatch ? versionMatch[1] : 'unknown';
100
96
 
101
97
  return {
package/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import { mkdir, readFile, stat, rm, readdir, writeFile, copyFile } from 'fs/promises';
4
4
  import { existsSync, watch, statSync, mkdirSync } from 'fs';
@@ -74,23 +74,38 @@ function checkTools() {
74
74
  }
75
75
  }
76
76
 
77
+ // Compiler / build tools check
77
78
  if (platform === 'win32') {
78
- const vsPaths = [
79
- 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC',
80
- 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Professional\\VC\\Tools\\MSVC',
81
- 'C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Tools\\MSVC'
82
- ];
83
-
79
+ // Use vswhere.exe first (same detection as `plusui doctor`)
84
80
  let vsFound = false;
85
- for (const p of vsPaths) {
86
- if (existsSync(p)) {
87
- vsFound = true;
88
- break;
81
+ const vswherePath = 'C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\vswhere.exe';
82
+ if (existsSync(vswherePath)) {
83
+ try {
84
+ const output = execSync(
85
+ `"${vswherePath}" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`,
86
+ { stdio: ['pipe', 'pipe', 'pipe'], encoding: 'utf8', timeout: 10000 }
87
+ ).trim();
88
+ if (output) vsFound = true;
89
+ } catch { }
90
+ }
91
+
92
+ // Fallback: check common VS paths for years 2019–2026
93
+ if (!vsFound) {
94
+ const vsYears = ['2026', '2025', '2024', '2023', '2022', '2019'];
95
+ const vsEditions = ['Community', 'Professional', 'Enterprise', 'BuildTools'];
96
+ for (const year of vsYears) {
97
+ for (const edition of vsEditions) {
98
+ if (existsSync(`C:\\Program Files\\Microsoft Visual Studio\\${year}\\${edition}\\VC\\Tools\\MSVC`)) {
99
+ vsFound = true;
100
+ break;
101
+ }
102
+ }
103
+ if (vsFound) break;
89
104
  }
90
105
  }
91
106
 
92
107
  if (!vsFound) {
93
- required.push({ name: 'Visual Studio 2022', install: 'Download from visualstudio.microsoft.com with C++ workload', auto: null });
108
+ required.push({ name: 'Visual Studio (C++ workload)', install: 'Download from visualstudio.microsoft.com with C++ workload', auto: null });
94
109
  }
95
110
  } else if (platform === 'darwin') {
96
111
  try {
@@ -196,16 +211,64 @@ function getCMakePath() {
196
211
  return 'cmake';
197
212
  }
198
213
 
214
+ // Find vcvarsall.bat for Windows builds (needed for Ninja generator)
215
+ let _vcvarsallCache = undefined;
216
+ function findVcvarsall() {
217
+ if (_vcvarsallCache !== undefined) return _vcvarsallCache;
218
+
219
+ const vswherePath = 'C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\vswhere.exe';
220
+ if (existsSync(vswherePath)) {
221
+ try {
222
+ const installPath = execSync(
223
+ `"${vswherePath}" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`,
224
+ { stdio: ['pipe', 'pipe', 'pipe'], encoding: 'utf8', timeout: 10000 }
225
+ ).trim();
226
+ if (installPath) {
227
+ const vcvars = join(installPath, 'VC', 'Auxiliary', 'Build', 'vcvarsall.bat');
228
+ if (existsSync(vcvars)) {
229
+ _vcvarsallCache = vcvars;
230
+ return vcvars;
231
+ }
232
+ }
233
+ } catch { }
234
+ }
235
+
236
+ // Fallback: scan known paths
237
+ const vsYears = ['2026', '2025', '2024', '2023', '2022', '2019'];
238
+ const vsEditions = ['Community', 'Professional', 'Enterprise', 'BuildTools'];
239
+ for (const year of vsYears) {
240
+ for (const edition of vsEditions) {
241
+ const vcvars = `C:\\Program Files\\Microsoft Visual Studio\\${year}\\${edition}\\VC\\Auxiliary\\Build\\vcvarsall.bat`;
242
+ if (existsSync(vcvars)) {
243
+ _vcvarsallCache = vcvars;
244
+ return vcvars;
245
+ }
246
+ }
247
+ }
248
+
249
+ _vcvarsallCache = null;
250
+ return null;
251
+ }
252
+
199
253
  function runCMake(args, options = {}) {
200
254
  const cmake = getCMakePath();
255
+
256
+ // On Windows, wrap cmake in vcvarsall to ensure cl.exe is in PATH (needed for Ninja)
257
+ if (process.platform === 'win32') {
258
+ const vcvarsall = findVcvarsall();
259
+ if (vcvarsall) {
260
+ return execSync(`cmd /c ""${vcvarsall}" x64 >nul 2>&1 && "${cmake}" ${args}"`, { stdio: 'inherit', shell: true, ...options });
261
+ }
262
+ }
263
+
201
264
  return execSync(`"${cmake}" ${args}`, { stdio: 'inherit', ...options });
202
265
  }
203
266
 
204
267
  function getInstalledPackageVersion(packageName) {
205
268
  try {
206
- const result = execSync(`npm list ${packageName} --depth=0 --json`, {
207
- encoding: 'utf8',
208
- stdio: ['pipe', 'pipe', 'ignore']
269
+ const result = execSync(`npm list ${packageName} --depth=0 --json`, {
270
+ encoding: 'utf8',
271
+ stdio: ['pipe', 'pipe', 'ignore']
209
272
  });
210
273
  const json = JSON.parse(result);
211
274
  if (json.dependencies && json.dependencies[packageName]) {
@@ -214,12 +277,12 @@ function getInstalledPackageVersion(packageName) {
214
277
  } catch {
215
278
  // Package not installed locally
216
279
  }
217
-
280
+
218
281
  // Try global installation
219
282
  try {
220
- const result = execSync(`npm list -g ${packageName} --depth=0 --json`, {
221
- encoding: 'utf8',
222
- stdio: ['pipe', 'pipe', 'ignore']
283
+ const result = execSync(`npm list -g ${packageName} --depth=0 --json`, {
284
+ encoding: 'utf8',
285
+ stdio: ['pipe', 'pipe', 'ignore']
223
286
  });
224
287
  const json = JSON.parse(result);
225
288
  if (json.dependencies && json.dependencies[packageName]) {
@@ -228,15 +291,15 @@ function getInstalledPackageVersion(packageName) {
228
291
  } catch {
229
292
  return null;
230
293
  }
231
-
294
+
232
295
  return null;
233
296
  }
234
297
 
235
298
  function getLatestPackageVersion(packageName) {
236
299
  try {
237
- const result = execSync(`npm view ${packageName} version`, {
238
- encoding: 'utf8',
239
- stdio: ['pipe', 'pipe', 'ignore']
300
+ const result = execSync(`npm view ${packageName} version`, {
301
+ encoding: 'utf8',
302
+ stdio: ['pipe', 'pipe', 'ignore']
240
303
  });
241
304
  return result.trim();
242
305
  } catch {
@@ -247,7 +310,7 @@ function getLatestPackageVersion(packageName) {
247
310
  function compareVersions(v1, v2) {
248
311
  const parts1 = v1.split('.').map(Number);
249
312
  const parts2 = v2.split('.').map(Number);
250
-
313
+
251
314
  for (let i = 0; i < 3; i++) {
252
315
  if (parts1[i] > parts2[i]) return 1;
253
316
  if (parts1[i] < parts2[i]) return -1;
@@ -262,9 +325,9 @@ function showVersionInfo() {
262
325
  'plusui-native-builder',
263
326
  'plusui-native-connect'
264
327
  ];
265
-
328
+
266
329
  logSection('PlusUI Package Versions');
267
-
330
+
268
331
  packages.forEach(pkg => {
269
332
  let version;
270
333
  if (pkg === cliPackageJson.name) {
@@ -279,33 +342,33 @@ function showVersionInfo() {
279
342
  }
280
343
  }
281
344
  });
282
-
345
+
283
346
  console.log('');
284
347
  }
285
348
 
286
349
  async function updatePlusUIPackages() {
287
350
  logSection('Updating PlusUI Packages');
288
-
351
+
289
352
  const packages = [
290
353
  cliPackageJson.name,
291
354
  'plusui-native-core',
292
355
  'plusui-native-builder',
293
356
  'plusui-native-connect'
294
357
  ];
295
-
358
+
296
359
  log('Checking for updates...\n', 'blue');
297
-
360
+
298
361
  // Check if packages are installed locally or globally
299
362
  const isInProject = existsSync(join(process.cwd(), 'package.json'));
300
-
363
+
301
364
  if (isInProject) {
302
365
  let updatedCount = 0;
303
366
  let upToDateCount = 0;
304
367
  let installedCount = 0;
305
-
368
+
306
369
  for (const pkg of packages) {
307
370
  const currentVersion = getInstalledPackageVersion(pkg);
308
-
371
+
309
372
  if (!currentVersion) {
310
373
  const latestVersion = getLatestPackageVersion(pkg);
311
374
 
@@ -327,22 +390,22 @@ async function updatePlusUIPackages() {
327
390
  }
328
391
  continue;
329
392
  }
330
-
393
+
331
394
  // Get latest version from npm
332
395
  const latestVersion = getLatestPackageVersion(pkg);
333
-
396
+
334
397
  if (!latestVersion) {
335
398
  log(`${COLORS.yellow}${pkg}: couldn't check for updates${COLORS.reset}`);
336
399
  continue;
337
400
  }
338
-
401
+
339
402
  const comparison = compareVersions(latestVersion, currentVersion);
340
-
403
+
341
404
  if (comparison > 0) {
342
405
  // Newer version available
343
406
  try {
344
407
  log(`${COLORS.blue}${pkg}: ${currentVersion} → ${latestVersion}${COLORS.reset}`);
345
- execSync(`npm install ${pkg}@${latestVersion}`, {
408
+ execSync(`npm install ${pkg}@${latestVersion}`, {
346
409
  stdio: ['ignore', 'ignore', 'pipe'],
347
410
  encoding: 'utf8'
348
411
  });
@@ -357,7 +420,7 @@ async function updatePlusUIPackages() {
357
420
  upToDateCount++;
358
421
  }
359
422
  }
360
-
423
+
361
424
  console.log('');
362
425
  if (updatedCount > 0) {
363
426
  log(`Updated ${updatedCount} package${updatedCount !== 1 ? 's' : ''}`, 'green');
@@ -370,17 +433,17 @@ async function updatePlusUIPackages() {
370
433
  }
371
434
  } else {
372
435
  log('Updating global CLI package...', 'cyan');
373
-
436
+
374
437
  const currentVersion = cliPackageJson.version;
375
438
  const latestVersion = getLatestPackageVersion(cliPackageJson.name);
376
-
439
+
377
440
  if (!latestVersion) {
378
441
  log('Couldn\'t check for updates', 'yellow');
379
442
  return;
380
443
  }
381
-
444
+
382
445
  const comparison = compareVersions(latestVersion, currentVersion);
383
-
446
+
384
447
  if (comparison > 0) {
385
448
  try {
386
449
  log(`${COLORS.blue}${cliPackageJson.name}: ${currentVersion} → ${latestVersion}${COLORS.reset}`);
@@ -393,7 +456,7 @@ async function updatePlusUIPackages() {
393
456
  log(`✓ ${cliPackageJson.name} v${currentVersion} (already up to date)`, 'green');
394
457
  }
395
458
  }
396
-
459
+
397
460
  console.log('');
398
461
  }
399
462
 
@@ -411,7 +474,7 @@ function findLikelyProjectDirs(baseDir) {
411
474
  if (entries) {
412
475
  // noop; just to ensure cwd is a Node project when possible
413
476
  }
414
- } catch {}
477
+ } catch { }
415
478
 
416
479
  const candidates = [];
417
480
  try {
@@ -431,7 +494,7 @@ function findLikelyProjectDirs(baseDir) {
431
494
  candidates.push(dirName);
432
495
  }
433
496
  }
434
- } catch {}
497
+ } catch { }
435
498
 
436
499
  return candidates;
437
500
  }
@@ -583,6 +646,9 @@ function buildBackend(platform = null, devMode = false) {
583
646
 
584
647
  if (platformConfig.generator) {
585
648
  cmakeArgs += ` -G "${platformConfig.generator}"`;
649
+ } else if (process.platform === 'win32') {
650
+ // Use Ninja on Windows to avoid VS generator compatibility issues
651
+ cmakeArgs += ' -G Ninja';
586
652
  }
587
653
 
588
654
  log(`Configuring CMake...`, 'blue');
@@ -645,30 +711,30 @@ async function embedAssets() {
645
711
  if (!existsSync(assetsDir)) {
646
712
  try {
647
713
  await mkdir(assetsDir, { recursive: true });
648
- } catch(e) {}
714
+ } catch (e) { }
649
715
  }
650
716
 
651
717
  logSection('Embedding Assets');
652
-
718
+
653
719
  // Always generate the header file, even if empty
654
720
  let headerContent = '#pragma once\n\n';
655
721
  headerContent += '// THIS FILE IS AUTO-GENERATED BY PLUSUI CLI\n';
656
722
  headerContent += '// DO NOT MODIFY MANUALLY\n\n';
657
-
658
- const files = existsSync(assetsDir)
723
+
724
+ const files = existsSync(assetsDir)
659
725
  ? (await readdir(assetsDir)).filter(f => !statSync(join(assetsDir, f)).isDirectory())
660
726
  : [];
661
-
727
+
662
728
  if (files.length === 0) {
663
729
  log('No assets found in assets/ folder', 'dim');
664
730
  } else {
665
731
  for (const file of files) {
666
732
  const filePath = join(assetsDir, file);
667
733
  log(`Processing ${file}...`, 'dim');
668
-
734
+
669
735
  const varName = file.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase();
670
736
  const data = await readFile(filePath);
671
-
737
+
672
738
  headerContent += `static const unsigned char ASSET_${varName}[] = {`;
673
739
  for (let i = 0; i < data.length; i++) {
674
740
  if (i % 16 === 0) headerContent += '\n ';
@@ -681,7 +747,7 @@ async function embedAssets() {
681
747
 
682
748
  const genDir = join(process.cwd(), 'generated');
683
749
  if (!existsSync(genDir)) await mkdir(genDir, { recursive: true });
684
-
750
+
685
751
  await writeFile(join(genDir, 'assets.h'), headerContent);
686
752
  log(`✓ Assets header generated: generated/assets.h`, 'green');
687
753
  }
@@ -774,9 +840,15 @@ async function startBackend() {
774
840
 
775
841
  const buildDir = getDevBuildDir();
776
842
 
843
+ // On Windows, use Ninja generator to avoid VS generator compatibility issues
844
+ let generatorArgs = '';
845
+ if (process.platform === 'win32') {
846
+ generatorArgs = ' -G Ninja';
847
+ }
848
+
777
849
  // Always configure with dev mode to ensure PLUSUI_DEV_MODE is set correctly
778
850
  log('Configuring CMake...', 'blue');
779
- runCMake(`-S . -B "${buildDir}" -DPLUSUI_DEV_MODE=ON`);
851
+ runCMake(`-S . -B "${buildDir}" -DPLUSUI_DEV_MODE=ON${generatorArgs}`);
780
852
 
781
853
  log('Compiling...', 'blue');
782
854
  runCMake(`--build "${buildDir}"`);
@@ -784,16 +856,17 @@ async function startBackend() {
784
856
  // Find executable
785
857
  let exePath;
786
858
  if (process.platform === 'win32') {
787
- // Visual Studio puts exe in build/dev/<projectname>/Debug/<projectname>.exe
788
- exePath = join(buildDir, projectName, 'Debug', `${projectName}.exe`);
859
+ // Ninja puts exe directly in build dir
860
+ exePath = join(buildDir, `${projectName}.exe`);
789
861
  if (!existsSync(exePath)) {
790
- exePath = join(buildDir, 'Debug', `${projectName}.exe`);
862
+ exePath = join(buildDir, 'bin', `${projectName}.exe`);
791
863
  }
864
+ // Fallback for VS generator (Debug subfolder)
792
865
  if (!existsSync(exePath)) {
793
- exePath = join(buildDir, 'bin', `${projectName}.exe`);
866
+ exePath = join(buildDir, projectName, 'Debug', `${projectName}.exe`);
794
867
  }
795
868
  if (!existsSync(exePath)) {
796
- exePath = join(buildDir, `${projectName}.exe`);
869
+ exePath = join(buildDir, 'Debug', `${projectName}.exe`);
797
870
  }
798
871
  } else {
799
872
  exePath = join(buildDir, projectName);
@@ -828,7 +901,7 @@ async function killPort(port) {
828
901
  try {
829
902
  const output = execSync(`netstat -ano | findstr :${port}`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
830
903
  const lines = output.split('\n').filter(line => line.includes(`:${port}`) && line.includes('LISTENING'));
831
-
904
+
832
905
  for (const line of lines) {
833
906
  const parts = line.trim().split(/\s+/);
834
907
  const pid = parts[parts.length - 1];
@@ -1097,7 +1170,7 @@ async function runBindgen(providedArgs = null, options = {}) {
1097
1170
  defaultOutputDir = appOutputDir;
1098
1171
  log(`Project mode: ${process.cwd()} -> ${appOutputDir}`, 'dim');
1099
1172
  }
1100
-
1173
+
1101
1174
  // Spawn node process
1102
1175
  const proc = spawn(process.execPath, [scriptPath, ...bindgenArgs], {
1103
1176
  stdio: 'inherit',