windowpp 0.1.1 → 0.1.3
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/lib/create.js +98 -283
- package/package.json +4 -2
- package/scripts/publish.js +4 -0
- package/scripts/sync-templates.js +238 -0
- package/templates/example/CMakeLists.txt +59 -0
- package/templates/example/frontend/index.html +20 -0
- package/templates/example/frontend/src/API.ts +56 -0
- package/templates/example/frontend/src/App.tsx +781 -0
- package/templates/example/frontend/src/Layout.tsx +5 -0
- package/templates/example/frontend/src/components/ClipboardToast.tsx +23 -0
- package/templates/example/frontend/src/components/FileSearch.tsx +936 -0
- package/templates/example/frontend/src/components/InfiniteScrollList.tsx +267 -0
- package/templates/example/frontend/src/components/index.ts +13 -0
- package/templates/example/frontend/src/filedrop.css +421 -0
- package/templates/example/frontend/src/index.css +1 -0
- package/templates/example/frontend/src/index.tsx +24 -0
- package/templates/example/frontend/src/pages/About.tsx +47 -0
- package/templates/example/frontend/src/pages/Settings.tsx +37 -0
- package/templates/example/frontend/tsconfig.json +20 -0
- package/templates/example/frontend/vite.config.ts +27 -0
- package/templates/example/main.cpp +224 -0
- package/templates/example/package.json +12 -0
- package/templates/solid/CMakeLists.txt +4 -1
- package/templates/solid/frontend/vite.config.ts +3 -1
package/lib/create.js
CHANGED
|
@@ -1,283 +1,98 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// cli/lib/create.js — WindowPP app scaffolding
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
'
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
content = content.split(token).join(value);
|
|
100
|
-
}
|
|
101
|
-
fs.writeFileSync(dest, content, 'utf8');
|
|
102
|
-
} else {
|
|
103
|
-
fs.copyFileSync(src, dest);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
module.exports = { create };
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
function create(name, options = {}) {
|
|
111
|
-
const {
|
|
112
|
-
template = 'solid',
|
|
113
|
-
outDir = process.cwd(),
|
|
114
|
-
installDeps = true,
|
|
115
|
-
} = options;
|
|
116
|
-
|
|
117
|
-
if (!name || !/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name)) {
|
|
118
|
-
console.error('Error: app name must start with a letter and contain only letters, digits, hyphens or underscores.');
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const appDir = path.join(outDir, name);
|
|
123
|
-
if (fs.existsSync(appDir)) {
|
|
124
|
-
console.error(`Error: directory "${appDir}" already exists.`);
|
|
125
|
-
process.exit(1);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const cliDir = path.resolve(__dirname, '..');
|
|
129
|
-
const templateDir = path.join(cliDir, 'templates', template);
|
|
130
|
-
if (!fs.existsSync(templateDir)) {
|
|
131
|
-
console.error(`Error: template "${template}" not found in ${path.join(cliDir, 'templates')}`);
|
|
132
|
-
process.exit(1);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const rootDir = findRootDir(outDir);
|
|
136
|
-
|
|
137
|
-
// cmake target name: replace hyphens with underscores
|
|
138
|
-
const cmakeTarget = name.replace(/-/g, '_');
|
|
139
|
-
const appTitle = name
|
|
140
|
-
.split(/[-_]/)
|
|
141
|
-
.map(w => w.charAt(0).toUpperCase() + w.slice(1))
|
|
142
|
-
.join(' ');
|
|
143
|
-
|
|
144
|
-
const tokens = {
|
|
145
|
-
'{{APP_NAME}}': name,
|
|
146
|
-
'{{APP_TITLE}}': appTitle,
|
|
147
|
-
'{{CMAKE_TARGET}}': cmakeTarget,
|
|
148
|
-
'{{REPO_ROOT}}': rootDir.replace(/\\/g, '/'),
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
console.log(`\n=== WindowPP Create — ${name} (template: ${template}) ===\n`);
|
|
152
|
-
console.log(`Scaffolding into: ${appDir}\n`);
|
|
153
|
-
|
|
154
|
-
// ── Copy template ─────────────────────────────────────────────────────────
|
|
155
|
-
copyDir(templateDir, appDir, tokens);
|
|
156
|
-
|
|
157
|
-
// ── Register in CMakeLists.txt ────────────────────────────────────────────
|
|
158
|
-
const appDirRelative = path.relative(rootDir, appDir).replace(/\\/g, '/');
|
|
159
|
-
addToCMake(rootDir, name, cmakeTarget, appDirRelative);
|
|
160
|
-
|
|
161
|
-
// ── Install frontend deps ─────────────────────────────────────────────────
|
|
162
|
-
const frontendDir = path.join(appDir, 'frontend');
|
|
163
|
-
if (installDeps && fs.existsSync(frontendDir)) {
|
|
164
|
-
console.log('Installing frontend dependencies...');
|
|
165
|
-
execSync('npm install', { cwd: frontendDir, stdio: 'inherit' });
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
console.log(`\n✓ Created "${name}"\n`);
|
|
169
|
-
console.log('Next steps:');
|
|
170
|
-
console.log(` cd ${name}`);
|
|
171
|
-
console.log(' windowpp dev\n');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
175
|
-
|
|
176
|
-
function copyDir(src, dest, tokens) {
|
|
177
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
178
|
-
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
179
|
-
const srcPath = path.join(src, entry.name);
|
|
180
|
-
const destPath = path.join(dest, entry.name);
|
|
181
|
-
if (entry.isDirectory()) {
|
|
182
|
-
copyDir(srcPath, destPath, tokens);
|
|
183
|
-
} else {
|
|
184
|
-
copyFile(srcPath, destPath, tokens);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const TEXT_EXTENSIONS = new Set([
|
|
190
|
-
'.cpp', '.h', '.ts', '.tsx', '.js', '.json', '.html',
|
|
191
|
-
'.css', '.md', '.txt', '.cmake', '.sh', '.env',
|
|
192
|
-
]);
|
|
193
|
-
|
|
194
|
-
function copyFile(src, dest, tokens) {
|
|
195
|
-
const ext = path.extname(src).toLowerCase();
|
|
196
|
-
if (TEXT_EXTENSIONS.has(ext)) {
|
|
197
|
-
let content = fs.readFileSync(src, 'utf8');
|
|
198
|
-
for (const [token, value] of Object.entries(tokens)) {
|
|
199
|
-
content = content.split(token).join(value);
|
|
200
|
-
}
|
|
201
|
-
fs.writeFileSync(dest, content, 'utf8');
|
|
202
|
-
} else {
|
|
203
|
-
fs.copyFileSync(src, dest);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function addToCMake(rootDir, appName, cmakeTarget, appDirRelative) {
|
|
208
|
-
const cmakePath = path.join(rootDir, 'CMakeLists.txt');
|
|
209
|
-
let cmake = fs.readFileSync(cmakePath, 'utf8');
|
|
210
|
-
|
|
211
|
-
// Insert before the # ─── Install section
|
|
212
|
-
const installMarker = '# ─── Install ──';
|
|
213
|
-
if (!cmake.includes(installMarker)) {
|
|
214
|
-
console.warn('Warning: could not locate Install section in CMakeLists.txt — skipping auto-registration.');
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const appBlock = generateCMakeBlock(appName, cmakeTarget, appDirRelative);
|
|
219
|
-
cmake = cmake.replace(installMarker, appBlock + '\n' + installMarker);
|
|
220
|
-
fs.writeFileSync(cmakePath, cmake, 'utf8');
|
|
221
|
-
console.log(`Registered ${cmakeTarget} in CMakeLists.txt`);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function generateCMakeBlock(appName, cmakeTarget, appDirRelative) {
|
|
225
|
-
const generatedVar = cmakeTarget.toUpperCase() + '_GENERATED_DIR';
|
|
226
|
-
const embeddedCppVar = cmakeTarget.toUpperCase() + '_EMBEDDED_ASSETS_CPP';
|
|
227
|
-
const embeddedHVar = cmakeTarget.toUpperCase() + '_EMBEDDED_ASSETS_H';
|
|
228
|
-
const assetsTarget = cmakeTarget + '_frontend_assets';
|
|
229
|
-
|
|
230
|
-
return `# ─── ${appName} ──────────────────────────────────────────────────────────────────
|
|
231
|
-
|
|
232
|
-
if(WPP_BUILD_EXAMPLES)
|
|
233
|
-
add_executable(${cmakeTarget} ${appDirRelative}/main.cpp)
|
|
234
|
-
target_link_libraries(${cmakeTarget} PRIVATE windowpp nlohmann_json::nlohmann_json)
|
|
235
|
-
if(WIN32 AND WEBVIEW2_INCLUDE_DIR)
|
|
236
|
-
target_include_directories(${cmakeTarget} PRIVATE \${WEBVIEW2_INCLUDE_DIR})
|
|
237
|
-
if(WEBVIEW2_STATIC)
|
|
238
|
-
target_link_libraries(${cmakeTarget} PRIVATE
|
|
239
|
-
\${CMAKE_CURRENT_SOURCE_DIR}/src/renderer/webview/x64/WebView2LoaderStatic.lib
|
|
240
|
-
version)
|
|
241
|
-
else()
|
|
242
|
-
target_link_libraries(${cmakeTarget} PRIVATE
|
|
243
|
-
\${CMAKE_CURRENT_SOURCE_DIR}/src/renderer/webview/x64/WebView2Loader.lib)
|
|
244
|
-
endif()
|
|
245
|
-
endif()
|
|
246
|
-
|
|
247
|
-
set(${generatedVar} \${CMAKE_BINARY_DIR}/generated_${cmakeTarget})
|
|
248
|
-
set(${embeddedCppVar} \${${generatedVar}}/embedded_assets.cpp)
|
|
249
|
-
set(${embeddedHVar} \${${generatedVar}}/embedded_assets.h)
|
|
250
|
-
|
|
251
|
-
add_custom_command(
|
|
252
|
-
OUTPUT \${${embeddedCppVar}} \${${embeddedHVar}}
|
|
253
|
-
COMMAND \${CMAKE_COMMAND} -E make_directory \${${generatedVar}}
|
|
254
|
-
COMMAND \${Python3_EXECUTABLE}
|
|
255
|
-
\${WPP_EMBED_SCRIPT}
|
|
256
|
-
\${CMAKE_CURRENT_SOURCE_DIR}/${appDirRelative}/frontend/dist
|
|
257
|
-
\${${embeddedCppVar}}
|
|
258
|
-
\${${embeddedHVar}}
|
|
259
|
-
DEPENDS \${CMAKE_CURRENT_SOURCE_DIR}/${appDirRelative}/frontend/dist/index.html
|
|
260
|
-
COMMENT "Embedding ${appName} frontend assets into C++ byte arrays..."
|
|
261
|
-
VERBATIM
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
set_source_files_properties(
|
|
265
|
-
\${${embeddedCppVar}}
|
|
266
|
-
\${${embeddedHVar}}
|
|
267
|
-
PROPERTIES GENERATED TRUE
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
add_custom_target(${assetsTarget} ALL
|
|
271
|
-
DEPENDS \${${embeddedCppVar}} \${${embeddedHVar}}
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
target_sources(${cmakeTarget} PRIVATE \${${embeddedCppVar}})
|
|
275
|
-
target_include_directories(${cmakeTarget} PRIVATE \${${generatedVar}})
|
|
276
|
-
target_compile_definitions(${cmakeTarget} PRIVATE WPP_EMBEDDED_ASSETS)
|
|
277
|
-
add_dependencies(${cmakeTarget} ${assetsTarget})
|
|
278
|
-
endif()
|
|
279
|
-
|
|
280
|
-
`;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
module.exports = { create };
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// cli/lib/create.js — WindowPP app scaffolding
|
|
3
|
+
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const { execSync } = require('child_process');
|
|
9
|
+
|
|
10
|
+
const TEXT_EXTENSIONS = new Set([
|
|
11
|
+
'.cpp', '.h', '.ts', '.tsx', '.js', '.json', '.html',
|
|
12
|
+
'.css', '.md', '.txt', '.cmake', '.sh', '.env',
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
function copyFile(src, dest, tokens) {
|
|
16
|
+
const ext = path.extname(src).toLowerCase();
|
|
17
|
+
if (TEXT_EXTENSIONS.has(ext)) {
|
|
18
|
+
let content = fs.readFileSync(src, 'utf8');
|
|
19
|
+
for (const [token, value] of Object.entries(tokens)) {
|
|
20
|
+
content = content.split(token).join(value);
|
|
21
|
+
}
|
|
22
|
+
fs.writeFileSync(dest, content, 'utf8');
|
|
23
|
+
} else {
|
|
24
|
+
fs.copyFileSync(src, dest);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function copyDir(src, dest, tokens) {
|
|
29
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
30
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
31
|
+
const srcPath = path.join(src, entry.name);
|
|
32
|
+
const destPath = path.join(dest, entry.name);
|
|
33
|
+
if (entry.isDirectory()) {
|
|
34
|
+
copyDir(srcPath, destPath, tokens);
|
|
35
|
+
} else {
|
|
36
|
+
copyFile(srcPath, destPath, tokens);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function create(name, options = {}) {
|
|
42
|
+
const {
|
|
43
|
+
template = 'solid',
|
|
44
|
+
outDir = process.cwd(),
|
|
45
|
+
installDeps = true,
|
|
46
|
+
} = options;
|
|
47
|
+
|
|
48
|
+
if (!name || !/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name)) {
|
|
49
|
+
console.error('Error: app name must start with a letter and contain only letters, digits, hyphens or underscores.');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const appDir = path.join(outDir, name);
|
|
54
|
+
if (fs.existsSync(appDir)) {
|
|
55
|
+
console.error(`Error: directory "${appDir}" already exists.`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const cliDir = path.resolve(__dirname, '..');
|
|
60
|
+
const templateDir = path.join(cliDir, 'templates', template);
|
|
61
|
+
if (!fs.existsSync(templateDir)) {
|
|
62
|
+
console.error(`Error: template "${template}" not found in ${path.join(cliDir, 'templates')}`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const cmakeTarget = name.replace(/-/g, '_');
|
|
67
|
+
const appTitle = name
|
|
68
|
+
.split(/[-_]/)
|
|
69
|
+
.map(w => w.charAt(0).toUpperCase() + w.slice(1))
|
|
70
|
+
.join(' ');
|
|
71
|
+
|
|
72
|
+
const tokens = {
|
|
73
|
+
'{{APP_NAME}}': name,
|
|
74
|
+
'{{APP_TITLE}}': appTitle,
|
|
75
|
+
'{{CMAKE_TARGET}}': cmakeTarget,
|
|
76
|
+
// Resolved at create-time so vite.config / API.ts can import from the
|
|
77
|
+
// installed framework's src/ tree via the @wpp alias.
|
|
78
|
+
'{{REPO_ROOT}}': path.join(cliDir, 'framework').replace(/\\/g, '/'),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
console.log(`\n=== WindowPP Create --- ${name} (template: ${template}) ===\n`);
|
|
82
|
+
console.log(`Scaffolding into: ${appDir}\n`);
|
|
83
|
+
|
|
84
|
+
copyDir(templateDir, appDir, tokens);
|
|
85
|
+
|
|
86
|
+
const frontendDir = path.join(appDir, 'frontend');
|
|
87
|
+
if (installDeps && fs.existsSync(frontendDir)) {
|
|
88
|
+
console.log('Installing frontend dependencies...');
|
|
89
|
+
execSync('npm install', { cwd: frontendDir, stdio: 'inherit' });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log(`\n-- Created "${name}"\n`);
|
|
93
|
+
console.log('Next steps:');
|
|
94
|
+
console.log(` cd ${name}`);
|
|
95
|
+
console.log(' windowpp dev\n');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = { create };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "windowpp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "WindowPP CLI — build, dev, and scaffold for WindowPP apps",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
18
18
|
"sync": "node scripts/sync-framework.js",
|
|
19
|
-
"
|
|
19
|
+
"sync:templates": "node scripts/sync-templates.js",
|
|
20
|
+
"sync:all": "node scripts/sync-framework.js && node scripts/sync-templates.js",
|
|
21
|
+
"prepublishOnly": "node scripts/sync-framework.js && node scripts/sync-templates.js"
|
|
20
22
|
},
|
|
21
23
|
"engines": {
|
|
22
24
|
"node": ">=18"
|
package/scripts/publish.js
CHANGED
|
@@ -47,6 +47,10 @@ console.log(`\n=== windowpp publish: ${pkg.version} → ${nextVersion} (${bumpTy
|
|
|
47
47
|
console.log('Syncing framework source...');
|
|
48
48
|
execSync('node scripts/sync-framework.js', { cwd: CLI_DIR, stdio: 'inherit' });
|
|
49
49
|
|
|
50
|
+
// ── Sync templates from monorepo apps ────────────────────────────────────────
|
|
51
|
+
console.log('\nSyncing templates...');
|
|
52
|
+
execSync('node scripts/sync-templates.js', { cwd: CLI_DIR, stdio: 'inherit' });
|
|
53
|
+
|
|
50
54
|
// ── Publish with the new version (without writing it yet) ─────────────────────
|
|
51
55
|
// Temporarily write the new version so npm publish picks it up, then revert on failure.
|
|
52
56
|
pkg.version = nextVersion;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// cli/scripts/sync-templates.js
|
|
3
|
+
//
|
|
4
|
+
// Syncs app directories from the monorepo into cli/templates/ as reusable
|
|
5
|
+
// project templates. Called manually or as part of the publish flow.
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// node cli/scripts/sync-templates.js — sync all templates
|
|
9
|
+
// node cli/scripts/sync-templates.js example — sync one by name
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
|
|
16
|
+
const SCRIPT_DIR = __dirname;
|
|
17
|
+
const CLI_DIR = path.join(SCRIPT_DIR, '..');
|
|
18
|
+
const REPO_ROOT = path.join(CLI_DIR, '..');
|
|
19
|
+
const TEMPLATES_DIR = path.join(CLI_DIR, 'templates');
|
|
20
|
+
const BASE_TEMPLATE = path.join(TEMPLATES_DIR, 'solid');
|
|
21
|
+
|
|
22
|
+
// ─── Template source configuration ───────────────────────────────────────────
|
|
23
|
+
//
|
|
24
|
+
// Each entry describes one template that can be used with `windowpp create`.
|
|
25
|
+
// To register a new app as a template, add an entry here and run this script.
|
|
26
|
+
//
|
|
27
|
+
// Fields:
|
|
28
|
+
// name — template dir name → cli/templates/<name>/
|
|
29
|
+
// source — source dir relative to repo root
|
|
30
|
+
// appName — concrete npm package name in source (replaced → {{APP_NAME}})
|
|
31
|
+
// cmakeTarget — concrete cmake target in source (replaced → {{CMAKE_TARGET}})
|
|
32
|
+
// appTitle — human-readable title in source (replaced → {{APP_TITLE}})
|
|
33
|
+
// useBase — paths taken from the solid base template instead of source
|
|
34
|
+
// (these need standalone-specific content not in the monorepo app)
|
|
35
|
+
// skip — relative paths / bare file names to skip when copying from source
|
|
36
|
+
|
|
37
|
+
const TEMPLATE_SOURCES = [
|
|
38
|
+
{
|
|
39
|
+
name: 'example',
|
|
40
|
+
source: 'example-app',
|
|
41
|
+
appName: 'windowpp-example',
|
|
42
|
+
cmakeTarget: 'wpp_example',
|
|
43
|
+
appTitle: 'WindowPP Example',
|
|
44
|
+
// Use the standalone-compatible versions from the solid base template
|
|
45
|
+
useBase: [
|
|
46
|
+
'CMakeLists.txt',
|
|
47
|
+
'package.json',
|
|
48
|
+
'frontend/vite.config.ts',
|
|
49
|
+
],
|
|
50
|
+
// Skip monorepo-specific / generated / lock files
|
|
51
|
+
skip: [
|
|
52
|
+
'build.js',
|
|
53
|
+
'dev.js',
|
|
54
|
+
'frontend/node_modules',
|
|
55
|
+
'frontend/dist',
|
|
56
|
+
'frontend/pnpm-lock.yaml',
|
|
57
|
+
'frontend/package-lock.json',
|
|
58
|
+
'frontend/.gitignore',
|
|
59
|
+
'frontend/README.md',
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
// ── To add more templates, copy an entry like this: ───────────────────────
|
|
63
|
+
// {
|
|
64
|
+
// name: 'icup',
|
|
65
|
+
// source: 'ICUP',
|
|
66
|
+
// appName: 'wpp-icup',
|
|
67
|
+
// cmakeTarget: 'wpp_icup',
|
|
68
|
+
// appTitle: 'WindowPP ICUP',
|
|
69
|
+
// useBase: ['CMakeLists.txt', 'package.json', 'frontend/vite.config.ts'],
|
|
70
|
+
// skip: ['build.js', 'dev.js', 'frontend/node_modules', 'frontend/dist'],
|
|
71
|
+
// },
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
// ─── File extensions that get text/token substitution ────────────────────────
|
|
75
|
+
const TEXT_EXTENSIONS = new Set([
|
|
76
|
+
'.cpp', '.h', '.ts', '.tsx', '.js', '.json', '.html',
|
|
77
|
+
'.css', '.md', '.txt', '.cmake', '.sh', '.env',
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
// ─── Build the list of [from, to] string substitution pairs for an entry ─────
|
|
81
|
+
//
|
|
82
|
+
// Transforms applied to all text files:
|
|
83
|
+
//
|
|
84
|
+
// 1. Concrete app name / cmake target / title → template tokens
|
|
85
|
+
//
|
|
86
|
+
// 2. TypeScript relative imports from frontend/src/ depth (3 levels up to src/):
|
|
87
|
+
// '../../../src/Foo' → '@wpp/Foo'
|
|
88
|
+
// Covered by the vite alias: '@wpp' → resolve(REPO_ROOT, 'src')
|
|
89
|
+
//
|
|
90
|
+
// 3. TypeScript relative imports from frontend/ depth (2 levels up):
|
|
91
|
+
// '../../src/Foo' → '@wpp/Foo'
|
|
92
|
+
//
|
|
93
|
+
// 4. C++ #include relative back to src/:
|
|
94
|
+
// "#include "../src/Foo/bar.h"" → "#include "Foo/bar.h""
|
|
95
|
+
// Works because CMakeLists adds target_include_directories(...PRIVATE "${WPP_FRAMEWORK_DIR}/src")
|
|
96
|
+
|
|
97
|
+
function buildTokens(entry) {
|
|
98
|
+
return [
|
|
99
|
+
// ── Name tokens (longer strings first to avoid partial matches) ─────
|
|
100
|
+
[entry.appName, '{{APP_NAME}}'],
|
|
101
|
+
[entry.cmakeTarget, '{{CMAKE_TARGET}}'],
|
|
102
|
+
[entry.appTitle, '{{APP_TITLE}}'],
|
|
103
|
+
|
|
104
|
+
// ── TypeScript/TSX: monorepo-relative imports from frontend/src/ ────
|
|
105
|
+
["'../../../src/", "'@wpp/"],
|
|
106
|
+
['"../../../src/', '"@wpp/'],
|
|
107
|
+
|
|
108
|
+
// ── TypeScript/TSX: from frontend/ level ───────────────────────────
|
|
109
|
+
["'../../src/", "'@wpp/"],
|
|
110
|
+
['"../../src/', '"@wpp/'],
|
|
111
|
+
|
|
112
|
+
// ── C++ includes: "../src/SubDir/file.h" → "SubDir/file.h" ─────────
|
|
113
|
+
// (CMakeLists adds ${WPP_FRAMEWORK_DIR}/src as a private include dir)
|
|
114
|
+
['"../src/', '"'],
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function applyTokens(content, tokens) {
|
|
119
|
+
for (const [from, to] of tokens) {
|
|
120
|
+
content = content.split(from).join(to);
|
|
121
|
+
}
|
|
122
|
+
return content;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ─── File copy helpers ───────────────────────────────────────────────────────
|
|
126
|
+
function copyFileTransformed(src, dest, tokens) {
|
|
127
|
+
const ext = path.extname(src).toLowerCase();
|
|
128
|
+
if (TEXT_EXTENSIONS.has(ext)) {
|
|
129
|
+
let content = fs.readFileSync(src, 'utf8');
|
|
130
|
+
content = applyTokens(content, tokens);
|
|
131
|
+
fs.writeFileSync(dest, content, 'utf8');
|
|
132
|
+
} else {
|
|
133
|
+
fs.copyFileSync(src, dest);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Copy a directory recursively, respecting a per-directory skip set.
|
|
138
|
+
// relPath tracks the path relative to the template root for skip matching.
|
|
139
|
+
function copySourceDir(src, dest, tokens, skipRelPaths, relPrefix) {
|
|
140
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
141
|
+
for (const dirent of fs.readdirSync(src, { withFileTypes: true })) {
|
|
142
|
+
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;
|
|
145
|
+
|
|
146
|
+
const srcPath = path.join(src, dirent.name);
|
|
147
|
+
const destPath = path.join(dest, dirent.name);
|
|
148
|
+
|
|
149
|
+
if (dirent.isDirectory()) {
|
|
150
|
+
copySourceDir(srcPath, destPath, tokens, skipRelPaths, relPath);
|
|
151
|
+
} else {
|
|
152
|
+
copyFileTransformed(srcPath, destPath, tokens);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ─── Sync a single template entry ────────────────────────────────────────────
|
|
158
|
+
function syncTemplate(entry) {
|
|
159
|
+
const srcDir = path.join(REPO_ROOT, entry.source);
|
|
160
|
+
const destDir = path.join(TEMPLATES_DIR, entry.name);
|
|
161
|
+
|
|
162
|
+
if (!fs.existsSync(srcDir)) {
|
|
163
|
+
console.warn(` ⚠ source not found: ${entry.source} — skipping`);
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Clear and recreate the destination template directory
|
|
168
|
+
if (fs.existsSync(destDir)) {
|
|
169
|
+
fs.rmSync(destDir, { recursive: true, force: true });
|
|
170
|
+
}
|
|
171
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
172
|
+
|
|
173
|
+
const tokens = buildTokens(entry);
|
|
174
|
+
const useBaseSet = new Set(entry.useBase ?? []);
|
|
175
|
+
const skipRelPaths = new Set(entry.skip ?? []);
|
|
176
|
+
|
|
177
|
+
// Also skip the "useBase" paths when copying from source
|
|
178
|
+
for (const rel of useBaseSet) skipRelPaths.add(rel);
|
|
179
|
+
|
|
180
|
+
let count = 0;
|
|
181
|
+
|
|
182
|
+
// 1. Overlay "base" files from cli/templates/solid/ (with this entry's tokens)
|
|
183
|
+
for (const rel of useBaseSet) {
|
|
184
|
+
const baseSrc = path.join(BASE_TEMPLATE, rel);
|
|
185
|
+
const baseDest = path.join(destDir, rel);
|
|
186
|
+
if (!fs.existsSync(baseSrc)) {
|
|
187
|
+
console.warn(` ⚠ base file not found: ${rel}`);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
fs.mkdirSync(path.dirname(baseDest), { recursive: true });
|
|
191
|
+
copyFileTransformed(baseSrc, baseDest, tokens);
|
|
192
|
+
count++;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 2. Copy everything else from the source app
|
|
196
|
+
copySourceDir(srcDir, destDir, tokens, skipRelPaths, '');
|
|
197
|
+
|
|
198
|
+
// Count total destination files
|
|
199
|
+
count = countFiles(destDir);
|
|
200
|
+
return count;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function countFiles(dir) {
|
|
204
|
+
let n = 0;
|
|
205
|
+
for (const dirent of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
206
|
+
if (dirent.isDirectory()) n += countFiles(path.join(dir, dirent.name));
|
|
207
|
+
else n++;
|
|
208
|
+
}
|
|
209
|
+
return n;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ─── Entry point ─────────────────────────────────────────────────────────────
|
|
213
|
+
function run() {
|
|
214
|
+
const filter = process.argv[2]; // optional: name of one template to sync
|
|
215
|
+
const targets = filter
|
|
216
|
+
? TEMPLATE_SOURCES.filter(e => e.name === filter)
|
|
217
|
+
: TEMPLATE_SOURCES;
|
|
218
|
+
|
|
219
|
+
if (filter && targets.length === 0) {
|
|
220
|
+
const names = TEMPLATE_SOURCES.map(e => e.name).join(', ');
|
|
221
|
+
console.error(`\nNo template named "${filter}". Available: ${names || '(none)'}\n`);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
console.log('\n=== windowpp: syncing templates ===\n');
|
|
226
|
+
|
|
227
|
+
let total = 0;
|
|
228
|
+
for (const entry of targets) {
|
|
229
|
+
process.stdout.write(` ${entry.source} → templates/${entry.name}/ ...`);
|
|
230
|
+
const n = syncTemplate(entry);
|
|
231
|
+
process.stdout.write(` ${n} file(s)\n`);
|
|
232
|
+
total += n;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
console.log(`\n✓ Synced ${targets.length} template(s) (${total} total files)\n`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
run();
|