windowpp 0.1.5 → 0.1.7

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/bin/windowpp.js CHANGED
@@ -58,6 +58,12 @@ switch (command) {
58
58
  break;
59
59
  }
60
60
 
61
+ case 'doctor': {
62
+ const { doctor } = require('../lib/doctor');
63
+ doctor();
64
+ break;
65
+ }
66
+
61
67
  case '--version':
62
68
  case '-v': {
63
69
  const pkg = require('../package.json');
@@ -73,11 +79,12 @@ Usage:
73
79
  windowpp create <app-name> Scaffold a new WindowPP app
74
80
  windowpp dev Start dev mode (Vite + native binary)
75
81
  windowpp build Build for production
82
+ windowpp doctor Check environment (CMake, compiler, deps)
76
83
 
77
84
  Options:
78
85
  --app-dir <path> App directory (default: cwd)
79
86
  --out-dir <path> Output directory for 'create' (default: cwd)
80
- --template <name> Template name for 'create' (default: solid)
87
+ --template <name> Template name for 'create' (default: prompt)
81
88
  --config <cfg> CMake config for 'build', e.g. Release|Debug (default: Release)
82
89
  --port <n> Vite dev server port (default: auto-detect or 3000)
83
90
  --clean Clean build outputs before building
package/lib/doctor.js ADDED
@@ -0,0 +1,317 @@
1
+ #!/usr/bin/env node
2
+ // cli/lib/doctor.js — WindowPP environment checker
3
+ //
4
+ // Checks everything required to build and run WindowPP apps on the current
5
+ // platform and reports status with fix instructions.
6
+ //
7
+ // Usage: windowpp doctor
8
+
9
+ 'use strict';
10
+
11
+ const { execSync, spawnSync } = require('child_process');
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const os = require('os');
15
+
16
+ const IS_WINDOWS = process.platform === 'win32';
17
+ const IS_MAC = process.platform === 'darwin';
18
+ const IS_LINUX = process.platform === 'linux';
19
+
20
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
21
+
22
+ function run(cmd) {
23
+ try {
24
+ return execSync(cmd, { stdio: 'pipe', encoding: 'utf8' }).trim();
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+
30
+ function which(name) {
31
+ const cmd = IS_WINDOWS ? `where ${name}` : `which ${name}`;
32
+ return run(cmd);
33
+ }
34
+
35
+ function versionOf(cmd) {
36
+ return run(`${cmd} --version`);
37
+ }
38
+
39
+ // ─── Check result builders ────────────────────────────────────────────────────
40
+
41
+ function ok(label, detail) {
42
+ return { status: 'ok', label, detail };
43
+ }
44
+
45
+ function warn(label, detail, fix) {
46
+ return { status: 'warn', label, detail, fix };
47
+ }
48
+
49
+ function fail(label, detail, fixes) {
50
+ return { status: 'fail', label, detail, fixes: Array.isArray(fixes) ? fixes : [fixes] };
51
+ }
52
+
53
+ // ─── Individual checks ────────────────────────────────────────────────────────
54
+
55
+ function checkNode() {
56
+ const v = process.version;
57
+ const major = parseInt(v.slice(1).split('.')[0], 10);
58
+ if (major >= 18) return ok('Node.js', v);
59
+ return warn('Node.js', v, `Upgrade to Node.js >=18: https://nodejs.org`);
60
+ }
61
+
62
+ function checkCMake() {
63
+ const v = run('cmake --version');
64
+ if (!v) {
65
+ if (IS_WINDOWS)
66
+ return fail('CMake', 'not found', [
67
+ 'winget install Kitware.CMake',
68
+ ' or download from https://cmake.org/download/',
69
+ ]);
70
+ if (IS_MAC)
71
+ return fail('CMake', 'not found', 'brew install cmake');
72
+ return fail('CMake', 'not found', [
73
+ 'sudo apt install cmake # Debian/Ubuntu',
74
+ 'sudo dnf install cmake # Fedora/RHEL',
75
+ ]);
76
+ }
77
+ const match = v.match(/cmake version (\S+)/i);
78
+ return ok('CMake', match ? match[1] : v.split('\n')[0]);
79
+ }
80
+
81
+ function checkPython() {
82
+ // Try python3 first, then python (Windows often only has python)
83
+ let v = run('python3 --version') || run('python --version');
84
+ if (!v) {
85
+ if (IS_WINDOWS)
86
+ return fail('Python 3', 'not found', [
87
+ 'winget install Python.Python.3',
88
+ ' or download from https://python.org/downloads/',
89
+ ]);
90
+ if (IS_MAC)
91
+ return fail('Python 3', 'not found', 'brew install python3');
92
+ return fail('Python 3', 'not found', [
93
+ 'sudo apt install python3 # Debian/Ubuntu',
94
+ 'sudo dnf install python3 # Fedora/RHEL',
95
+ ]);
96
+ }
97
+ const major = parseInt((v.match(/\d+/) || ['0'])[0], 10);
98
+ if (major < 3) return fail('Python 3', `found ${v}`, 'Install Python 3 from https://python.org');
99
+ return ok('Python 3', v.replace('Python ', ''));
100
+ }
101
+
102
+ function checkGit() {
103
+ const v = run('git --version');
104
+ if (!v) return warn('Git', 'not found (optional, needed for source clone)',
105
+ IS_WINDOWS ? 'winget install Git.Git' : IS_MAC ? 'brew install git' : 'sudo apt install git');
106
+ return ok('Git', v.replace('git version ', ''));
107
+ }
108
+
109
+ // ─── Platform-specific checks ─────────────────────────────────────────────────
110
+
111
+ function checksWindows() {
112
+ const results = [];
113
+
114
+ // Visual Studio / Build Tools
115
+ const vsWhere = [
116
+ 'C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\vswhere.exe',
117
+ 'C:\\Program Files\\Microsoft Visual Studio\\Installer\\vswhere.exe',
118
+ ].find(p => fs.existsSync(p));
119
+
120
+ if (vsWhere) {
121
+ const info = run(`"${vsWhere}" -latest -format value -property displayName`);
122
+ results.push(ok('Visual Studio / Build Tools', info || 'found'));
123
+ } else {
124
+ // Fallback: check for cl.exe or ninja
125
+ const cl = run('where cl.exe');
126
+ if (cl) {
127
+ results.push(ok('MSVC Compiler', 'cl.exe found'));
128
+ } else {
129
+ results.push(fail('Visual Studio Build Tools', 'not found', [
130
+ 'Install Visual Studio 2022 (Community is free):',
131
+ ' winget install Microsoft.VisualStudio.2022.Community',
132
+ ' or Build Tools only (no IDE):',
133
+ ' winget install Microsoft.VisualStudio.2022.BuildTools',
134
+ ' Make sure to select "Desktop development with C++" workload.',
135
+ ]));
136
+ }
137
+ }
138
+
139
+ // WebView2 Runtime (usually pre-installed on Windows 10/11)
140
+ const webview2Keys = [
141
+ 'HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}',
142
+ 'HKCU\\Software\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}',
143
+ ];
144
+ const wv2Found = webview2Keys.some(key => {
145
+ const r = run(`reg query "${key}" /v pv 2>nul`);
146
+ return r && r.includes('pv');
147
+ });
148
+ if (wv2Found) {
149
+ results.push(ok('WebView2 Runtime', 'installed'));
150
+ } else {
151
+ results.push(warn('WebView2 Runtime', 'not detected',
152
+ 'Download from https://developer.microsoft.com/microsoft-edge/webview2/\n' +
153
+ ' (Usually pre-installed on Windows 10 22H2+ and Windows 11.)'));
154
+ }
155
+
156
+ return results;
157
+ }
158
+
159
+ function checksMac() {
160
+ const results = [];
161
+
162
+ // Xcode Command Line Tools
163
+ const xcodeSelect = run('xcode-select -p');
164
+ if (xcodeSelect && fs.existsSync(xcodeSelect)) {
165
+ results.push(ok('Xcode Command Line Tools', xcodeSelect));
166
+ } else {
167
+ results.push(fail('Xcode Command Line Tools', 'not found',
168
+ 'xcode-select --install'));
169
+ }
170
+
171
+ // clang++
172
+ const clang = run('clang++ --version');
173
+ if (clang) {
174
+ results.push(ok('clang++', clang.split('\n')[0]));
175
+ } else {
176
+ results.push(fail('clang++', 'not found', 'xcode-select --install'));
177
+ }
178
+
179
+ return results;
180
+ }
181
+
182
+ function checksLinux() {
183
+ const results = [];
184
+
185
+ // C++ compiler
186
+ const gpp = run('g++ --version');
187
+ const clangpp = run('clang++ --version');
188
+ if (gpp) {
189
+ results.push(ok('g++', gpp.split('\n')[0]));
190
+ } else if (clangpp) {
191
+ results.push(ok('clang++', clangpp.split('\n')[0]));
192
+ } else {
193
+ results.push(fail('C++ Compiler', 'g++ or clang++ not found', [
194
+ 'sudo apt install build-essential # Debian/Ubuntu',
195
+ 'sudo dnf install gcc-c++ # Fedora/RHEL',
196
+ ]));
197
+ }
198
+
199
+ // pkg-config
200
+ if (!which('pkg-config')) {
201
+ results.push(fail('pkg-config', 'not found', [
202
+ 'sudo apt install pkg-config # Debian/Ubuntu',
203
+ 'sudo dnf install pkgconf # Fedora/RHEL',
204
+ ]));
205
+ } else {
206
+ results.push(ok('pkg-config', 'found'));
207
+ }
208
+
209
+ // webkit2gtk
210
+ const wk = run('pkg-config --modversion webkit2gtk-4.1 2>/dev/null') ||
211
+ run('pkg-config --modversion webkit2gtk-4.0 2>/dev/null');
212
+ if (wk) {
213
+ results.push(ok('WebKit2GTK', wk));
214
+ } else {
215
+ results.push(fail('WebKit2GTK', 'dev headers not found', [
216
+ 'sudo apt install libwebkit2gtk-4.1-dev # Ubuntu 22.04+',
217
+ 'sudo apt install libwebkit2gtk-4.0-dev # Ubuntu 20.04',
218
+ 'sudo dnf install webkit2gtk4.1-devel # Fedora/RHEL',
219
+ ]));
220
+ }
221
+
222
+ // GTK3
223
+ const gtk = run('pkg-config --modversion gtk+-3.0 2>/dev/null');
224
+ if (gtk) {
225
+ results.push(ok('GTK3', gtk));
226
+ } else {
227
+ results.push(fail('GTK3', 'dev headers not found', [
228
+ 'sudo apt install libgtk-3-dev # Debian/Ubuntu',
229
+ 'sudo dnf install gtk3-devel # Fedora/RHEL',
230
+ ]));
231
+ }
232
+
233
+ // XCB
234
+ const xcb = run('pkg-config --modversion xcb 2>/dev/null');
235
+ if (xcb) {
236
+ results.push(ok('XCB', xcb));
237
+ } else {
238
+ results.push(fail('XCB', 'dev headers not found', [
239
+ 'sudo apt install libxcb1-dev libxcb-util-dev libxcb-randr0-dev \\',
240
+ ' libxcb-icccm4-dev libxcb-ewmh-dev libxcb-keysyms1-dev libxcb-xfixes0-dev',
241
+ 'sudo dnf install libxcb-devel xcb-util-devel xcb-util-keysyms-devel',
242
+ ]));
243
+ }
244
+
245
+ return results;
246
+ }
247
+
248
+ // ─── Report printer ───────────────────────────────────────────────────────────
249
+
250
+ function printResults(results) {
251
+ const icons = { ok: '✓', warn: '⚠', fail: '✗' };
252
+ const colors = {
253
+ ok: s => `\x1B[32m${s}\x1B[0m`,
254
+ warn: s => `\x1B[33m${s}\x1B[0m`,
255
+ fail: s => `\x1B[31m${s}\x1B[0m`,
256
+ };
257
+ // Fallback for non-color terminals
258
+ const noColor = process.env.NO_COLOR || !process.stdout.isTTY;
259
+
260
+ const c = (status, s) => noColor ? s : colors[status](s);
261
+
262
+ for (const r of results) {
263
+ const icon = icons[r.status];
264
+ const label = r.label.padEnd(28);
265
+ const detail = r.detail ? ` ${r.detail}` : '';
266
+ console.log(` ${c(r.status, icon)} ${label}${detail}`);
267
+ if (r.status !== 'ok') {
268
+ const fixes = r.fixes ?? (r.fix ? [r.fix] : []);
269
+ if (fixes.length) {
270
+ console.log();
271
+ console.log(` Fix:`);
272
+ for (const line of fixes) {
273
+ for (const l of line.split('\n')) {
274
+ console.log(` ${l}`);
275
+ }
276
+ }
277
+ console.log();
278
+ }
279
+ }
280
+ }
281
+ }
282
+
283
+ // ─── Entry point ─────────────────────────────────────────────────────────────
284
+
285
+ function doctor() {
286
+ const platformLabel = IS_WINDOWS ? 'Windows' : IS_MAC ? 'macOS' : 'Linux';
287
+ console.log(`\n=== WindowPP Doctor (${platformLabel}) ===\n`);
288
+
289
+ const results = [
290
+ checkNode(),
291
+ checkCMake(),
292
+ checkPython(),
293
+ checkGit(),
294
+ ...(IS_WINDOWS ? checksWindows() :
295
+ IS_MAC ? checksMac() :
296
+ checksLinux()),
297
+ ];
298
+
299
+ printResults(results);
300
+
301
+ const fails = results.filter(r => r.status === 'fail');
302
+ const warns = results.filter(r => r.status === 'warn');
303
+ const oks = results.filter(r => r.status === 'ok');
304
+
305
+ console.log(`\n ${oks.length} ok | ${warns.length} warning(s) | ${fails.length} error(s)\n`);
306
+
307
+ if (fails.length) {
308
+ console.log(' Fix the errors above before running windowpp build / windowpp dev.\n');
309
+ process.exitCode = 1;
310
+ } else if (warns.length) {
311
+ console.log(' Environment is usable, but review the warnings above.\n');
312
+ } else {
313
+ console.log(' Environment is ready. Run windowpp dev in your app directory.\n');
314
+ }
315
+ }
316
+
317
+ module.exports = { doctor };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "windowpp",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "WindowPP CLI — build, dev, and scaffold for WindowPP apps",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -41,6 +41,12 @@ const TEMPLATE_SOURCES = [
41
41
  appName: 'windowpp-example',
42
42
  cmakeTarget: 'wpp_example',
43
43
  appTitle: 'WindowPP Example',
44
+ // Extra literal strings in main.cpp that need to become the app name/title
45
+ extraTokens: [
46
+ ['vite-template-solid', '{{APP_NAME}}-frontend'],
47
+ ['"WindowPP Dev"', '"{{APP_TITLE}}"'],
48
+ ['"WindowPP - Dev View"','\"{{APP_TITLE}}"'],
49
+ ],
44
50
  // Use the standalone-compatible versions from the solid base template
45
51
  useBase: [
46
52
  'CMakeLists.txt',
@@ -95,12 +101,16 @@ const TEXT_EXTENSIONS = new Set([
95
101
  // Works because CMakeLists adds target_include_directories(...PRIVATE "${WPP_FRAMEWORK_DIR}/src")
96
102
 
97
103
  function buildTokens(entry) {
104
+ const extra = entry.extraTokens ?? [];
98
105
  return [
99
106
  // ── Name tokens (longer strings first to avoid partial matches) ─────
100
107
  [entry.appName, '{{APP_NAME}}'],
101
108
  [entry.cmakeTarget, '{{CMAKE_TARGET}}'],
102
109
  [entry.appTitle, '{{APP_TITLE}}'],
103
110
 
111
+ // ── Extra per-template literal substitutions ───────────────────────
112
+ ...extra,
113
+
104
114
  // ── TypeScript/TSX: monorepo-relative imports from frontend/src/ ────
105
115
  ["'../../../src/", "'@wpp/"],
106
116
  ['"../../../src/', '"@wpp/'],
@@ -140,8 +150,10 @@ function copySourceDir(src, dest, tokens, skipRelPaths, relPrefix) {
140
150
  fs.mkdirSync(dest, { recursive: true });
141
151
  for (const dirent of fs.readdirSync(src, { withFileTypes: true })) {
142
152
  const relPath = relPrefix ? `${relPrefix}/${dirent.name}` : dirent.name;
143
- // Skip if matched by bare name OR by relative path
144
- if (skipRelPaths.has(dirent.name) || skipRelPaths.has(relPath)) continue;
153
+ // Only match by full relative path — bare-name matching is intentionally
154
+ // avoided so that e.g. 'package.json' in useBase only skips the ROOT
155
+ // package.json and not frontend/package.json or any nested one.
156
+ if (skipRelPaths.has(relPath)) continue;
145
157
 
146
158
  const srcPath = path.join(src, dirent.name);
147
159
  const destPath = path.join(dest, dirent.name);
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "{{APP_NAME}}-frontend",
3
+ "version": "0.0.0",
4
+ "description": "",
5
+ "type": "module",
6
+ "scripts": {
7
+ "start": "vite",
8
+ "dev": "vite",
9
+ "build": "vite build",
10
+ "serve": "vite preview"
11
+ },
12
+ "license": "MIT",
13
+ "devDependencies": {
14
+ "@tailwindcss/vite": "^4.1.13",
15
+ "solid-devtools": "^0.34.3",
16
+ "tailwindcss": "^4.1.13",
17
+ "typescript": "^5.9.2",
18
+ "vite": "^7.1.4",
19
+ "vite-plugin-solid": "^2.11.8"
20
+ },
21
+ "dependencies": {
22
+ "@solidjs/router": "^0.16.1",
23
+ "solid-js": "^1.9.9"
24
+ }
25
+ }
@@ -28,7 +28,7 @@
28
28
 
29
29
  int main() {
30
30
  wpp::AppConfig app_config;
31
- app_config.name = "WindowPP Dev";
31
+ app_config.name = "{{APP_TITLE}}";
32
32
  app_config.quit_on_last_close = true;
33
33
  app_config.tray_enabled = true;
34
34
 
@@ -38,7 +38,7 @@ int main() {
38
38
 
39
39
  wpp::WindowConfig main_win;
40
40
  main_win.id = "main";
41
- main_win.title = "WindowPP - Dev View";
41
+ main_win.title = "{{APP_TITLE}}";
42
42
  main_win.width = 1280;
43
43
  main_win.height = 800;
44
44
  main_win.center = true;