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.
|
|
4
|
-
"description": "PlusUI CLI - Build C++ desktop apps
|
|
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.
|
|
31
|
-
"plusui-native-bindgen": "^0.1.
|
|
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.
|
|
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
|
|
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
|
|
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 || '
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
800
|
-
|
|
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
|
|
93
|
-
├── MacOS/ # macOS
|
|
94
|
-
|
|
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)
|