create-switch-framework-app 0.1.0

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.
Files changed (207) hide show
  1. package/README.md +25 -0
  2. package/bin/create-switch-framework-app.js +502 -0
  3. package/package.json +31 -0
  4. package/templates/electron/base/app/(tabs)/+not-found.js +158 -0
  5. package/templates/electron/base/app/(tabs)/_layout.js +125 -0
  6. package/templates/electron/base/app/(tabs)/explore.js +44 -0
  7. package/templates/electron/base/app/(tabs)/index.js +218 -0
  8. package/templates/electron/base/app/(tabs)/register.js +12 -0
  9. package/templates/electron/base/app/+not-found.js +158 -0
  10. package/templates/electron/base/app/_layout.js +50 -0
  11. package/templates/electron/base/app/index.js +265 -0
  12. package/templates/electron/base/app/register.js +12 -0
  13. package/templates/electron/base/assets/files/Profile.jpg +0 -0
  14. package/templates/electron/base/assets/files/fingerprint-icon.svg +4 -0
  15. package/templates/electron/base/assets/fonts/DMSans-Black.ttf +0 -0
  16. package/templates/electron/base/assets/fonts/DMSans-BlackItalic.ttf +0 -0
  17. package/templates/electron/base/assets/fonts/DMSans-Bold.ttf +0 -0
  18. package/templates/electron/base/assets/fonts/DMSans-BoldItalic.ttf +0 -0
  19. package/templates/electron/base/assets/fonts/DMSans-ExtraBold.ttf +0 -0
  20. package/templates/electron/base/assets/fonts/DMSans-ExtraBoldItalic.ttf +0 -0
  21. package/templates/electron/base/assets/fonts/DMSans-ExtraLight.ttf +0 -0
  22. package/templates/electron/base/assets/fonts/DMSans-ExtraLightItalic.ttf +0 -0
  23. package/templates/electron/base/assets/fonts/DMSans-Italic.ttf +0 -0
  24. package/templates/electron/base/assets/fonts/DMSans-Light.ttf +0 -0
  25. package/templates/electron/base/assets/fonts/DMSans-LightItalic.ttf +0 -0
  26. package/templates/electron/base/assets/fonts/DMSans-Medium.ttf +0 -0
  27. package/templates/electron/base/assets/fonts/DMSans-MediumItalic.ttf +0 -0
  28. package/templates/electron/base/assets/fonts/DMSans-Regular.ttf +0 -0
  29. package/templates/electron/base/assets/fonts/DMSans-SemiBold.ttf +0 -0
  30. package/templates/electron/base/assets/fonts/DMSans-SemiBoldItalic.ttf +0 -0
  31. package/templates/electron/base/assets/fonts/DMSans-Thin.ttf +0 -0
  32. package/templates/electron/base/assets/fonts/DMSans-ThinItalic.ttf +0 -0
  33. package/templates/electron/base/assets/fonts/DMSans_18pt-Black.ttf +0 -0
  34. package/templates/electron/base/assets/fonts/DMSans_18pt-BlackItalic.ttf +0 -0
  35. package/templates/electron/base/assets/fonts/DMSans_18pt-Bold.ttf +0 -0
  36. package/templates/electron/base/assets/fonts/DMSans_18pt-BoldItalic.ttf +0 -0
  37. package/templates/electron/base/assets/fonts/DMSans_18pt-ExtraBold.ttf +0 -0
  38. package/templates/electron/base/assets/fonts/DMSans_18pt-ExtraBoldItalic.ttf +0 -0
  39. package/templates/electron/base/assets/fonts/DMSans_18pt-ExtraLight.ttf +0 -0
  40. package/templates/electron/base/assets/fonts/DMSans_18pt-ExtraLightItalic.ttf +0 -0
  41. package/templates/electron/base/assets/fonts/DMSans_18pt-Italic.ttf +0 -0
  42. package/templates/electron/base/assets/fonts/DMSans_18pt-Light.ttf +0 -0
  43. package/templates/electron/base/assets/fonts/DMSans_18pt-LightItalic.ttf +0 -0
  44. package/templates/electron/base/assets/fonts/DMSans_18pt-Medium.ttf +0 -0
  45. package/templates/electron/base/assets/fonts/DMSans_18pt-MediumItalic.ttf +0 -0
  46. package/templates/electron/base/assets/fonts/DMSans_18pt-Regular.ttf +0 -0
  47. package/templates/electron/base/assets/fonts/DMSans_18pt-SemiBold.ttf +0 -0
  48. package/templates/electron/base/assets/fonts/DMSans_18pt-SemiBoldItalic.ttf +0 -0
  49. package/templates/electron/base/assets/fonts/DMSans_18pt-Thin.ttf +0 -0
  50. package/templates/electron/base/assets/fonts/DMSans_18pt-ThinItalic.ttf +0 -0
  51. package/templates/electron/base/assets/fonts/DMSans_24pt-Black.ttf +0 -0
  52. package/templates/electron/base/assets/fonts/DMSans_24pt-BlackItalic.ttf +0 -0
  53. package/templates/electron/base/assets/fonts/DMSans_24pt-Bold.ttf +0 -0
  54. package/templates/electron/base/assets/fonts/DMSans_24pt-BoldItalic.ttf +0 -0
  55. package/templates/electron/base/assets/fonts/DMSans_24pt-ExtraBold.ttf +0 -0
  56. package/templates/electron/base/assets/fonts/DMSans_24pt-ExtraBoldItalic.ttf +0 -0
  57. package/templates/electron/base/assets/fonts/DMSans_24pt-ExtraLight.ttf +0 -0
  58. package/templates/electron/base/assets/fonts/DMSans_24pt-ExtraLightItalic.ttf +0 -0
  59. package/templates/electron/base/assets/fonts/DMSans_24pt-Italic.ttf +0 -0
  60. package/templates/electron/base/assets/fonts/DMSans_24pt-Light.ttf +0 -0
  61. package/templates/electron/base/assets/fonts/DMSans_24pt-LightItalic.ttf +0 -0
  62. package/templates/electron/base/assets/fonts/DMSans_24pt-Medium.ttf +0 -0
  63. package/templates/electron/base/assets/fonts/DMSans_24pt-MediumItalic.ttf +0 -0
  64. package/templates/electron/base/assets/fonts/DMSans_24pt-Regular.ttf +0 -0
  65. package/templates/electron/base/assets/fonts/DMSans_24pt-SemiBold.ttf +0 -0
  66. package/templates/electron/base/assets/fonts/DMSans_24pt-SemiBoldItalic.ttf +0 -0
  67. package/templates/electron/base/assets/fonts/DMSans_24pt-Thin.ttf +0 -0
  68. package/templates/electron/base/assets/fonts/DMSans_24pt-ThinItalic.ttf +0 -0
  69. package/templates/electron/base/assets/fonts/DMSans_36pt-Black.ttf +0 -0
  70. package/templates/electron/base/assets/fonts/DMSans_36pt-BlackItalic.ttf +0 -0
  71. package/templates/electron/base/assets/fonts/DMSans_36pt-Bold.ttf +0 -0
  72. package/templates/electron/base/assets/fonts/DMSans_36pt-BoldItalic.ttf +0 -0
  73. package/templates/electron/base/assets/fonts/DMSans_36pt-ExtraBold.ttf +0 -0
  74. package/templates/electron/base/assets/fonts/DMSans_36pt-ExtraBoldItalic.ttf +0 -0
  75. package/templates/electron/base/assets/fonts/DMSans_36pt-ExtraLight.ttf +0 -0
  76. package/templates/electron/base/assets/fonts/DMSans_36pt-ExtraLightItalic.ttf +0 -0
  77. package/templates/electron/base/assets/fonts/DMSans_36pt-Italic.ttf +0 -0
  78. package/templates/electron/base/assets/fonts/DMSans_36pt-Light.ttf +0 -0
  79. package/templates/electron/base/assets/fonts/DMSans_36pt-LightItalic.ttf +0 -0
  80. package/templates/electron/base/assets/fonts/DMSans_36pt-Medium.ttf +0 -0
  81. package/templates/electron/base/assets/fonts/DMSans_36pt-MediumItalic.ttf +0 -0
  82. package/templates/electron/base/assets/fonts/DMSans_36pt-Regular.ttf +0 -0
  83. package/templates/electron/base/assets/fonts/DMSans_36pt-SemiBold.ttf +0 -0
  84. package/templates/electron/base/assets/fonts/DMSans_36pt-SemiBoldItalic.ttf +0 -0
  85. package/templates/electron/base/assets/fonts/DMSans_36pt-Thin.ttf +0 -0
  86. package/templates/electron/base/assets/fonts/DMSans_36pt-ThinItalic.ttf +0 -0
  87. package/templates/electron/base/assets/icons/fonts/switch-icons.eot +0 -0
  88. package/templates/electron/base/assets/icons/fonts/switch-icons.svg +2881 -0
  89. package/templates/electron/base/assets/icons/fonts/switch-icons.ttf +0 -0
  90. package/templates/electron/base/assets/icons/fonts/switch-icons.woff +0 -0
  91. package/templates/electron/base/assets/icons/style.css +8583 -0
  92. package/templates/electron/base/assets/logo.svg +5 -0
  93. package/templates/electron/base/assets/script/states.js +38 -0
  94. package/templates/electron/base/assets/script/themechanger.js +14 -0
  95. package/templates/electron/base/assets/styles/styles.css +131 -0
  96. package/templates/electron/base/components/SwSplashScreen.js +1 -0
  97. package/templates/electron/base/components/SwStarterSplashScreen.js +140 -0
  98. package/templates/electron/base/components/SwTabBar.js +153 -0
  99. package/templates/electron/base/electron/electron-builder.json +19 -0
  100. package/templates/electron/base/electron/main.js +30 -0
  101. package/templates/electron/base/electron/preload.js +5 -0
  102. package/templates/electron/base/hooks/checkIntro.js +14 -0
  103. package/templates/electron/base/index.html +14 -0
  104. package/templates/electron/base/index.js +5 -0
  105. package/templates/electron/base/main.js +1 -0
  106. package/templates/electron/base/preload.js +1 -0
  107. package/templates/electron/base/routes/api.js +1 -0
  108. package/templates/electron/base/server.js +42 -0
  109. package/templates/web/base/app/(tabs)/+not-found.js +158 -0
  110. package/templates/web/base/app/(tabs)/_layout.js +125 -0
  111. package/templates/web/base/app/(tabs)/explore.js +44 -0
  112. package/templates/web/base/app/(tabs)/index.js +218 -0
  113. package/templates/web/base/app/(tabs)/register.js +12 -0
  114. package/templates/web/base/app/+not-found.js +158 -0
  115. package/templates/web/base/app/_layout.js +50 -0
  116. package/templates/web/base/app/index.js +265 -0
  117. package/templates/web/base/app/register.js +12 -0
  118. package/templates/web/base/assets/files/Profile.jpg +0 -0
  119. package/templates/web/base/assets/files/fingerprint-icon.svg +4 -0
  120. package/templates/web/base/assets/fonts/DMSans-Black.ttf +0 -0
  121. package/templates/web/base/assets/fonts/DMSans-BlackItalic.ttf +0 -0
  122. package/templates/web/base/assets/fonts/DMSans-Bold.ttf +0 -0
  123. package/templates/web/base/assets/fonts/DMSans-BoldItalic.ttf +0 -0
  124. package/templates/web/base/assets/fonts/DMSans-ExtraBold.ttf +0 -0
  125. package/templates/web/base/assets/fonts/DMSans-ExtraBoldItalic.ttf +0 -0
  126. package/templates/web/base/assets/fonts/DMSans-ExtraLight.ttf +0 -0
  127. package/templates/web/base/assets/fonts/DMSans-ExtraLightItalic.ttf +0 -0
  128. package/templates/web/base/assets/fonts/DMSans-Italic.ttf +0 -0
  129. package/templates/web/base/assets/fonts/DMSans-Light.ttf +0 -0
  130. package/templates/web/base/assets/fonts/DMSans-LightItalic.ttf +0 -0
  131. package/templates/web/base/assets/fonts/DMSans-Medium.ttf +0 -0
  132. package/templates/web/base/assets/fonts/DMSans-MediumItalic.ttf +0 -0
  133. package/templates/web/base/assets/fonts/DMSans-Regular.ttf +0 -0
  134. package/templates/web/base/assets/fonts/DMSans-SemiBold.ttf +0 -0
  135. package/templates/web/base/assets/fonts/DMSans-SemiBoldItalic.ttf +0 -0
  136. package/templates/web/base/assets/fonts/DMSans-Thin.ttf +0 -0
  137. package/templates/web/base/assets/fonts/DMSans-ThinItalic.ttf +0 -0
  138. package/templates/web/base/assets/fonts/DMSans_18pt-Black.ttf +0 -0
  139. package/templates/web/base/assets/fonts/DMSans_18pt-BlackItalic.ttf +0 -0
  140. package/templates/web/base/assets/fonts/DMSans_18pt-Bold.ttf +0 -0
  141. package/templates/web/base/assets/fonts/DMSans_18pt-BoldItalic.ttf +0 -0
  142. package/templates/web/base/assets/fonts/DMSans_18pt-ExtraBold.ttf +0 -0
  143. package/templates/web/base/assets/fonts/DMSans_18pt-ExtraBoldItalic.ttf +0 -0
  144. package/templates/web/base/assets/fonts/DMSans_18pt-ExtraLight.ttf +0 -0
  145. package/templates/web/base/assets/fonts/DMSans_18pt-ExtraLightItalic.ttf +0 -0
  146. package/templates/web/base/assets/fonts/DMSans_18pt-Italic.ttf +0 -0
  147. package/templates/web/base/assets/fonts/DMSans_18pt-Light.ttf +0 -0
  148. package/templates/web/base/assets/fonts/DMSans_18pt-LightItalic.ttf +0 -0
  149. package/templates/web/base/assets/fonts/DMSans_18pt-Medium.ttf +0 -0
  150. package/templates/web/base/assets/fonts/DMSans_18pt-MediumItalic.ttf +0 -0
  151. package/templates/web/base/assets/fonts/DMSans_18pt-Regular.ttf +0 -0
  152. package/templates/web/base/assets/fonts/DMSans_18pt-SemiBold.ttf +0 -0
  153. package/templates/web/base/assets/fonts/DMSans_18pt-SemiBoldItalic.ttf +0 -0
  154. package/templates/web/base/assets/fonts/DMSans_18pt-Thin.ttf +0 -0
  155. package/templates/web/base/assets/fonts/DMSans_18pt-ThinItalic.ttf +0 -0
  156. package/templates/web/base/assets/fonts/DMSans_24pt-Black.ttf +0 -0
  157. package/templates/web/base/assets/fonts/DMSans_24pt-BlackItalic.ttf +0 -0
  158. package/templates/web/base/assets/fonts/DMSans_24pt-Bold.ttf +0 -0
  159. package/templates/web/base/assets/fonts/DMSans_24pt-BoldItalic.ttf +0 -0
  160. package/templates/web/base/assets/fonts/DMSans_24pt-ExtraBold.ttf +0 -0
  161. package/templates/web/base/assets/fonts/DMSans_24pt-ExtraBoldItalic.ttf +0 -0
  162. package/templates/web/base/assets/fonts/DMSans_24pt-ExtraLight.ttf +0 -0
  163. package/templates/web/base/assets/fonts/DMSans_24pt-ExtraLightItalic.ttf +0 -0
  164. package/templates/web/base/assets/fonts/DMSans_24pt-Italic.ttf +0 -0
  165. package/templates/web/base/assets/fonts/DMSans_24pt-Light.ttf +0 -0
  166. package/templates/web/base/assets/fonts/DMSans_24pt-LightItalic.ttf +0 -0
  167. package/templates/web/base/assets/fonts/DMSans_24pt-Medium.ttf +0 -0
  168. package/templates/web/base/assets/fonts/DMSans_24pt-MediumItalic.ttf +0 -0
  169. package/templates/web/base/assets/fonts/DMSans_24pt-Regular.ttf +0 -0
  170. package/templates/web/base/assets/fonts/DMSans_24pt-SemiBold.ttf +0 -0
  171. package/templates/web/base/assets/fonts/DMSans_24pt-SemiBoldItalic.ttf +0 -0
  172. package/templates/web/base/assets/fonts/DMSans_24pt-Thin.ttf +0 -0
  173. package/templates/web/base/assets/fonts/DMSans_24pt-ThinItalic.ttf +0 -0
  174. package/templates/web/base/assets/fonts/DMSans_36pt-Black.ttf +0 -0
  175. package/templates/web/base/assets/fonts/DMSans_36pt-BlackItalic.ttf +0 -0
  176. package/templates/web/base/assets/fonts/DMSans_36pt-Bold.ttf +0 -0
  177. package/templates/web/base/assets/fonts/DMSans_36pt-BoldItalic.ttf +0 -0
  178. package/templates/web/base/assets/fonts/DMSans_36pt-ExtraBold.ttf +0 -0
  179. package/templates/web/base/assets/fonts/DMSans_36pt-ExtraBoldItalic.ttf +0 -0
  180. package/templates/web/base/assets/fonts/DMSans_36pt-ExtraLight.ttf +0 -0
  181. package/templates/web/base/assets/fonts/DMSans_36pt-ExtraLightItalic.ttf +0 -0
  182. package/templates/web/base/assets/fonts/DMSans_36pt-Italic.ttf +0 -0
  183. package/templates/web/base/assets/fonts/DMSans_36pt-Light.ttf +0 -0
  184. package/templates/web/base/assets/fonts/DMSans_36pt-LightItalic.ttf +0 -0
  185. package/templates/web/base/assets/fonts/DMSans_36pt-Medium.ttf +0 -0
  186. package/templates/web/base/assets/fonts/DMSans_36pt-MediumItalic.ttf +0 -0
  187. package/templates/web/base/assets/fonts/DMSans_36pt-Regular.ttf +0 -0
  188. package/templates/web/base/assets/fonts/DMSans_36pt-SemiBold.ttf +0 -0
  189. package/templates/web/base/assets/fonts/DMSans_36pt-SemiBoldItalic.ttf +0 -0
  190. package/templates/web/base/assets/fonts/DMSans_36pt-Thin.ttf +0 -0
  191. package/templates/web/base/assets/fonts/DMSans_36pt-ThinItalic.ttf +0 -0
  192. package/templates/web/base/assets/icons/fonts/switch-icons.eot +0 -0
  193. package/templates/web/base/assets/icons/fonts/switch-icons.svg +2881 -0
  194. package/templates/web/base/assets/icons/fonts/switch-icons.ttf +0 -0
  195. package/templates/web/base/assets/icons/fonts/switch-icons.woff +0 -0
  196. package/templates/web/base/assets/icons/style.css +8583 -0
  197. package/templates/web/base/assets/logo.svg +5 -0
  198. package/templates/web/base/assets/script/states.js +38 -0
  199. package/templates/web/base/assets/script/themechanger.js +14 -0
  200. package/templates/web/base/assets/styles/styles.css +131 -0
  201. package/templates/web/base/components/SwSplashScreen.js +1 -0
  202. package/templates/web/base/components/SwStarterSplashScreen.js +140 -0
  203. package/templates/web/base/components/SwTabBar.js +153 -0
  204. package/templates/web/base/hooks/checkIntro.js +14 -0
  205. package/templates/web/base/index.html +14 -0
  206. package/templates/web/base/index.js +5 -0
  207. package/templates/web/base/routes/api.js +1 -0
package/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # create-switch-framework-app
2
+
3
+ Status: Under maintenance. Documentation is not ready yet.
4
+
5
+ create-switch-framework-app is a CLI for initializing apps that use Switch Framework tools.
6
+
7
+ It can scaffold projects for:
8
+
9
+ - Web app development
10
+ - Electron desktop app development
11
+
12
+ ## Usage
13
+
14
+ ```bash
15
+ npx create-switch-framework-app my-app
16
+ ```
17
+
18
+ Or install globally:
19
+
20
+ ```bash
21
+ npm i -g create-switch-framework-app
22
+ create-switch-framework-app my-app
23
+ ```
24
+
25
+ Made by Switcherfaiz
@@ -0,0 +1,502 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from 'node:path';
4
+ import process from 'node:process';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { createRequire } from 'node:module';
7
+
8
+ import fs from 'fs-extra';
9
+ import chalk from 'chalk';
10
+ import ora from 'ora';
11
+ import enquirer from 'enquirer';
12
+
13
+ const { prompt } = enquirer;
14
+ const require = createRequire(import.meta.url);
15
+
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = path.dirname(__filename);
18
+
19
+ function printHelp() {
20
+ console.log(`\n${chalk.bold('create-switch-framework-app')}\n`);
21
+ console.log('Usage:');
22
+ console.log(' npx create-switch-framework-app <project-name> [options]');
23
+ console.log('\nOptions:');
24
+ console.log(' --yes, -y Skip prompts and use defaults');
25
+ console.log(' --app-type One of: web | electron | both');
26
+ console.log(' --port Server port (1-65535)');
27
+ console.log(' --no-install Do not run npm install');
28
+ console.log(' --use-local Use npm link for switch-framework + switch-framework-backend (no npm registry)');
29
+ console.log(' -h, --help Show help');
30
+ }
31
+
32
+ function parseArgs(argv) {
33
+ const args = argv.slice(2);
34
+ const flags = new Set(args.filter(a => a.startsWith('-')));
35
+
36
+ const help = flags.has('-h') || flags.has('--help');
37
+ const yes = flags.has('--yes') || flags.has('-y');
38
+ const noInstall = flags.has('--no-install');
39
+ const useLocal = flags.has('--use-local');
40
+
41
+ const getValue = (name) => {
42
+ const i = args.indexOf(name);
43
+ if (i === -1) return null;
44
+ const v = args[i + 1];
45
+ if (!v || v.startsWith('-')) return null;
46
+ return v;
47
+ };
48
+
49
+ const appType = getValue('--app-type');
50
+ const port = getValue('--port');
51
+
52
+ const positional = args.filter(a => !a.startsWith('-'));
53
+ const projectName = positional[0] || null;
54
+
55
+ return { help, yes, noInstall, useLocal, projectName, appType, port };
56
+ }
57
+
58
+ function sanitizeProjectName(input) {
59
+ const name = String(input || '').trim();
60
+ return name.replace(/[\\/]/g, '-');
61
+ }
62
+
63
+ function toPackageName(projectName) {
64
+ return projectName
65
+ .toLowerCase()
66
+ .replace(/\s+/g, '-')
67
+ .replace(/[^a-z0-9-_]/g, '-')
68
+ .replace(/-+/g, '-')
69
+ .replace(/^-+|-+$/g, '') || 'switch-framework-app';
70
+ }
71
+
72
+ async function askQuestions({ projectName, yes, noInstall, appTypeOverride, portOverride }) {
73
+ const defaults = {
74
+ appType: 'web',
75
+ port: 3000,
76
+ install: true
77
+ };
78
+
79
+ const resolvedAppType = appTypeOverride || defaults.appType;
80
+ const resolvedPort = portOverride != null ? Number(portOverride) : defaults.port;
81
+
82
+ if (yes) {
83
+ return {
84
+ projectName: projectName || 'switch-framework-app',
85
+ appType: resolvedAppType,
86
+ port: resolvedPort,
87
+ install: noInstall ? false : defaults.install
88
+ };
89
+ }
90
+
91
+ const questions = [];
92
+
93
+ if (!projectName) {
94
+ questions.push({
95
+ type: 'input',
96
+ name: 'projectName',
97
+ message: 'Project name',
98
+ initial: 'switch-framework-app',
99
+ validate(value) {
100
+ const v = sanitizeProjectName(value);
101
+ if (!v) return 'Project name is required';
102
+ return true;
103
+ }
104
+ });
105
+ }
106
+
107
+ questions.push(
108
+ {
109
+ type: 'select',
110
+ name: 'appType',
111
+ message: 'What type of app do you want to create?',
112
+ choices: [
113
+ { name: 'web', message: 'Web App (browser + Node.js/Express backend)' },
114
+ { name: 'electron', message: 'Electron Desktop App (with shared Express backend)' },
115
+ { name: 'both', message: 'Both (monorepo with web + electron targets)' }
116
+ ],
117
+ initial: resolvedAppType
118
+ },
119
+ {
120
+ type: 'numeral',
121
+ name: 'port',
122
+ message: 'Which port do you want the server to use?',
123
+ initial: resolvedPort,
124
+ validate(value) {
125
+ const n = Number(value);
126
+ if (!Number.isInteger(n) || n < 1 || n > 65535) return 'Enter a valid port (1-65535)';
127
+ return true;
128
+ }
129
+ },
130
+ {
131
+ type: 'confirm',
132
+ name: 'install',
133
+ message: 'Do you want to install dependencies automatically?',
134
+ initial: defaults.install,
135
+ skip() {
136
+ return noInstall;
137
+ }
138
+ }
139
+ );
140
+
141
+ const answers = await prompt(questions);
142
+ return {
143
+ projectName: projectName || answers.projectName,
144
+ appType: answers.appType,
145
+ port: Number(answers.port),
146
+ install: noInstall ? false : Boolean(answers.install)
147
+ };
148
+ }
149
+
150
+ function serverJsTemplate({ port, appType }) {
151
+ const staticRoot = appType === 'both' ? 'web' : '.';
152
+
153
+ return `const express = require('express');\n` +
154
+ `const session = require('express-session');\n` +
155
+ `const path = require('node:path');\n` +
156
+ `require('dotenv').config();\n\n` +
157
+ `const { checkRestrict } = require('switch-framework-backend/middleware');\n\n` +
158
+ `const app = express();\n` +
159
+ `const PORT = process.env.PORT ? Number(process.env.PORT) : ${port};\n\n` +
160
+ `app.use(express.json({ limit: '25mb' }));\n\n` +
161
+ `app.use(session({\n` +
162
+ ` secret: process.env.SESSION_SECRET || 'dev-secret',\n` +
163
+ ` resave: false,\n` +
164
+ ` saveUninitialized: false\n` +
165
+ `}));\n\n` +
166
+ `// Serve switch-framework to the browser from node_modules\n` +
167
+ `app.use('/switch-framework', express.static(path.join(__dirname, 'node_modules', 'switch-framework')));\n\n` +
168
+ `// Serve project files\n` +
169
+ `app.use(express.static(path.join(__dirname, '${staticRoot}')));\n\n` +
170
+ `const restrictConfig = {\n` +
171
+ ` public: ['/', '/login'],\n` +
172
+ ` rules: [\n` +
173
+ ` { prefix: '/admin', roles: ['admin'] },\n` +
174
+ ` { prefix: '/billing', roles: ['billing', 'admin'] },\n` +
175
+ ` { path: '/login', roles: ['*'] }\n` +
176
+ ` ]\n` +
177
+ `};\n\n` +
178
+ `app.use(checkRestrict(restrictConfig));\n\n` +
179
+ `app.get('*', (req, res) => {\n` +
180
+ ` res.sendFile(path.join(__dirname, '${staticRoot}', 'index.html'));\n` +
181
+ `});\n\n` +
182
+ `app.listen(PORT, () => {\n` +
183
+ ` console.log('Switch Framework app running at http://localhost:' + PORT);\n` +
184
+ `});\n`;
185
+ }
186
+
187
+ function electronMainTemplate({ port }) {
188
+ return `const { app, BrowserWindow } = require('electron');\n` +
189
+ `const path = require('node:path');\n\n` +
190
+ `let mainWindow;\n\n` +
191
+ `function createWindow() {\n` +
192
+ ` mainWindow = new BrowserWindow({\n` +
193
+ ` width: 1200,\n` +
194
+ ` height: 800,\n` +
195
+ ` webPreferences: {\n` +
196
+ ` preload: path.join(__dirname, 'preload.js')\n` +
197
+ ` }\n` +
198
+ ` });\n\n` +
199
+ ` mainWindow.loadURL('http://localhost:${port}');\n` +
200
+ `}\n\n` +
201
+ `app.whenReady().then(() => {\n` +
202
+ ` require('../server.js');\n` +
203
+ ` setTimeout(createWindow, 350);\n` +
204
+ ` app.on('activate', () => {\n` +
205
+ ` if (BrowserWindow.getAllWindows().length === 0) createWindow();\n` +
206
+ ` });\n` +
207
+ `});\n\n` +
208
+ `app.on('window-all-closed', () => {\n` +
209
+ ` if (process.platform !== 'darwin') app.quit();\n` +
210
+ `});\n`;
211
+ }
212
+
213
+ function electronPreloadTemplate() {
214
+ return `const { contextBridge } = require('electron');\n\n` +
215
+ `contextBridge.exposeInMainWorld('switchApp', {\n` +
216
+ ` ping: () => 'pong'\n` +
217
+ `});\n`;
218
+ }
219
+
220
+ function electronBuilderTemplate({ packageName }) {
221
+ return JSON.stringify(
222
+ {
223
+ appId: `com.switchframework.${packageName}`,
224
+ productName: packageName,
225
+ directories: { output: 'dist' },
226
+ files: ['**/*'],
227
+ win: { target: 'nsis' },
228
+ mac: { target: 'dmg' },
229
+ linux: { target: 'AppImage' }
230
+ },
231
+ null,
232
+ 2
233
+ ) + '\n';
234
+ }
235
+
236
+ function createPackageJson({ packageName, appType, port, useLocal }) {
237
+ const scripts = {
238
+ dev: 'node server.js',
239
+ start: 'node server.js'
240
+ };
241
+
242
+ if (appType === 'electron' || appType === 'both') {
243
+ scripts['electron:dev'] = 'electron .';
244
+ }
245
+
246
+ const deps = {
247
+ dotenv: '^16.4.5',
248
+ express: '^4.19.2',
249
+ 'express-session': '^1.18.1'
250
+ };
251
+
252
+ // When --use-local is set, we intentionally do NOT add switch-framework deps to package.json
253
+ // to avoid npm registry fetching during testing. We will npm link them instead.
254
+ if (!useLocal) {
255
+ deps['switch-framework'] = '^0.1.0';
256
+ deps['switch-framework-backend'] = '^0.1.0';
257
+ }
258
+
259
+ const pkg = {
260
+ name: packageName,
261
+ private: true,
262
+ type: 'commonjs',
263
+ scripts,
264
+ dependencies: deps
265
+ };
266
+
267
+ if (appType === 'electron' || appType === 'both') {
268
+ pkg.main = 'main.js';
269
+ }
270
+
271
+ if (appType === 'electron' || appType === 'both') {
272
+ pkg.devDependencies = {
273
+ electron: '^31.3.1',
274
+ 'electron-builder': '^24.13.3'
275
+ };
276
+ }
277
+
278
+ // Keep port discoverable
279
+ pkg.switchFramework = { port };
280
+
281
+ return JSON.stringify(pkg, null, 2) + '\n';
282
+ }
283
+
284
+ async function copyDir(srcDir, destDir) {
285
+ await fs.ensureDir(destDir);
286
+ await fs.copy(srcDir, destDir, {
287
+ overwrite: true,
288
+ errorOnExist: false
289
+ });
290
+ }
291
+
292
+ async function runNpmInstall({ cwd }) {
293
+ return new Promise((resolve, reject) => {
294
+ const child = require('node:child_process').spawn('npm', ['install'], {
295
+ cwd,
296
+ stdio: 'inherit',
297
+ shell: process.platform === 'win32'
298
+ });
299
+
300
+ child.on('exit', (code) => {
301
+ if (code === 0) resolve();
302
+ else reject(new Error(`npm install failed with exit code ${code}`));
303
+ });
304
+
305
+ child.on('error', reject);
306
+ });
307
+ }
308
+
309
+ async function runNpmLink({ cwd, packages }) {
310
+ return new Promise((resolve, reject) => {
311
+ const child = require('node:child_process').spawn('npm', ['link', ...packages], {
312
+ cwd,
313
+ stdio: 'inherit',
314
+ shell: process.platform === 'win32'
315
+ });
316
+
317
+ child.on('exit', (code) => {
318
+ if (code === 0) resolve();
319
+ else reject(new Error(`npm link failed with exit code ${code}`));
320
+ });
321
+
322
+ child.on('error', reject);
323
+ });
324
+ }
325
+
326
+ async function main() {
327
+ const { help, yes, noInstall, useLocal, appType: appTypeArg, port: portArg, projectName: rawProjectName } = parseArgs(process.argv);
328
+ if (help) {
329
+ printHelp();
330
+ process.exit(0);
331
+ }
332
+
333
+ const normalizedAppType = appTypeArg ? String(appTypeArg).trim().toLowerCase() : null;
334
+ if (normalizedAppType && !['web', 'electron', 'both'].includes(normalizedAppType)) {
335
+ console.error(chalk.red(`Invalid --app-type: ${appTypeArg}. Use one of: web | electron | both`));
336
+ process.exit(1);
337
+ }
338
+
339
+ const normalizedPort = portArg != null ? Number(portArg) : null;
340
+ if (portArg != null && (!Number.isInteger(normalizedPort) || normalizedPort < 1 || normalizedPort > 65535)) {
341
+ console.error(chalk.red(`Invalid --port: ${portArg}. Use an integer 1-65535`));
342
+ process.exit(1);
343
+ }
344
+
345
+ const answers = await askQuestions({
346
+ projectName: rawProjectName,
347
+ yes,
348
+ noInstall,
349
+ appTypeOverride: normalizedAppType,
350
+ portOverride: normalizedPort
351
+ });
352
+ const projectName = sanitizeProjectName(answers.projectName);
353
+ const appType = answers.appType;
354
+ const port = answers.port;
355
+ const install = answers.install;
356
+
357
+ if (!projectName) {
358
+ console.error(chalk.red('Project name is required.'));
359
+ process.exit(1);
360
+ }
361
+
362
+ const targetDir = path.resolve(process.cwd(), projectName);
363
+ const spinner = ora();
364
+
365
+ const templatesRoot = path.resolve(__dirname, '..', 'templates');
366
+ const webBase = path.join(templatesRoot, 'web', 'base');
367
+ const electronBase = path.join(templatesRoot, 'electron', 'base');
368
+
369
+ try {
370
+ if (await fs.pathExists(targetDir)) {
371
+ const items = await fs.readdir(targetDir);
372
+ if (items.length > 0) {
373
+ console.error(chalk.red(`Target directory already exists and is not empty: ${targetDir}`));
374
+ process.exit(1);
375
+ }
376
+ }
377
+
378
+ spinner.start('Creating project...');
379
+ await fs.ensureDir(targetDir);
380
+
381
+ const packageName = toPackageName(projectName);
382
+
383
+ if (appType === 'web') {
384
+ await copyDir(webBase, targetDir);
385
+ await fs.writeFile(path.join(targetDir, 'server.js'), serverJsTemplate({ port, appType }), 'utf8');
386
+ }
387
+
388
+ if (appType === 'electron') {
389
+ const baseDir = (await fs.pathExists(electronBase)) ? electronBase : webBase;
390
+ await copyDir(baseDir, targetDir);
391
+ await fs.ensureDir(path.join(targetDir, 'electron'));
392
+ await fs.writeFile(path.join(targetDir, 'server.js'), serverJsTemplate({ port, appType }), 'utf8');
393
+ await fs.writeFile(path.join(targetDir, 'electron', 'main.js'), electronMainTemplate({ port }), 'utf8');
394
+ await fs.writeFile(path.join(targetDir, 'electron', 'preload.js'), electronPreloadTemplate(), 'utf8');
395
+ await fs.writeFile(path.join(targetDir, 'electron', 'electron-builder.json'), electronBuilderTemplate({ packageName }), 'utf8');
396
+
397
+ // Electron expects entry at project root
398
+ await fs.writeFile(
399
+ path.join(targetDir, 'main.js'),
400
+ "module.exports = require('./electron/main.js');\n",
401
+ 'utf8'
402
+ );
403
+ await fs.writeFile(
404
+ path.join(targetDir, 'preload.js'),
405
+ "module.exports = require('./electron/preload.js');\n",
406
+ 'utf8'
407
+ );
408
+ }
409
+
410
+ if (appType === 'both') {
411
+ await fs.ensureDir(path.join(targetDir, 'web'));
412
+ await copyDir(webBase, path.join(targetDir, 'web'));
413
+
414
+ await fs.ensureDir(path.join(targetDir, 'electron'));
415
+ await fs.writeFile(path.join(targetDir, 'server.js'), serverJsTemplate({ port, appType }), 'utf8');
416
+ await fs.writeFile(path.join(targetDir, 'electron', 'main.js'), electronMainTemplate({ port }), 'utf8');
417
+ await fs.writeFile(path.join(targetDir, 'electron', 'preload.js'), electronPreloadTemplate(), 'utf8');
418
+ await fs.writeFile(path.join(targetDir, 'electron', 'electron-builder.json'), electronBuilderTemplate({ packageName }), 'utf8');
419
+
420
+ await fs.writeFile(
421
+ path.join(targetDir, 'main.js'),
422
+ "module.exports = require('./electron/main.js');\n",
423
+ 'utf8'
424
+ );
425
+ await fs.writeFile(
426
+ path.join(targetDir, 'preload.js'),
427
+ "module.exports = require('./electron/preload.js');\n",
428
+ 'utf8'
429
+ );
430
+ }
431
+
432
+ await fs.writeFile(
433
+ path.join(targetDir, '.env.example'),
434
+ `PORT=${port}\nSESSION_SECRET=dev-secret\n`,
435
+ 'utf8'
436
+ );
437
+
438
+ await fs.writeFile(
439
+ path.join(targetDir, 'package.json'),
440
+ createPackageJson({ packageName, appType, port, useLocal }),
441
+ 'utf8'
442
+ );
443
+
444
+ spinner.succeed('Project created');
445
+
446
+ if (install) {
447
+ spinner.start('Installing dependencies (npm install)...');
448
+ try {
449
+ await runNpmInstall({ cwd: targetDir });
450
+ spinner.succeed('Dependencies installed');
451
+ } catch (e) {
452
+ spinner.warn('npm install failed (project was still created)');
453
+ console.error(chalk.yellow(e?.message || String(e)));
454
+ }
455
+ }
456
+
457
+ if (useLocal) {
458
+ spinner.start('Linking local packages (npm link switch-framework switch-framework-backend)...');
459
+ try {
460
+ await runNpmLink({ cwd: targetDir, packages: ['switch-framework', 'switch-framework-backend'] });
461
+ spinner.succeed('Local packages linked');
462
+ } catch (e) {
463
+ spinner.warn('npm link failed');
464
+ console.error(chalk.yellow(e?.message || String(e)));
465
+ console.log(chalk.yellow('Make sure you ran npm link inside your switch-framework and switch-framework-backend packages first.'));
466
+ }
467
+ }
468
+
469
+ // Ensure no spinner state bleeds into final output
470
+ spinner.stop();
471
+
472
+ console.log('\n' + chalk.green(chalk.bold('Success!')));
473
+ console.log('\nNext steps:');
474
+ console.log(' ' + chalk.cyan('cd ' + projectName));
475
+
476
+ if (!install) {
477
+ console.log(' ' + chalk.cyan('npm install'));
478
+ }
479
+
480
+ if (useLocal) {
481
+ console.log(' ' + chalk.cyan('npm link switch-framework switch-framework-backend'));
482
+ }
483
+
484
+ if (appType === 'web') {
485
+ console.log(' ' + chalk.cyan('npm run dev'));
486
+ } else if (appType === 'electron') {
487
+ console.log(' ' + chalk.cyan('npm run electron:dev'));
488
+ } else {
489
+ console.log(' ' + chalk.cyan('npm run dev'));
490
+ console.log(' ' + chalk.cyan('npm run electron:dev'));
491
+ console.log('\nNote: web UI lives in ./web and electron files in ./electron');
492
+ }
493
+
494
+ console.log('');
495
+ } catch (err) {
496
+ spinner.fail('Failed');
497
+ console.error(chalk.red(err?.stack || err?.message || String(err)));
498
+ process.exit(1);
499
+ }
500
+ }
501
+
502
+ main();
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "create-switch-framework-app",
3
+ "version": "0.1.0",
4
+ "description": "Create a Switch Framework app",
5
+ "github": "https://github.com/Switcherfaiz/create-switch-framework-app",
6
+ "author":"Switcherfaiz",
7
+ "bin": {
8
+ "create-switch-framework-app": "./bin/create-switch-framework-app.js"
9
+ },
10
+ "type": "module",
11
+ "keywords": [
12
+ "cli",
13
+ "scaffold",
14
+ "create-app",
15
+ "switch-framework"
16
+ ],
17
+ "files": [
18
+ "bin",
19
+ "templates"
20
+ ],
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "dependencies": {
25
+ "chalk": "^5.3.0",
26
+ "enquirer": "^2.4.1",
27
+ "fs-extra": "^11.2.0",
28
+ "ora": "^8.0.1"
29
+ },
30
+ "license": "MIT"
31
+ }