pulse-js-framework 1.11.3 → 1.11.4
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/cli/analyze.js +21 -8
- package/cli/build.js +83 -56
- package/cli/dev.js +108 -94
- package/cli/docs-test.js +52 -33
- package/cli/index.js +81 -51
- package/cli/mobile.js +92 -40
- package/cli/release.js +64 -46
- package/cli/scaffold.js +14 -13
- package/compiler/lexer.js +55 -54
- package/compiler/parser/core.js +1 -0
- package/compiler/parser/state.js +6 -12
- package/compiler/parser/style.js +17 -20
- package/compiler/parser/view.js +1 -3
- package/compiler/preprocessor.js +124 -262
- package/compiler/sourcemap.js +10 -4
- package/compiler/transformer/expressions.js +122 -106
- package/compiler/transformer/index.js +2 -4
- package/compiler/transformer/style.js +74 -7
- package/compiler/transformer/view.js +86 -36
- package/loader/esbuild-plugin-server-components.js +209 -0
- package/loader/esbuild-plugin.js +41 -93
- package/loader/parcel-plugin.js +37 -97
- package/loader/rollup-plugin-server-components.js +30 -169
- package/loader/rollup-plugin.js +27 -78
- package/loader/shared.js +362 -0
- package/loader/swc-plugin.js +65 -82
- package/loader/vite-plugin-server-components.js +30 -171
- package/loader/vite-plugin.js +25 -10
- package/loader/webpack-loader-server-components.js +21 -134
- package/loader/webpack-loader.js +25 -80
- package/package.json +52 -12
- package/runtime/dom-selector.js +2 -1
- package/runtime/form.js +4 -3
- package/runtime/http.js +6 -1
- package/runtime/logger.js +44 -24
- package/runtime/router/utils.js +14 -7
- package/runtime/security.js +13 -1
- package/runtime/server-components/actions-server.js +23 -19
- package/runtime/server-components/error-sanitizer.js +18 -18
- package/runtime/server-components/security.js +41 -24
- package/runtime/ssr-preload.js +5 -3
- package/runtime/testing.js +759 -0
- package/runtime/utils.js +3 -2
- package/server/utils.js +15 -9
- package/sw/index.js +2 -0
- package/types/loaders.d.ts +1043 -0
- package/compiler/parser/_extract.js +0 -393
- package/loader/README.md +0 -509
package/cli/docs-test.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - .pulse file compilation check
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { readFileSync,
|
|
11
|
+
import { readFileSync, readdirSync, accessSync } from 'fs';
|
|
12
12
|
import { join, dirname, relative } from 'path';
|
|
13
13
|
import { fileURLToPath } from 'url';
|
|
14
14
|
import { createServer } from 'http';
|
|
@@ -27,18 +27,23 @@ const SKIP_DIRS = ['node_modules', '.git', 'dist', 'build', '.next', '.nuxt', 'c
|
|
|
27
27
|
* Collect all JS files recursively
|
|
28
28
|
*/
|
|
29
29
|
function collectJsFiles(dir, files = []) {
|
|
30
|
-
|
|
30
|
+
let entries;
|
|
31
|
+
try {
|
|
32
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
33
|
+
} catch (e) {
|
|
34
|
+
if (e.code === 'ENOENT') return files;
|
|
35
|
+
throw e;
|
|
36
|
+
}
|
|
31
37
|
|
|
32
|
-
for (const entry of
|
|
38
|
+
for (const entry of entries) {
|
|
33
39
|
// Skip common non-source directories
|
|
34
|
-
if (SKIP_DIRS.includes(entry)) continue;
|
|
40
|
+
if (SKIP_DIRS.includes(entry.name)) continue;
|
|
35
41
|
|
|
36
|
-
const fullPath = join(dir, entry);
|
|
37
|
-
const stat = statSync(fullPath);
|
|
42
|
+
const fullPath = join(dir, entry.name);
|
|
38
43
|
|
|
39
|
-
if (
|
|
44
|
+
if (entry.isDirectory()) {
|
|
40
45
|
collectJsFiles(fullPath, files);
|
|
41
|
-
} else if (entry.endsWith('.js')) {
|
|
46
|
+
} else if (entry.name.endsWith('.js')) {
|
|
42
47
|
files.push(fullPath);
|
|
43
48
|
}
|
|
44
49
|
}
|
|
@@ -50,18 +55,23 @@ function collectJsFiles(dir, files = []) {
|
|
|
50
55
|
* Collect all .pulse files recursively
|
|
51
56
|
*/
|
|
52
57
|
function collectPulseFiles(dir, files = []) {
|
|
53
|
-
|
|
58
|
+
let entries;
|
|
59
|
+
try {
|
|
60
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
61
|
+
} catch (e) {
|
|
62
|
+
if (e.code === 'ENOENT') return files;
|
|
63
|
+
throw e;
|
|
64
|
+
}
|
|
54
65
|
|
|
55
|
-
for (const entry of
|
|
66
|
+
for (const entry of entries) {
|
|
56
67
|
// Skip common non-source directories
|
|
57
|
-
if (SKIP_DIRS.includes(entry)) continue;
|
|
68
|
+
if (SKIP_DIRS.includes(entry.name)) continue;
|
|
58
69
|
|
|
59
|
-
const fullPath = join(dir, entry);
|
|
60
|
-
const stat = statSync(fullPath);
|
|
70
|
+
const fullPath = join(dir, entry.name);
|
|
61
71
|
|
|
62
|
-
if (
|
|
72
|
+
if (entry.isDirectory()) {
|
|
63
73
|
collectPulseFiles(fullPath, files);
|
|
64
|
-
} else if (entry.endsWith('.pulse')) {
|
|
74
|
+
} else if (entry.name.endsWith('.pulse')) {
|
|
65
75
|
files.push(fullPath);
|
|
66
76
|
}
|
|
67
77
|
}
|
|
@@ -194,10 +204,16 @@ function resolveImport(importPath, fromFile) {
|
|
|
194
204
|
|
|
195
205
|
// Add .js extension if missing
|
|
196
206
|
if (!resolved.endsWith('.js') && !resolved.endsWith('.pulse')) {
|
|
197
|
-
|
|
207
|
+
try {
|
|
208
|
+
accessSync(resolved + '.js');
|
|
198
209
|
resolved += '.js';
|
|
199
|
-
}
|
|
200
|
-
|
|
210
|
+
} catch (e) {
|
|
211
|
+
try {
|
|
212
|
+
accessSync(resolved + '/index.js');
|
|
213
|
+
resolved = join(resolved, 'index.js');
|
|
214
|
+
} catch (_e) {
|
|
215
|
+
// Neither exists, return as-is
|
|
216
|
+
}
|
|
201
217
|
}
|
|
202
218
|
}
|
|
203
219
|
|
|
@@ -224,7 +240,9 @@ function validateImports(filePath) {
|
|
|
224
240
|
// Skip pulse-js-framework imports
|
|
225
241
|
if (importPath.includes('pulse-js-framework')) continue;
|
|
226
242
|
|
|
227
|
-
|
|
243
|
+
try {
|
|
244
|
+
accessSync(resolved);
|
|
245
|
+
} catch (_e) {
|
|
228
246
|
errors.push({
|
|
229
247
|
file: filePath,
|
|
230
248
|
importPath,
|
|
@@ -310,21 +328,22 @@ function startTestServer(port = 0) {
|
|
|
310
328
|
|
|
311
329
|
const filePath = join(docsDir, pathname);
|
|
312
330
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
'css': 'text/css',
|
|
320
|
-
'json': 'application/json'
|
|
321
|
-
};
|
|
322
|
-
res.writeHead(200, { 'Content-Type': mimeTypes[ext] || 'text/plain' });
|
|
323
|
-
res.end(content);
|
|
324
|
-
} else {
|
|
325
|
-
res.writeHead(404);
|
|
326
|
-
res.end('Not Found');
|
|
331
|
+
let content;
|
|
332
|
+
try {
|
|
333
|
+
content = readFileSync(filePath);
|
|
334
|
+
} catch (err) {
|
|
335
|
+
if (err.code === 'ENOENT' || err.code === 'EISDIR') { res.writeHead(404); res.end('Not Found'); return; }
|
|
336
|
+
throw err;
|
|
327
337
|
}
|
|
338
|
+
const ext = pathname.split('.').pop();
|
|
339
|
+
const mimeTypes = {
|
|
340
|
+
'html': 'text/html',
|
|
341
|
+
'js': 'application/javascript',
|
|
342
|
+
'css': 'text/css',
|
|
343
|
+
'json': 'application/json'
|
|
344
|
+
};
|
|
345
|
+
res.writeHead(200, { 'Content-Type': mimeTypes[ext] || 'text/plain' });
|
|
346
|
+
res.end(content);
|
|
328
347
|
});
|
|
329
348
|
|
|
330
349
|
// Disable keep-alive timeout
|
package/cli/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import { dirname, join, resolve, relative } from 'path';
|
|
9
|
-
import {
|
|
9
|
+
import { mkdirSync, writeFileSync, readFileSync, readdirSync, watch, accessSync } from 'fs';
|
|
10
10
|
import { log } from './logger.js';
|
|
11
11
|
import { findPulseFiles, parseArgs } from './utils/file-utils.js';
|
|
12
12
|
import { runHelp } from './help.js';
|
|
@@ -218,7 +218,9 @@ function copyExampleTemplate(templateName, projectPath, projectName) {
|
|
|
218
218
|
const examplesDir = join(__dirname, '..', 'examples');
|
|
219
219
|
const templateDir = join(examplesDir, templateName);
|
|
220
220
|
|
|
221
|
-
|
|
221
|
+
try {
|
|
222
|
+
accessSync(templateDir);
|
|
223
|
+
} catch (e) {
|
|
222
224
|
throw new Error(`Template "${templateName}" not found at ${templateDir}`);
|
|
223
225
|
}
|
|
224
226
|
|
|
@@ -226,9 +228,7 @@ function copyExampleTemplate(templateName, projectPath, projectName) {
|
|
|
226
228
|
* Recursively copy directory, transforming JS files
|
|
227
229
|
*/
|
|
228
230
|
function copyDir(src, dest) {
|
|
229
|
-
|
|
230
|
-
mkdirSync(dest, { recursive: true });
|
|
231
|
-
}
|
|
231
|
+
mkdirSync(dest, { recursive: true });
|
|
232
232
|
|
|
233
233
|
const entries = readdirSync(src, { withFileTypes: true });
|
|
234
234
|
|
|
@@ -272,17 +272,24 @@ function copyExampleTemplate(templateName, projectPath, projectName) {
|
|
|
272
272
|
|
|
273
273
|
// Copy src directory
|
|
274
274
|
const srcDir = join(templateDir, 'src');
|
|
275
|
-
|
|
275
|
+
try {
|
|
276
|
+
readdirSync(srcDir);
|
|
276
277
|
copyDir(srcDir, join(projectPath, 'src'));
|
|
278
|
+
} catch (e) {
|
|
279
|
+
if (e.code !== 'ENOENT') throw e;
|
|
280
|
+
// No src directory in template, skip
|
|
277
281
|
}
|
|
278
282
|
|
|
279
283
|
// Copy index.html if exists
|
|
280
284
|
const indexHtml = join(templateDir, 'index.html');
|
|
281
|
-
|
|
285
|
+
try {
|
|
282
286
|
let content = readFileSync(indexHtml, 'utf-8');
|
|
283
287
|
// Update title to project name
|
|
284
288
|
content = content.replace(/<title>[^<]*<\/title>/, `<title>${projectName}</title>`);
|
|
285
289
|
writeFileSync(join(projectPath, 'index.html'), content);
|
|
290
|
+
} catch (e) {
|
|
291
|
+
if (e.code !== 'ENOENT') throw e;
|
|
292
|
+
// No index.html in template, skip
|
|
286
293
|
}
|
|
287
294
|
|
|
288
295
|
return true;
|
|
@@ -312,9 +319,13 @@ async function createProject(args) {
|
|
|
312
319
|
|
|
313
320
|
const projectPath = resolve(process.cwd(), projectName);
|
|
314
321
|
|
|
315
|
-
|
|
322
|
+
try {
|
|
323
|
+
accessSync(projectPath);
|
|
316
324
|
log.error(`Directory "${projectName}" already exists.`);
|
|
317
325
|
process.exit(1);
|
|
326
|
+
} catch (e) {
|
|
327
|
+
if (e.code !== 'ENOENT') throw e;
|
|
328
|
+
// Path doesn't exist, safe to create
|
|
318
329
|
}
|
|
319
330
|
|
|
320
331
|
const useTypescript = options.typescript || options.ts || false;
|
|
@@ -675,34 +686,30 @@ async function initProject(args) {
|
|
|
675
686
|
log.info(`Initializing Pulse project in current directory${useTypescript ? ' (TypeScript)' : ''}...`);
|
|
676
687
|
|
|
677
688
|
// Create src directory if it doesn't exist
|
|
678
|
-
|
|
679
|
-
mkdirSync(join(cwd, 'src'));
|
|
680
|
-
}
|
|
689
|
+
mkdirSync(join(cwd, 'src'), { recursive: true });
|
|
681
690
|
|
|
682
691
|
// Create public directory if it doesn't exist
|
|
683
|
-
|
|
684
|
-
mkdirSync(join(cwd, 'public'));
|
|
685
|
-
}
|
|
692
|
+
mkdirSync(join(cwd, 'public'), { recursive: true });
|
|
686
693
|
|
|
687
694
|
// Check for existing package.json
|
|
688
695
|
const pkgPath = join(cwd, 'package.json');
|
|
689
696
|
let pkg = {};
|
|
690
697
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
}
|
|
698
|
+
try {
|
|
699
|
+
const pkgContent = readFileSync(pkgPath, 'utf-8');
|
|
700
|
+
if (!pkgContent.trim()) {
|
|
701
|
+
log.warn('Existing package.json is empty, creating new one.');
|
|
702
|
+
} else {
|
|
703
|
+
pkg = JSON.parse(pkgContent);
|
|
704
|
+
log.info('Found existing package.json, merging...');
|
|
705
|
+
}
|
|
706
|
+
} catch (e) {
|
|
707
|
+
if (e.code === 'ENOENT') {
|
|
708
|
+
// No existing package.json, will create new one
|
|
709
|
+
} else if (e instanceof SyntaxError) {
|
|
710
|
+
log.warn(`Invalid JSON in package.json: ${e.message}. Creating new one.`);
|
|
711
|
+
} else {
|
|
712
|
+
log.warn(`Could not read package.json: ${e.message}. Creating new one.`);
|
|
706
713
|
}
|
|
707
714
|
}
|
|
708
715
|
|
|
@@ -742,7 +749,7 @@ async function initProject(args) {
|
|
|
742
749
|
const viteConfigExt = useTypescript ? 'ts' : 'js';
|
|
743
750
|
const viteConfigPath = join(cwd, `vite.config.${viteConfigExt}`);
|
|
744
751
|
|
|
745
|
-
|
|
752
|
+
{
|
|
746
753
|
const viteConfig = `import { defineConfig } from 'vite';
|
|
747
754
|
import pulse from 'pulse-js-framework/vite';
|
|
748
755
|
|
|
@@ -750,12 +757,17 @@ export default defineConfig({
|
|
|
750
757
|
plugins: [pulse()]
|
|
751
758
|
});
|
|
752
759
|
`;
|
|
753
|
-
|
|
754
|
-
|
|
760
|
+
// Use atomic wx flag to avoid TOCTOU race (no existsSync check needed)
|
|
761
|
+
try {
|
|
762
|
+
writeFileSync(viteConfigPath, viteConfig, { flag: 'wx' });
|
|
763
|
+
log.success(`Created vite.config.${viteConfigExt}`);
|
|
764
|
+
} catch (e) {
|
|
765
|
+
if (e.code !== 'EEXIST') throw e;
|
|
766
|
+
}
|
|
755
767
|
}
|
|
756
768
|
|
|
757
|
-
// Create tsconfig if TypeScript
|
|
758
|
-
if (useTypescript
|
|
769
|
+
// Create tsconfig if TypeScript (use wx flag to avoid TOCTOU race)
|
|
770
|
+
if (useTypescript) {
|
|
759
771
|
const tsConfig = {
|
|
760
772
|
compilerOptions: {
|
|
761
773
|
target: 'ES2022',
|
|
@@ -777,13 +789,17 @@ export default defineConfig({
|
|
|
777
789
|
exclude: ['node_modules', 'dist']
|
|
778
790
|
};
|
|
779
791
|
|
|
780
|
-
|
|
781
|
-
|
|
792
|
+
try {
|
|
793
|
+
writeFileSync(join(cwd, 'tsconfig.json'), JSON.stringify(tsConfig, null, 2), { flag: 'wx' });
|
|
794
|
+
log.success('Created tsconfig.json');
|
|
795
|
+
} catch (e) {
|
|
796
|
+
if (e.code !== 'EEXIST') throw e;
|
|
797
|
+
}
|
|
782
798
|
}
|
|
783
799
|
|
|
784
800
|
// Create index.html if it doesn't exist
|
|
785
801
|
const indexHtmlPath = join(cwd, 'index.html');
|
|
786
|
-
|
|
802
|
+
{
|
|
787
803
|
const mainExt = useTypescript ? 'ts' : 'js';
|
|
788
804
|
const indexHtml = `<!DOCTYPE html>
|
|
789
805
|
<html lang="en">
|
|
@@ -798,14 +814,18 @@ export default defineConfig({
|
|
|
798
814
|
</body>
|
|
799
815
|
</html>
|
|
800
816
|
`;
|
|
801
|
-
|
|
802
|
-
|
|
817
|
+
try {
|
|
818
|
+
writeFileSync(indexHtmlPath, indexHtml, { flag: 'wx' });
|
|
819
|
+
log.success('Created index.html');
|
|
820
|
+
} catch (e) {
|
|
821
|
+
if (e.code !== 'EEXIST') throw e;
|
|
822
|
+
}
|
|
803
823
|
}
|
|
804
824
|
|
|
805
825
|
// Create main entry file if it doesn't exist
|
|
806
826
|
const mainExt = useTypescript ? 'ts' : 'js';
|
|
807
827
|
const mainPath = join(cwd, 'src', `main.${mainExt}`);
|
|
808
|
-
|
|
828
|
+
{
|
|
809
829
|
const mainContent = useTypescript
|
|
810
830
|
? `import App from './App.pulse';
|
|
811
831
|
|
|
@@ -821,13 +841,17 @@ if (import.meta.hot) {
|
|
|
821
841
|
|
|
822
842
|
App.mount('#app');
|
|
823
843
|
`;
|
|
824
|
-
|
|
825
|
-
|
|
844
|
+
try {
|
|
845
|
+
writeFileSync(mainPath, mainContent, { flag: 'wx' });
|
|
846
|
+
log.success(`Created src/main.${mainExt}`);
|
|
847
|
+
} catch (e) {
|
|
848
|
+
if (e.code !== 'EEXIST') throw e;
|
|
849
|
+
}
|
|
826
850
|
}
|
|
827
851
|
|
|
828
852
|
// Create App.pulse if it doesn't exist
|
|
829
853
|
const appPulsePath = join(cwd, 'src', 'App.pulse');
|
|
830
|
-
|
|
854
|
+
{
|
|
831
855
|
const appPulse = `@page App
|
|
832
856
|
|
|
833
857
|
// Welcome to Pulse Framework!
|
|
@@ -970,20 +994,28 @@ style {
|
|
|
970
994
|
}
|
|
971
995
|
}
|
|
972
996
|
`;
|
|
973
|
-
|
|
974
|
-
|
|
997
|
+
try {
|
|
998
|
+
writeFileSync(appPulsePath, appPulse, { flag: 'wx' });
|
|
999
|
+
log.success('Created src/App.pulse');
|
|
1000
|
+
} catch (e) {
|
|
1001
|
+
if (e.code !== 'EEXIST') throw e;
|
|
1002
|
+
}
|
|
975
1003
|
}
|
|
976
1004
|
|
|
977
1005
|
// Create .gitignore if it doesn't exist
|
|
978
1006
|
const gitignorePath = join(cwd, '.gitignore');
|
|
979
|
-
|
|
1007
|
+
{
|
|
980
1008
|
const gitignore = `node_modules
|
|
981
1009
|
dist
|
|
982
1010
|
.DS_Store
|
|
983
1011
|
*.local
|
|
984
1012
|
${useTypescript ? '*.tsbuildinfo\n' : ''}`;
|
|
985
|
-
|
|
986
|
-
|
|
1013
|
+
try {
|
|
1014
|
+
writeFileSync(gitignorePath, gitignore, { flag: 'wx' });
|
|
1015
|
+
log.success('Created .gitignore');
|
|
1016
|
+
} catch (e) {
|
|
1017
|
+
if (e.code !== 'EEXIST') throw e;
|
|
1018
|
+
}
|
|
987
1019
|
}
|
|
988
1020
|
|
|
989
1021
|
log.info(`
|
|
@@ -1283,9 +1315,7 @@ async function compileFiles(args) {
|
|
|
1283
1315
|
if (result.success) {
|
|
1284
1316
|
// Ensure output directory exists
|
|
1285
1317
|
const outDir = dirname(outputFile);
|
|
1286
|
-
|
|
1287
|
-
mkdirSync(outDir, { recursive: true });
|
|
1288
|
-
}
|
|
1318
|
+
mkdirSync(outDir, { recursive: true });
|
|
1289
1319
|
writeFileSync(outputFile, result.code);
|
|
1290
1320
|
log.info(`Compiled: ${relPath} -> ${relOutput}`);
|
|
1291
1321
|
writtenCount++;
|
package/cli/mobile.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Zero-dependency mobile platform for Pulse Framework
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { mkdirSync, readFileSync, writeFileSync, cpSync, readdirSync } from 'fs';
|
|
7
7
|
import { join, dirname } from 'path';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
import { execSync } from 'child_process';
|
|
@@ -19,7 +19,7 @@ const CONFIG_FILE = 'pulse.mobile.json';
|
|
|
19
19
|
// ============================================================================
|
|
20
20
|
|
|
21
21
|
/** Create directory if it doesn't exist */
|
|
22
|
-
const mkdirp = (path) =>
|
|
22
|
+
const mkdirp = (path) => mkdirSync(path, { recursive: true });
|
|
23
23
|
|
|
24
24
|
/** Create multiple directories at once */
|
|
25
25
|
const mkdirs = (base, dirs) => dirs.forEach(d => mkdirp(join(base, d)));
|
|
@@ -83,24 +83,31 @@ async function initMobile(args) {
|
|
|
83
83
|
console.log('Initializing Pulse Mobile...\n');
|
|
84
84
|
|
|
85
85
|
// Check if dist exists
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
try {
|
|
87
|
+
readdirSync(join(root, 'dist'));
|
|
88
|
+
} catch (e) {
|
|
89
|
+
if (e.code === 'ENOENT') {
|
|
90
|
+
console.warn('Warning: No dist/ folder found. Run "pulse build" first.\n');
|
|
91
|
+
} else {
|
|
92
|
+
throw e;
|
|
93
|
+
}
|
|
88
94
|
}
|
|
89
95
|
|
|
90
96
|
// Create mobile directory
|
|
91
|
-
|
|
92
|
-
mkdirSync(mobileDir, { recursive: true });
|
|
93
|
-
}
|
|
97
|
+
mkdirSync(mobileDir, { recursive: true });
|
|
94
98
|
|
|
95
99
|
// Read project name from package.json
|
|
96
100
|
let projectName = 'PulseApp';
|
|
97
101
|
let packageId = 'com.pulse.app';
|
|
98
102
|
|
|
99
103
|
const packageJsonPath = join(root, 'package.json');
|
|
100
|
-
|
|
104
|
+
try {
|
|
101
105
|
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
102
106
|
projectName = toPascalCase(pkg.name || 'PulseApp');
|
|
103
107
|
packageId = `com.pulse.${pkg.name?.toLowerCase().replace(/[^a-z0-9]/g, '') || 'app'}`;
|
|
108
|
+
} catch (e) {
|
|
109
|
+
if (e.code !== 'ENOENT') throw e;
|
|
110
|
+
// No package.json, use defaults
|
|
104
111
|
}
|
|
105
112
|
|
|
106
113
|
// Create default config
|
|
@@ -122,54 +129,70 @@ async function initMobile(args) {
|
|
|
122
129
|
};
|
|
123
130
|
|
|
124
131
|
// Write config if not exists
|
|
125
|
-
|
|
126
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
132
|
+
try {
|
|
133
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2), { flag: 'wx' });
|
|
127
134
|
console.log(`Created ${CONFIG_FILE}`);
|
|
128
|
-
}
|
|
129
|
-
|
|
135
|
+
} catch (err) {
|
|
136
|
+
if (err.code === 'EEXIST') {
|
|
137
|
+
console.log(`${CONFIG_FILE} already exists, skipping...`);
|
|
138
|
+
} else {
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
130
141
|
}
|
|
131
142
|
|
|
132
143
|
// Copy Android template
|
|
133
144
|
const androidTemplateDir = join(__dirname, '..', 'mobile', 'templates', 'android');
|
|
134
145
|
const androidDir = join(mobileDir, 'android');
|
|
135
146
|
|
|
136
|
-
|
|
137
|
-
|
|
147
|
+
try {
|
|
148
|
+
readdirSync(androidDir);
|
|
149
|
+
console.log('Android directory exists, skipping...');
|
|
150
|
+
} catch (e) {
|
|
151
|
+
if (e.code !== 'ENOENT') throw e;
|
|
152
|
+
try {
|
|
153
|
+
readdirSync(androidTemplateDir);
|
|
138
154
|
console.log('Initializing Android project...');
|
|
139
155
|
copyAndProcessTemplate(androidTemplateDir, androidDir, config);
|
|
140
156
|
console.log('Android project created.');
|
|
141
|
-
}
|
|
157
|
+
} catch (te) {
|
|
158
|
+
if (te.code !== 'ENOENT') throw te;
|
|
142
159
|
console.log('Creating Android project structure...');
|
|
143
160
|
createAndroidProject(androidDir, config);
|
|
144
161
|
console.log('Android project created.');
|
|
145
162
|
}
|
|
146
|
-
} else {
|
|
147
|
-
console.log('Android directory exists, skipping...');
|
|
148
163
|
}
|
|
149
164
|
|
|
150
165
|
// Copy iOS template
|
|
151
166
|
const iosTemplateDir = join(__dirname, '..', 'mobile', 'templates', 'ios');
|
|
152
167
|
const iosDir = join(mobileDir, 'ios');
|
|
153
168
|
|
|
154
|
-
|
|
155
|
-
|
|
169
|
+
try {
|
|
170
|
+
readdirSync(iosDir);
|
|
171
|
+
console.log('iOS directory exists, skipping...');
|
|
172
|
+
} catch (e) {
|
|
173
|
+
if (e.code !== 'ENOENT') throw e;
|
|
174
|
+
try {
|
|
175
|
+
readdirSync(iosTemplateDir);
|
|
156
176
|
console.log('Initializing iOS project...');
|
|
157
177
|
copyAndProcessTemplate(iosTemplateDir, iosDir, config);
|
|
158
178
|
console.log('iOS project created.');
|
|
159
|
-
}
|
|
179
|
+
} catch (te) {
|
|
180
|
+
if (te.code !== 'ENOENT') throw te;
|
|
160
181
|
console.log('Creating iOS project structure...');
|
|
161
182
|
createIOSProject(iosDir, config);
|
|
162
183
|
console.log('iOS project created.');
|
|
163
184
|
}
|
|
164
|
-
} else {
|
|
165
|
-
console.log('iOS directory exists, skipping...');
|
|
166
185
|
}
|
|
167
186
|
|
|
168
187
|
// Copy bridge script to dist if it exists
|
|
169
188
|
const bridgeSource = join(__dirname, '..', 'mobile', 'bridge', 'pulse-native.js');
|
|
170
|
-
|
|
189
|
+
try {
|
|
190
|
+
readdirSync(join(root, 'dist'));
|
|
171
191
|
cpSync(bridgeSource, join(root, 'dist', 'pulse-native.js'));
|
|
172
192
|
console.log('Native bridge script copied to dist/');
|
|
193
|
+
} catch (e) {
|
|
194
|
+
if (e.code !== 'ENOENT') throw e;
|
|
195
|
+
// dist or bridge source doesn't exist, skip
|
|
173
196
|
}
|
|
174
197
|
|
|
175
198
|
console.log(`
|
|
@@ -202,7 +225,10 @@ async function buildMobile(args) {
|
|
|
202
225
|
const config = loadConfig(root);
|
|
203
226
|
|
|
204
227
|
// First, ensure web build exists
|
|
205
|
-
|
|
228
|
+
try {
|
|
229
|
+
readdirSync(join(root, config.webDir));
|
|
230
|
+
} catch (e) {
|
|
231
|
+
if (e.code !== 'ENOENT') throw e;
|
|
206
232
|
console.log('Building web app first...');
|
|
207
233
|
const { buildProject } = await import('./build.js');
|
|
208
234
|
await buildProject([]);
|
|
@@ -268,9 +294,14 @@ async function syncAssets(args) {
|
|
|
268
294
|
async function syncWebAssets(root, platform, config) {
|
|
269
295
|
const webDir = join(root, config.webDir);
|
|
270
296
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
297
|
+
try {
|
|
298
|
+
readdirSync(webDir);
|
|
299
|
+
} catch (e) {
|
|
300
|
+
if (e.code === 'ENOENT' || e.code === 'ENOTDIR') {
|
|
301
|
+
console.error(`Web directory "${config.webDir}" not found. Run "pulse build" first.`);
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
304
|
+
throw e;
|
|
274
305
|
}
|
|
275
306
|
|
|
276
307
|
let assetsDir;
|
|
@@ -288,8 +319,11 @@ async function syncWebAssets(root, platform, config) {
|
|
|
288
319
|
|
|
289
320
|
// Copy native bridge
|
|
290
321
|
const bridgeSource = join(__dirname, '..', 'mobile', 'bridge', 'pulse-native.js');
|
|
291
|
-
|
|
322
|
+
try {
|
|
292
323
|
cpSync(bridgeSource, join(assetsDir, 'pulse-native.js'));
|
|
324
|
+
} catch (e) {
|
|
325
|
+
if (e.code !== 'ENOENT') throw e;
|
|
326
|
+
// Bridge source not found, skip
|
|
293
327
|
}
|
|
294
328
|
|
|
295
329
|
console.log(`Web assets synced to ${platform}`);
|
|
@@ -301,9 +335,14 @@ async function syncWebAssets(root, platform, config) {
|
|
|
301
335
|
async function buildAndroid(root, config) {
|
|
302
336
|
const androidDir = join(root, MOBILE_DIR, 'android');
|
|
303
337
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
338
|
+
try {
|
|
339
|
+
readdirSync(androidDir);
|
|
340
|
+
} catch (e) {
|
|
341
|
+
if (e.code === 'ENOENT' || e.code === 'ENOTDIR') {
|
|
342
|
+
console.error('Android project not found. Run "pulse mobile init" first.');
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
throw e;
|
|
307
346
|
}
|
|
308
347
|
|
|
309
348
|
console.log('Building Android APK...\n');
|
|
@@ -341,9 +380,14 @@ async function buildIOS(root, config) {
|
|
|
341
380
|
|
|
342
381
|
const iosDir = join(root, MOBILE_DIR, 'ios');
|
|
343
382
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
383
|
+
try {
|
|
384
|
+
readdirSync(iosDir);
|
|
385
|
+
} catch (e) {
|
|
386
|
+
if (e.code === 'ENOENT' || e.code === 'ENOTDIR') {
|
|
387
|
+
console.error('iOS project not found. Run "pulse mobile init" first.');
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
throw e;
|
|
347
391
|
}
|
|
348
392
|
|
|
349
393
|
console.log('Building iOS app...\n');
|
|
@@ -443,17 +487,25 @@ async function runIOS(root, config) {
|
|
|
443
487
|
function loadConfig(root) {
|
|
444
488
|
const configPath = join(root, CONFIG_FILE);
|
|
445
489
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
490
|
+
try {
|
|
491
|
+
return JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
492
|
+
} catch (e) {
|
|
493
|
+
if (e.code === 'ENOENT') {
|
|
494
|
+
console.error(`No ${CONFIG_FILE} found. Run "pulse mobile init" first.`);
|
|
495
|
+
process.exit(1);
|
|
496
|
+
}
|
|
497
|
+
throw e;
|
|
449
498
|
}
|
|
450
|
-
|
|
451
|
-
return JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
452
499
|
}
|
|
453
500
|
|
|
454
501
|
/** Copy and process template files */
|
|
455
502
|
function copyAndProcessTemplate(src, dest, config) {
|
|
456
|
-
|
|
503
|
+
try {
|
|
504
|
+
readdirSync(src);
|
|
505
|
+
} catch (e) {
|
|
506
|
+
if (e.code === 'ENOENT') return;
|
|
507
|
+
throw e;
|
|
508
|
+
}
|
|
457
509
|
mkdirp(dest);
|
|
458
510
|
|
|
459
511
|
for (const file of readdirSync(src, { withFileTypes: true })) {
|