tools-template-cli 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 (198) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +54 -0
  3. package/dist/index.js +380 -0
  4. package/dist/templates.js +44 -0
  5. package/package.json +49 -0
  6. package/supabase-expo-app/.env.example +2 -0
  7. package/supabase-expo-app/App.tsx +55 -0
  8. package/supabase-expo-app/README.md +179 -0
  9. package/supabase-expo-app/app.json +34 -0
  10. package/supabase-expo-app/assets/adaptive-icon.png +0 -0
  11. package/supabase-expo-app/assets/favicon.png +0 -0
  12. package/supabase-expo-app/assets/icon.png +0 -0
  13. package/supabase-expo-app/assets/splash-icon.png +0 -0
  14. package/supabase-expo-app/components/Account.tsx +234 -0
  15. package/supabase-expo-app/components/Auth.tsx +161 -0
  16. package/supabase-expo-app/components/Avatar.tsx +173 -0
  17. package/supabase-expo-app/index.ts +8 -0
  18. package/supabase-expo-app/lib/supabase.ts +63 -0
  19. package/supabase-expo-app/package-lock.json +8983 -0
  20. package/supabase-expo-app/package.json +32 -0
  21. package/supabase-expo-app/scripts/setup-supabase.sh +73 -0
  22. package/supabase-expo-app/supabase/schema.sql +57 -0
  23. package/supabase-expo-app/tsconfig.json +6 -0
  24. package/supabase-swiftui-app/Package.swift +32 -0
  25. package/supabase-swiftui-app/README.md +215 -0
  26. package/supabase-swiftui-app/SupabaseSwiftUIApp/AccountView.swift +181 -0
  27. package/supabase-swiftui-app/SupabaseSwiftUIApp/AuthView.swift +123 -0
  28. package/supabase-swiftui-app/SupabaseSwiftUIApp/AvatarView.swift +113 -0
  29. package/supabase-swiftui-app/SupabaseSwiftUIApp/ContentView.swift +18 -0
  30. package/supabase-swiftui-app/SupabaseSwiftUIApp/Supabase.swift +13 -0
  31. package/supabase-swiftui-app/SupabaseSwiftUIApp/SupabaseSwiftUIApp.swift +22 -0
  32. package/supabase-swiftui-app/scripts/setup-supabase.sh +67 -0
  33. package/supabase-swiftui-app/supabase/schema.sql +57 -0
  34. package/supabase-user-management/AGENTS.md +5 -0
  35. package/supabase-user-management/CLAUDE.md +1 -0
  36. package/supabase-user-management/README.md +178 -0
  37. package/supabase-user-management/app/account/account-form.tsx +174 -0
  38. package/supabase-user-management/app/account/avatar.tsx +109 -0
  39. package/supabase-user-management/app/account/page.tsx +28 -0
  40. package/supabase-user-management/app/auth/confirm/route.ts +26 -0
  41. package/supabase-user-management/app/error.tsx +14 -0
  42. package/supabase-user-management/app/favicon.ico +0 -0
  43. package/supabase-user-management/app/globals.css +130 -0
  44. package/supabase-user-management/app/layout.tsx +22 -0
  45. package/supabase-user-management/app/loading.tsx +7 -0
  46. package/supabase-user-management/app/login/actions.ts +45 -0
  47. package/supabase-user-management/app/login/page.tsx +90 -0
  48. package/supabase-user-management/app/page.tsx +16 -0
  49. package/supabase-user-management/components/ui/button.tsx +58 -0
  50. package/supabase-user-management/components.json +25 -0
  51. package/supabase-user-management/eslint.config.mjs +18 -0
  52. package/supabase-user-management/lib/supabase/client.ts +8 -0
  53. package/supabase-user-management/lib/supabase/middleware.ts +52 -0
  54. package/supabase-user-management/lib/supabase/server.ts +29 -0
  55. package/supabase-user-management/lib/utils.ts +6 -0
  56. package/supabase-user-management/next.config.ts +7 -0
  57. package/supabase-user-management/package-lock.json +9910 -0
  58. package/supabase-user-management/package.json +36 -0
  59. package/supabase-user-management/postcss.config.mjs +7 -0
  60. package/supabase-user-management/public/file.svg +1 -0
  61. package/supabase-user-management/public/globe.svg +1 -0
  62. package/supabase-user-management/public/next.svg +1 -0
  63. package/supabase-user-management/public/vercel.svg +1 -0
  64. package/supabase-user-management/public/window.svg +1 -0
  65. package/supabase-user-management/scripts/setup-supabase.sh +98 -0
  66. package/supabase-user-management/src/proxy.ts +12 -0
  67. package/supabase-user-management/supabase/schema.sql +57 -0
  68. package/supabase-user-management/tsconfig.json +34 -0
  69. package/supabase_flutter_app/.metadata +45 -0
  70. package/supabase_flutter_app/README.md +195 -0
  71. package/supabase_flutter_app/analysis_options.yaml +28 -0
  72. package/supabase_flutter_app/android/app/build.gradle.kts +44 -0
  73. package/supabase_flutter_app/android/app/src/debug/AndroidManifest.xml +7 -0
  74. package/supabase_flutter_app/android/app/src/main/AndroidManifest.xml +54 -0
  75. package/supabase_flutter_app/android/app/src/main/kotlin/com/example/supabase_flutter_app/MainActivity.kt +5 -0
  76. package/supabase_flutter_app/android/app/src/main/res/drawable/launch_background.xml +12 -0
  77. package/supabase_flutter_app/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
  78. package/supabase_flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  79. package/supabase_flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  80. package/supabase_flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  81. package/supabase_flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  82. package/supabase_flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  83. package/supabase_flutter_app/android/app/src/main/res/values/styles.xml +18 -0
  84. package/supabase_flutter_app/android/app/src/main/res/values-night/styles.xml +18 -0
  85. package/supabase_flutter_app/android/app/src/profile/AndroidManifest.xml +7 -0
  86. package/supabase_flutter_app/android/build.gradle.kts +24 -0
  87. package/supabase_flutter_app/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  88. package/supabase_flutter_app/android/gradle.properties +2 -0
  89. package/supabase_flutter_app/android/settings.gradle.kts +26 -0
  90. package/supabase_flutter_app/ios/Flutter/AppFrameworkInfo.plist +26 -0
  91. package/supabase_flutter_app/ios/Flutter/Debug.xcconfig +2 -0
  92. package/supabase_flutter_app/ios/Flutter/Release.xcconfig +2 -0
  93. package/supabase_flutter_app/ios/Podfile +43 -0
  94. package/supabase_flutter_app/ios/Runner/AppDelegate.swift +13 -0
  95. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +122 -0
  96. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png +0 -0
  97. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png +0 -0
  98. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png +0 -0
  99. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png +0 -0
  100. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png +0 -0
  101. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png +0 -0
  102. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png +0 -0
  103. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png +0 -0
  104. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png +0 -0
  105. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png +0 -0
  106. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png +0 -0
  107. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png +0 -0
  108. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png +0 -0
  109. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png +0 -0
  110. package/supabase_flutter_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png +0 -0
  111. package/supabase_flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +23 -0
  112. package/supabase_flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
  113. package/supabase_flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
  114. package/supabase_flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
  115. package/supabase_flutter_app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +5 -0
  116. package/supabase_flutter_app/ios/Runner/Base.lproj/LaunchScreen.storyboard +37 -0
  117. package/supabase_flutter_app/ios/Runner/Base.lproj/Main.storyboard +26 -0
  118. package/supabase_flutter_app/ios/Runner/Info.plist +61 -0
  119. package/supabase_flutter_app/ios/Runner/Runner-Bridging-Header.h +1 -0
  120. package/supabase_flutter_app/ios/Runner.xcodeproj/project.pbxproj +619 -0
  121. package/supabase_flutter_app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  122. package/supabase_flutter_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  123. package/supabase_flutter_app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  124. package/supabase_flutter_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +101 -0
  125. package/supabase_flutter_app/ios/Runner.xcworkspace/contents.xcworkspacedata +7 -0
  126. package/supabase_flutter_app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  127. package/supabase_flutter_app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  128. package/supabase_flutter_app/ios/RunnerTests/RunnerTests.swift +12 -0
  129. package/supabase_flutter_app/lib/components/avatar.dart +153 -0
  130. package/supabase_flutter_app/lib/main.dart +70 -0
  131. package/supabase_flutter_app/lib/pages/account_page.dart +189 -0
  132. package/supabase_flutter_app/lib/pages/login_page.dart +150 -0
  133. package/supabase_flutter_app/linux/CMakeLists.txt +128 -0
  134. package/supabase_flutter_app/linux/flutter/CMakeLists.txt +88 -0
  135. package/supabase_flutter_app/linux/flutter/generated_plugin_registrant.cc +23 -0
  136. package/supabase_flutter_app/linux/flutter/generated_plugin_registrant.h +15 -0
  137. package/supabase_flutter_app/linux/flutter/generated_plugins.cmake +26 -0
  138. package/supabase_flutter_app/linux/runner/CMakeLists.txt +26 -0
  139. package/supabase_flutter_app/linux/runner/main.cc +6 -0
  140. package/supabase_flutter_app/linux/runner/my_application.cc +148 -0
  141. package/supabase_flutter_app/linux/runner/my_application.h +21 -0
  142. package/supabase_flutter_app/macos/Flutter/Flutter-Debug.xcconfig +2 -0
  143. package/supabase_flutter_app/macos/Flutter/Flutter-Release.xcconfig +2 -0
  144. package/supabase_flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift +18 -0
  145. package/supabase_flutter_app/macos/Podfile +42 -0
  146. package/supabase_flutter_app/macos/Runner/AppDelegate.swift +13 -0
  147. package/supabase_flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +68 -0
  148. package/supabase_flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
  149. package/supabase_flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
  150. package/supabase_flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
  151. package/supabase_flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
  152. package/supabase_flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
  153. package/supabase_flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
  154. package/supabase_flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
  155. package/supabase_flutter_app/macos/Runner/Base.lproj/MainMenu.xib +343 -0
  156. package/supabase_flutter_app/macos/Runner/Configs/AppInfo.xcconfig +14 -0
  157. package/supabase_flutter_app/macos/Runner/Configs/Debug.xcconfig +2 -0
  158. package/supabase_flutter_app/macos/Runner/Configs/Release.xcconfig +2 -0
  159. package/supabase_flutter_app/macos/Runner/Configs/Warnings.xcconfig +13 -0
  160. package/supabase_flutter_app/macos/Runner/DebugProfile.entitlements +12 -0
  161. package/supabase_flutter_app/macos/Runner/Info.plist +32 -0
  162. package/supabase_flutter_app/macos/Runner/MainFlutterWindow.swift +15 -0
  163. package/supabase_flutter_app/macos/Runner/Release.entitlements +8 -0
  164. package/supabase_flutter_app/macos/Runner.xcodeproj/project.pbxproj +705 -0
  165. package/supabase_flutter_app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  166. package/supabase_flutter_app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +99 -0
  167. package/supabase_flutter_app/macos/Runner.xcworkspace/contents.xcworkspacedata +7 -0
  168. package/supabase_flutter_app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  169. package/supabase_flutter_app/macos/RunnerTests/RunnerTests.swift +12 -0
  170. package/supabase_flutter_app/pubspec.lock +818 -0
  171. package/supabase_flutter_app/pubspec.yaml +26 -0
  172. package/supabase_flutter_app/scripts/setup-supabase.sh +72 -0
  173. package/supabase_flutter_app/supabase/schema.sql +57 -0
  174. package/supabase_flutter_app/test/widget_test.dart +30 -0
  175. package/supabase_flutter_app/web/favicon.png +0 -0
  176. package/supabase_flutter_app/web/icons/Icon-192.png +0 -0
  177. package/supabase_flutter_app/web/icons/Icon-512.png +0 -0
  178. package/supabase_flutter_app/web/icons/Icon-maskable-192.png +0 -0
  179. package/supabase_flutter_app/web/icons/Icon-maskable-512.png +0 -0
  180. package/supabase_flutter_app/web/index.html +38 -0
  181. package/supabase_flutter_app/web/manifest.json +35 -0
  182. package/supabase_flutter_app/windows/CMakeLists.txt +108 -0
  183. package/supabase_flutter_app/windows/flutter/CMakeLists.txt +109 -0
  184. package/supabase_flutter_app/windows/flutter/generated_plugin_registrant.cc +20 -0
  185. package/supabase_flutter_app/windows/flutter/generated_plugin_registrant.h +15 -0
  186. package/supabase_flutter_app/windows/flutter/generated_plugins.cmake +26 -0
  187. package/supabase_flutter_app/windows/runner/CMakeLists.txt +40 -0
  188. package/supabase_flutter_app/windows/runner/Runner.rc +121 -0
  189. package/supabase_flutter_app/windows/runner/flutter_window.cpp +71 -0
  190. package/supabase_flutter_app/windows/runner/flutter_window.h +33 -0
  191. package/supabase_flutter_app/windows/runner/main.cpp +43 -0
  192. package/supabase_flutter_app/windows/runner/resource.h +16 -0
  193. package/supabase_flutter_app/windows/runner/resources/app_icon.ico +0 -0
  194. package/supabase_flutter_app/windows/runner/runner.exe.manifest +14 -0
  195. package/supabase_flutter_app/windows/runner/utils.cpp +65 -0
  196. package/supabase_flutter_app/windows/runner/utils.h +19 -0
  197. package/supabase_flutter_app/windows/runner/win32_window.cpp +288 -0
  198. package/supabase_flutter_app/windows/runner/win32_window.h +102 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # tmp-cli
2
+
3
+ A small TypeScript CLI for cloning the starter templates in this repository into a fresh project directory.
4
+
5
+ ## Install
6
+
7
+ ### From npm
8
+
9
+ ```bash
10
+ npm install -g tools-template-cli
11
+ tmp-cli list
12
+ ```
13
+
14
+ ### Locally while developing
15
+
16
+ ```bash
17
+ npm install
18
+ npm run build
19
+ npm link
20
+ tmp-cli list
21
+ ```
22
+
23
+ ## Commands
24
+
25
+ ```bash
26
+ npm install
27
+ npm run build
28
+ node dist/index.js list
29
+ node dist/index.js create supabase-expo-app my-mobile-app
30
+ node dist/index.js create ./apps/admin --template supabase-user-management --git
31
+ ```
32
+
33
+ ## What it does
34
+
35
+ - Lists the templates that live in this repository
36
+ - Copies a template into a new target directory
37
+ - Skips `.git`, `node_modules`, `.DS_Store`, and common build output directories
38
+ - Renames basic config values for the JavaScript templates
39
+ - Optionally runs `npm install`, `pnpm install`, `yarn install`, or `bun install`
40
+ - Optionally initializes a fresh git repository in the generated project
41
+
42
+ ## Global usage
43
+
44
+ ```bash
45
+ tmp-cli list
46
+ tmp-cli create supabase-expo-app my-mobile-app
47
+ tmp-cli create ./apps/admin --template supabase-user-management --git
48
+ ```
49
+
50
+ ## Notes
51
+
52
+ - `supabase-expo-app` and `supabase-user-management` get automatic config renaming in `package.json`, `package-lock.json`, `app.json`, and `README.md`
53
+ - The SwiftUI and Flutter templates are copied as-is, apart from the target directory name
54
+ - Use `node dist/index.js help` to see all options
package/dist/index.js ADDED
@@ -0,0 +1,380 @@
1
+ #!/usr/bin/env node
2
+ import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
3
+ import { spawnSync } from "node:child_process";
4
+ import { basename, dirname, join, resolve } from "node:path";
5
+ import process from "node:process";
6
+ import { fileURLToPath } from "node:url";
7
+ import { getTemplate, templates } from "./templates.js";
8
+ const CLI_VERSION = "0.1.0";
9
+ const ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..");
10
+ const SKIP_NAMES = new Set([
11
+ ".git",
12
+ ".DS_Store",
13
+ "node_modules",
14
+ ".next",
15
+ ".expo",
16
+ ".turbo",
17
+ "dist",
18
+ "build"
19
+ ]);
20
+ const VALID_PACKAGE_MANAGERS = new Set([
21
+ "npm",
22
+ "pnpm",
23
+ "yarn",
24
+ "bun"
25
+ ]);
26
+ function main() {
27
+ try {
28
+ const parsed = parseArgs(process.argv.slice(2));
29
+ if (hasBooleanFlag(parsed.flags, "help") || parsed.command === "help") {
30
+ printHelp();
31
+ return;
32
+ }
33
+ if (hasBooleanFlag(parsed.flags, "version")) {
34
+ console.log(CLI_VERSION);
35
+ return;
36
+ }
37
+ switch (parsed.command) {
38
+ case "list":
39
+ listTemplates();
40
+ return;
41
+ case "create":
42
+ createProject(resolveCreateOptions(parsed));
43
+ return;
44
+ default:
45
+ printHelp(`Unknown command: ${parsed.command}`);
46
+ }
47
+ }
48
+ catch (error) {
49
+ const message = error instanceof Error ? error.message : "Unknown CLI failure";
50
+ console.error(`Error: ${message}`);
51
+ process.exit(1);
52
+ }
53
+ }
54
+ function parseArgs(argv) {
55
+ const command = argv[0] ?? "help";
56
+ const positionals = [];
57
+ const flags = new Map();
58
+ for (let index = 1; index < argv.length; index += 1) {
59
+ const token = argv[index];
60
+ if (!token.startsWith("-")) {
61
+ positionals.push(token);
62
+ continue;
63
+ }
64
+ if (token === "--") {
65
+ positionals.push(...argv.slice(index + 1));
66
+ break;
67
+ }
68
+ if (token.startsWith("--")) {
69
+ const [rawName, inlineValue] = token.slice(2).split("=", 2);
70
+ const name = normalizeFlagName(rawName);
71
+ if (inlineValue !== undefined) {
72
+ flags.set(name, inlineValue);
73
+ continue;
74
+ }
75
+ if (flagTakesValue(name)) {
76
+ const next = argv[index + 1];
77
+ if (!next || next.startsWith("-")) {
78
+ throw new Error(`Missing value for --${rawName}`);
79
+ }
80
+ flags.set(name, next);
81
+ index += 1;
82
+ continue;
83
+ }
84
+ flags.set(name, true);
85
+ continue;
86
+ }
87
+ const shortFlags = token.slice(1).split("");
88
+ for (let shortIndex = 0; shortIndex < shortFlags.length; shortIndex += 1) {
89
+ const shortFlag = shortFlags[shortIndex];
90
+ const name = mapShortFlag(shortFlag);
91
+ if (flagTakesValue(name)) {
92
+ const remainder = shortFlags.slice(shortIndex + 1).join("");
93
+ if (remainder.length > 0) {
94
+ flags.set(name, remainder);
95
+ break;
96
+ }
97
+ const next = argv[index + 1];
98
+ if (!next || next.startsWith("-")) {
99
+ throw new Error(`Missing value for -${shortFlag}`);
100
+ }
101
+ flags.set(name, next);
102
+ index += 1;
103
+ break;
104
+ }
105
+ flags.set(name, true);
106
+ }
107
+ }
108
+ return { command, positionals, flags };
109
+ }
110
+ function resolveCreateOptions(parsed) {
111
+ const templateFlag = getStringFlag(parsed.flags, "template");
112
+ const force = hasBooleanFlag(parsed.flags, "force");
113
+ const install = hasBooleanFlag(parsed.flags, "install");
114
+ const git = hasBooleanFlag(parsed.flags, "git");
115
+ let templateId = templateFlag;
116
+ let targetDirArg;
117
+ if (templateId) {
118
+ targetDirArg = parsed.positionals[0] ?? templateId;
119
+ }
120
+ else {
121
+ templateId = parsed.positionals[0];
122
+ targetDirArg = parsed.positionals[1] ?? parsed.positionals[0];
123
+ }
124
+ if (!templateId) {
125
+ throw new Error("Missing template name. Run `tmp-cli list` to see available templates.");
126
+ }
127
+ const template = getTemplate(templateId);
128
+ if (!template) {
129
+ throw new Error(`Unknown template "${templateId}". Run \`tmp-cli list\` to see available templates.`);
130
+ }
131
+ if (!targetDirArg) {
132
+ throw new Error("Missing target directory for create command.");
133
+ }
134
+ const targetDir = resolve(process.cwd(), targetDirArg);
135
+ const directoryName = basename(targetDir);
136
+ const displayName = getStringFlag(parsed.flags, "name") ?? directoryName;
137
+ const packageName = normalizePackageName(directoryName);
138
+ const packageManagerValue = getStringFlag(parsed.flags, "package-manager") ??
139
+ template.defaultPackageManager;
140
+ if (packageManagerValue !== undefined &&
141
+ !VALID_PACKAGE_MANAGERS.has(packageManagerValue)) {
142
+ throw new Error(`Unsupported package manager "${packageManagerValue}". Use npm, pnpm, yarn, or bun.`);
143
+ }
144
+ return {
145
+ template,
146
+ targetDir,
147
+ displayName,
148
+ directoryName,
149
+ packageName,
150
+ force,
151
+ install,
152
+ git,
153
+ packageManager: packageManagerValue
154
+ };
155
+ }
156
+ function createProject(options) {
157
+ const templateSourceDir = resolve(ROOT_DIR, options.template.path);
158
+ if (!existsSync(templateSourceDir)) {
159
+ throw new Error(`Template path does not exist: ${templateSourceDir}`);
160
+ }
161
+ prepareTargetDirectory(options.targetDir, options.force);
162
+ copyDirectory(templateSourceDir, options.targetDir);
163
+ applyTransforms(options);
164
+ const nextSteps = [`cd ${options.targetDir}`];
165
+ if (options.git) {
166
+ runCommand("git", ["init"], options.targetDir);
167
+ nextSteps.push("git add .", 'git commit -m "Initial commit"');
168
+ }
169
+ if (options.install) {
170
+ const packageManager = options.packageManager;
171
+ if (!packageManager) {
172
+ console.warn("Warning: --install is only supported for templates with a configured JavaScript package manager.");
173
+ }
174
+ else {
175
+ runCommand(packageManager, ["install"], options.targetDir);
176
+ }
177
+ }
178
+ else if (options.packageManager) {
179
+ nextSteps.push(`${options.packageManager} install`);
180
+ }
181
+ console.log(`Created ${options.template.id} at ${options.targetDir}`);
182
+ console.log(`Platform: ${options.template.platform}`);
183
+ if (options.template.renameStrategy === "directory-only") {
184
+ console.log("Note: this template keeps its original framework identifiers. Only the target directory and README reference were updated.");
185
+ }
186
+ if (nextSteps.length > 0) {
187
+ console.log("\nNext:");
188
+ for (const step of nextSteps) {
189
+ console.log(` ${step}`);
190
+ }
191
+ }
192
+ }
193
+ function listTemplates() {
194
+ console.log("Available templates:\n");
195
+ for (const template of templates) {
196
+ console.log(`- ${template.id}`);
197
+ console.log(` ${template.description}`);
198
+ console.log(` Platform: ${template.platform}`);
199
+ }
200
+ }
201
+ function prepareTargetDirectory(targetDir, force) {
202
+ if (!existsSync(targetDir)) {
203
+ return;
204
+ }
205
+ const targetStats = statSync(targetDir);
206
+ if (!targetStats.isDirectory()) {
207
+ throw new Error(`Target path is not a directory: ${targetDir}`);
208
+ }
209
+ const entries = readdirSync(targetDir);
210
+ if (entries.length === 0) {
211
+ rmSync(targetDir, { recursive: true, force: true });
212
+ return;
213
+ }
214
+ if (!force) {
215
+ throw new Error(`Target directory is not empty: ${targetDir}. Use --force to overwrite it.`);
216
+ }
217
+ rmSync(targetDir, { recursive: true, force: true });
218
+ }
219
+ function copyDirectory(sourceDir, targetDir) {
220
+ mkdirSync(targetDir, { recursive: true });
221
+ for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
222
+ if (SKIP_NAMES.has(entry.name)) {
223
+ continue;
224
+ }
225
+ const sourcePath = join(sourceDir, entry.name);
226
+ const targetPath = join(targetDir, entry.name);
227
+ if (entry.isDirectory()) {
228
+ copyDirectory(sourcePath, targetPath);
229
+ continue;
230
+ }
231
+ if (entry.isFile()) {
232
+ copyFileSync(sourcePath, targetPath);
233
+ chmodSync(targetPath, statSync(sourcePath).mode);
234
+ }
235
+ }
236
+ }
237
+ function applyTransforms(options) {
238
+ for (const transform of options.template.transforms) {
239
+ applyTransform(transform, options);
240
+ }
241
+ }
242
+ function applyTransform(transform, options) {
243
+ switch (transform) {
244
+ case "package-json-name":
245
+ updateJsonFile(join(options.targetDir, "package.json"), (json) => {
246
+ json.name = options.packageName;
247
+ });
248
+ return;
249
+ case "package-lock-name":
250
+ updateJsonFile(join(options.targetDir, "package-lock.json"), (json) => {
251
+ json.name = options.packageName;
252
+ if (json.packages?.[""]) {
253
+ json.packages[""].name = options.packageName;
254
+ }
255
+ });
256
+ return;
257
+ case "expo-config":
258
+ updateJsonFile(join(options.targetDir, "app.json"), (json) => {
259
+ if (!json.expo) {
260
+ return;
261
+ }
262
+ json.expo.name = options.displayName;
263
+ json.expo.slug = slugify(options.directoryName);
264
+ });
265
+ return;
266
+ case "readme-name":
267
+ replaceInFile(join(options.targetDir, "README.md"), options.template.id, options.directoryName);
268
+ }
269
+ }
270
+ function updateJsonFile(filePath, updater) {
271
+ if (!existsSync(filePath)) {
272
+ return;
273
+ }
274
+ const parsed = JSON.parse(readFileSync(filePath, "utf8"));
275
+ updater(parsed);
276
+ writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}\n`);
277
+ }
278
+ function replaceInFile(filePath, searchValue, replacementValue) {
279
+ if (!existsSync(filePath)) {
280
+ return;
281
+ }
282
+ const original = readFileSync(filePath, "utf8");
283
+ const updated = original.split(searchValue).join(replacementValue);
284
+ if (updated !== original) {
285
+ writeFileSync(filePath, updated);
286
+ }
287
+ }
288
+ function runCommand(command, args, cwd) {
289
+ const result = spawnSync(command, args, {
290
+ cwd,
291
+ stdio: "inherit"
292
+ });
293
+ if (result.error) {
294
+ throw result.error;
295
+ }
296
+ if (result.status !== 0) {
297
+ throw new Error(`Command failed: ${command} ${args.join(" ")}`);
298
+ }
299
+ }
300
+ function normalizeFlagName(name) {
301
+ return name.trim().toLowerCase();
302
+ }
303
+ function flagTakesValue(name) {
304
+ return name === "template" || name === "name" || name === "package-manager";
305
+ }
306
+ function mapShortFlag(flag) {
307
+ switch (flag) {
308
+ case "t":
309
+ return "template";
310
+ case "n":
311
+ return "name";
312
+ case "p":
313
+ return "package-manager";
314
+ case "f":
315
+ return "force";
316
+ case "i":
317
+ return "install";
318
+ case "g":
319
+ return "git";
320
+ case "h":
321
+ return "help";
322
+ case "v":
323
+ return "version";
324
+ default:
325
+ throw new Error(`Unknown flag: -${flag}`);
326
+ }
327
+ }
328
+ function hasBooleanFlag(flags, name) {
329
+ return flags.get(name) === true;
330
+ }
331
+ function getStringFlag(flags, name) {
332
+ const value = flags.get(name);
333
+ return typeof value === "string" ? value : undefined;
334
+ }
335
+ function normalizePackageName(value) {
336
+ return value
337
+ .trim()
338
+ .toLowerCase()
339
+ .replace(/[^a-z0-9._-]+/g, "-")
340
+ .replace(/^-+|-+$/g, "")
341
+ .replace(/-{2,}/g, "-");
342
+ }
343
+ function slugify(value) {
344
+ return value
345
+ .trim()
346
+ .toLowerCase()
347
+ .replace(/[^a-z0-9]+/g, "-")
348
+ .replace(/^-+|-+$/g, "")
349
+ .replace(/-{2,}/g, "-");
350
+ }
351
+ function printHelp(errorMessage) {
352
+ if (errorMessage) {
353
+ console.error(errorMessage);
354
+ console.error("");
355
+ }
356
+ console.log(`tmp-cli ${CLI_VERSION}
357
+
358
+ Usage:
359
+ tmp-cli list
360
+ tmp-cli create <template> [target-directory] [options]
361
+ tmp-cli create <target-directory> --template <template> [options]
362
+
363
+ Options:
364
+ -t, --template <name> Template name to copy
365
+ -n, --name <display-name> Display name for supported templates
366
+ -p, --package-manager <tool> npm | pnpm | yarn | bun
367
+ -f, --force Overwrite a non-empty target directory
368
+ -i, --install Run package-manager install after copy
369
+ -g, --git Run git init in the generated directory
370
+ -h, --help Show help
371
+ -v, --version Show CLI version
372
+
373
+ Examples:
374
+ tmp-cli list
375
+ tmp-cli create supabase-expo-app my-mobile-app
376
+ tmp-cli create ./apps/admin --template supabase-user-management --git
377
+ tmp-cli create supabase-expo-app my-mobile-app --name "My Mobile App" --install
378
+ `);
379
+ }
380
+ main();
@@ -0,0 +1,44 @@
1
+ export const templates = [
2
+ {
3
+ id: "supabase-expo-app",
4
+ path: "supabase-expo-app",
5
+ description: "Expo + Supabase starter with auth, account, and avatar flows",
6
+ platform: "React Native / Expo",
7
+ defaultPackageManager: "npm",
8
+ transforms: [
9
+ "package-json-name",
10
+ "package-lock-name",
11
+ "expo-config",
12
+ "readme-name"
13
+ ],
14
+ renameStrategy: "config-only"
15
+ },
16
+ {
17
+ id: "supabase-user-management",
18
+ path: "supabase-user-management",
19
+ description: "Next.js + Supabase user management starter",
20
+ platform: "Next.js",
21
+ defaultPackageManager: "npm",
22
+ transforms: ["package-json-name", "package-lock-name", "readme-name"],
23
+ renameStrategy: "config-only"
24
+ },
25
+ {
26
+ id: "supabase-swiftui-app",
27
+ path: "supabase-swiftui-app",
28
+ description: "SwiftUI + Supabase starter",
29
+ platform: "SwiftUI",
30
+ transforms: ["readme-name"],
31
+ renameStrategy: "directory-only"
32
+ },
33
+ {
34
+ id: "supabase_flutter_app",
35
+ path: "supabase_flutter_app",
36
+ description: "Flutter + Supabase starter",
37
+ platform: "Flutter",
38
+ transforms: ["readme-name"],
39
+ renameStrategy: "directory-only"
40
+ }
41
+ ];
42
+ export function getTemplate(id) {
43
+ return templates.find((template) => template.id === id);
44
+ }
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "tools-template-cli",
3
+ "version": "0.1.0",
4
+ "description": "CLI for cloning starter templates from this repository into fresh project directories",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "tmp-cli": "dist/index.js"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/vinzeny/templates.git"
13
+ },
14
+ "homepage": "https://github.com/vinzeny/templates",
15
+ "bugs": {
16
+ "url": "https://github.com/vinzeny/templates/issues"
17
+ },
18
+ "keywords": [
19
+ "cli",
20
+ "template",
21
+ "starter",
22
+ "expo",
23
+ "nextjs",
24
+ "flutter",
25
+ "swiftui"
26
+ ],
27
+ "scripts": {
28
+ "build": "tsc -p tsconfig.json",
29
+ "check": "npm run build",
30
+ "prepack": "npm run build",
31
+ "list": "node dist/index.js list",
32
+ "create": "node dist/index.js create"
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "README.md",
37
+ "supabase-expo-app",
38
+ "supabase-user-management",
39
+ "supabase-swiftui-app",
40
+ "supabase_flutter_app"
41
+ ],
42
+ "engines": {
43
+ "node": ">=20"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^22.15.17",
47
+ "typescript": "^5.9.3"
48
+ }
49
+ }
@@ -0,0 +1,2 @@
1
+ EXPO_PUBLIC_SUPABASE_URL=your_supabase_url
2
+ EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your_supabase_publishable_key
@@ -0,0 +1,55 @@
1
+ import { useState, useEffect } from 'react'
2
+ import { StyleSheet, View, ActivityIndicator } from 'react-native'
3
+ import { supabase } from './lib/supabase'
4
+ import Auth from './components/Auth'
5
+ import Account from './components/Account'
6
+
7
+ export default function App() {
8
+ const [session, setSession] = useState<any>(null)
9
+ const [loading, setLoading] = useState(true)
10
+
11
+ useEffect(() => {
12
+ // Get initial session
13
+ supabase.auth.getSession().then(({ data: { session } }) => {
14
+ setSession(session)
15
+ setLoading(false)
16
+ })
17
+
18
+ // Listen for auth changes
19
+ const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
20
+ setSession(session)
21
+ })
22
+
23
+ return () => subscription.unsubscribe()
24
+ }, [])
25
+
26
+ if (loading) {
27
+ return (
28
+ <View style={styles.loadingContainer}>
29
+ <ActivityIndicator size="large" color="#10b981" />
30
+ </View>
31
+ )
32
+ }
33
+
34
+ return (
35
+ <View style={styles.container}>
36
+ {session && session.user ? (
37
+ <Account key={session.user.id} session={session} />
38
+ ) : (
39
+ <Auth onAuthSuccess={() => {}} />
40
+ )}
41
+ </View>
42
+ )
43
+ }
44
+
45
+ const styles = StyleSheet.create({
46
+ container: {
47
+ flex: 1,
48
+ backgroundColor: '#fff',
49
+ },
50
+ loadingContainer: {
51
+ flex: 1,
52
+ justifyContent: 'center',
53
+ alignItems: 'center',
54
+ },
55
+ })