@edgedev/create-edge-app 1.1.29 → 1.2.30

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 (32) hide show
  1. package/agents.md +2 -0
  2. package/bin/cli.js +13 -5
  3. package/deploy-services.sh +237 -0
  4. package/deploy.sh +88 -1
  5. package/edge/components/cms/blockEditor.vue +748 -7
  6. package/{pages/app/dashboard/blocks/index.vue → edge/components/cms/blocksManager.vue} +9 -24
  7. package/edge/components/cms/menu.vue +100 -15
  8. package/edge/components/cms/site.vue +83 -3
  9. package/edge/components/cms/sitesManager.vue +110 -0
  10. package/edge/components/cms/themeEditor.vue +9 -3
  11. package/edge/components/dashboard.vue +22 -3
  12. package/edge/components/editor.vue +100 -35
  13. package/edge/components/organizationMembers.vue +294 -221
  14. package/edge/components/shad/combobox.vue +2 -2
  15. package/edge/composables/global.ts +1 -1
  16. package/edge/routes/cms/dashboard/blocks/index.vue +21 -0
  17. package/edge/routes/cms/dashboard/sites/index.vue +13 -0
  18. package/edge/routes/cms/nuxtHooks.js +52 -0
  19. package/edge/routes/cms/routes.js +56 -0
  20. package/firebase_init.sh +63 -2
  21. package/nuxt.config.ts +19 -2
  22. package/package.json +1 -1
  23. package/services/.deploy.shared.env.example +12 -0
  24. package/pages/app/dashboard/sites/index.vue +0 -114
  25. /package/{pages/app → edge/routes/cms}/dashboard/blocks/[block].vue +0 -0
  26. /package/{pages/app → edge/routes/cms}/dashboard/media/index.vue +0 -0
  27. /package/{pages/app → edge/routes/cms}/dashboard/sites/[site]/[[page]].vue +0 -0
  28. /package/{pages/app → edge/routes/cms}/dashboard/sites/[site].vue +0 -0
  29. /package/{pages/app → edge/routes/cms}/dashboard/templates/[page].vue +0 -0
  30. /package/{pages/app/dashboard/templates.vue → edge/routes/cms/dashboard/templates/index.vue} +0 -0
  31. /package/{pages/app → edge/routes/cms}/dashboard/themes/[theme].vue +0 -0
  32. /package/{pages/app → edge/routes/cms}/dashboard/themes/index.vue +0 -0
@@ -0,0 +1,21 @@
1
+ <script setup>
2
+ const state = reactive({
3
+ mounted: false,
4
+ })
5
+
6
+ definePageMeta({
7
+ middleware: 'auth',
8
+ })
9
+
10
+ onMounted(() => {
11
+ state.mounted = true
12
+ })
13
+ </script>
14
+
15
+ <template>
16
+ <div
17
+ v-if="edgeGlobal.edgeState.organizationDocPath && state.mounted"
18
+ >
19
+ <edge-cms-blocks-manager />
20
+ </div>
21
+ </template>
@@ -0,0 +1,13 @@
1
+ <script setup>
2
+ definePageMeta({
3
+ middleware: 'auth',
4
+ })
5
+ </script>
6
+
7
+ <template>
8
+ <div
9
+ v-if="edgeGlobal.edgeState.organizationDocPath"
10
+ >
11
+ <edge-cms-sites-manager />
12
+ </div>
13
+ </template>
@@ -0,0 +1,52 @@
1
+ import { resolve as resolvePath } from 'node:path'
2
+ import { cmsRoutes } from './routes'
3
+
4
+ const resolveRouteFileFromCwd = (file) => {
5
+ const normalizedPath = String(file || '').replace(/^\.\//, '')
6
+ return resolvePath(process.cwd(), normalizedPath)
7
+ }
8
+
9
+ export const createCmsNuxtHooks = (resolveFile) => {
10
+ const resolveRouteFile = typeof resolveFile === 'function'
11
+ ? resolveFile
12
+ : resolveRouteFileFromCwd
13
+
14
+ const routeExists = (routeList, name) => {
15
+ for (const route of routeList) {
16
+ if (route.name === name)
17
+ return true
18
+ if (route.children?.length && routeExists(route.children, name))
19
+ return true
20
+ }
21
+ return false
22
+ }
23
+
24
+ const resolveCmsRoute = (route, appRootRoute, isChild = false) => {
25
+ const shouldStripAppPrefix = Boolean(appRootRoute) && !isChild
26
+ const nextRoute = {
27
+ ...route,
28
+ path: shouldStripAppPrefix
29
+ ? route.path.replace(/^\/app\/?/, '')
30
+ : route.path,
31
+ file: resolveRouteFile(route.file),
32
+ }
33
+
34
+ if (route.children?.length)
35
+ nextRoute.children = route.children.map(child => resolveCmsRoute(child, appRootRoute, true))
36
+
37
+ return nextRoute
38
+ }
39
+
40
+ return {
41
+ 'pages:extend': (pages) => {
42
+ const appRootRoute = pages.find(page => page.path === '/app')
43
+ const targetRoutes = appRootRoute?.children || pages
44
+
45
+ cmsRoutes.forEach((route) => {
46
+ const routeToAdd = resolveCmsRoute(route, appRootRoute)
47
+ if (!routeExists(targetRoutes, routeToAdd.name))
48
+ targetRoutes.push(routeToAdd)
49
+ })
50
+ },
51
+ }
52
+ }
@@ -0,0 +1,56 @@
1
+ export const cmsRoutes = [
2
+ {
3
+ name: 'cms-dashboard-sites-index',
4
+ path: '/app/dashboard/sites',
5
+ file: './edge/routes/cms/dashboard/sites/index.vue',
6
+ },
7
+ {
8
+ name: 'cms-dashboard-blocks-index',
9
+ path: '/app/dashboard/blocks',
10
+ file: './edge/routes/cms/dashboard/blocks/index.vue',
11
+ },
12
+ {
13
+ name: 'cms-dashboard-blocks-block',
14
+ path: '/app/dashboard/blocks/:block',
15
+ file: './edge/routes/cms/dashboard/blocks/[block].vue',
16
+ },
17
+ {
18
+ name: 'cms-dashboard-media-index',
19
+ path: '/app/dashboard/media',
20
+ file: './edge/routes/cms/dashboard/media/index.vue',
21
+ },
22
+ {
23
+ name: 'cms-dashboard-sites-site',
24
+ path: '/app/dashboard/sites/:site',
25
+ file: './edge/routes/cms/dashboard/sites/[site].vue',
26
+ children: [
27
+ {
28
+ name: 'cms-dashboard-sites-site-page',
29
+ path: ':page',
30
+ file: './edge/routes/cms/dashboard/sites/[site]/[[page]].vue',
31
+ },
32
+ ],
33
+ },
34
+ {
35
+ name: 'cms-dashboard-templates-index',
36
+ path: '/app/dashboard/templates',
37
+ file: './edge/routes/cms/dashboard/templates/index.vue',
38
+ children: [
39
+ {
40
+ name: 'cms-dashboard-templates-page',
41
+ path: ':page',
42
+ file: './edge/routes/cms/dashboard/templates/[page].vue',
43
+ },
44
+ ],
45
+ },
46
+ {
47
+ name: 'cms-dashboard-themes-index',
48
+ path: '/app/dashboard/themes',
49
+ file: './edge/routes/cms/dashboard/themes/index.vue',
50
+ },
51
+ {
52
+ name: 'cms-dashboard-themes-theme',
53
+ path: '/app/dashboard/themes/:theme',
54
+ file: './edge/routes/cms/dashboard/themes/[theme].vue',
55
+ },
56
+ ]
package/firebase_init.sh CHANGED
@@ -1,3 +1,46 @@
1
+ # Install gcloud if missing (best-effort by platform)
2
+ ensure_gcloud() {
3
+ if command -v gcloud >/dev/null 2>&1; then
4
+ return 0
5
+ fi
6
+
7
+ echo "gcloud CLI not found. Attempting to install Google Cloud SDK..."
8
+
9
+ if command -v brew >/dev/null 2>&1; then
10
+ if ! brew list --cask google-cloud-sdk >/dev/null 2>&1; then
11
+ brew install --cask google-cloud-sdk || {
12
+ echo "Failed to install Google Cloud SDK with Homebrew."
13
+ exit 1
14
+ }
15
+ fi
16
+ elif command -v snap >/dev/null 2>&1; then
17
+ if [ "$(id -u)" -eq 0 ]; then
18
+ snap install google-cloud-cli --classic || {
19
+ echo "Failed to install Google Cloud SDK with Snap."
20
+ exit 1
21
+ }
22
+ elif command -v sudo >/dev/null 2>&1; then
23
+ sudo snap install google-cloud-cli --classic || {
24
+ echo "Failed to install Google Cloud SDK with Snap."
25
+ exit 1
26
+ }
27
+ else
28
+ echo "Snap install requires elevated privileges, but 'sudo' is unavailable."
29
+ exit 1
30
+ fi
31
+ else
32
+ echo "Unable to auto-install Google Cloud SDK on this system."
33
+ echo "Please install it manually: https://cloud.google.com/sdk/docs/install"
34
+ exit 1
35
+ fi
36
+
37
+ if ! command -v gcloud >/dev/null 2>&1; then
38
+ echo "gcloud is still not available in PATH after install."
39
+ echo "Open a new terminal and run this script again."
40
+ exit 1
41
+ fi
42
+ }
43
+
1
44
  # Prompt for the Firebase configuration values
2
45
  echo "Please enter your Firebase project ID:"
3
46
  read project_id
@@ -34,11 +77,29 @@ if [ -z "$project_id" ]; then
34
77
  fi
35
78
 
36
79
  # Check if firebase is installed
37
- if ! command -v firebase &> /dev/null; then
80
+ if ! command -v firebase >/dev/null 2>&1; then
38
81
  echo "Firebase CLI could not be found. Please install it and try again."
39
82
  exit 1
40
83
  fi
41
84
 
85
+ ensure_gcloud
86
+
87
+ # Initialize gcloud account/config if needed, then set active project
88
+ active_account="$(gcloud auth list --filter=status:ACTIVE --format='value(account)' 2>/dev/null)"
89
+ if [ -z "$active_account" ]; then
90
+ echo "No active gcloud account found. Running gcloud init..."
91
+ if ! gcloud init; then
92
+ echo "gcloud init failed."
93
+ exit 1
94
+ fi
95
+ fi
96
+
97
+ echo "Setting gcloud project to '$project_id'..."
98
+ if ! gcloud config set project "$project_id"; then
99
+ echo "Failed to set gcloud project to '$project_id'."
100
+ exit 1
101
+ fi
102
+
42
103
  # Backup firebase.json if it exists
43
104
  if [ -f ./firebase.json ]; then
44
105
  cp ./firebase.json ./firebase.json.temp
@@ -93,4 +154,4 @@ echo "VITE_FIREBASE_EMULATOR_FIRESTORE=8080" >> .env.dev
93
154
  echo "VITE_FIREBASE_EMULATOR_FUNCTIONS=5001" >> .env.dev
94
155
  echo "VITE_FIREBASE_EMULATOR_STORAGE=9199" >> .env.dev
95
156
  echo "REGISTRATION_CODE=organization-registration-template" >> .env.dev
96
- echo "DEVELOPMENT_MODE=true" >> .env.dev
157
+ echo "DEVELOPMENT_MODE=true" >> .env.dev
package/nuxt.config.ts CHANGED
@@ -1,3 +1,8 @@
1
+ import { createCmsNuxtHooks } from './edge/routes/cms/nuxtHooks'
2
+
3
+ const cmsRouteHooks = {}
4
+ Object.assign(cmsRouteHooks, createCmsNuxtHooks()) // Comment out this line to disable CMS routes.
5
+
1
6
  // https://nuxt.com/docs/api/configuration/nuxt-config
2
7
  export default defineNuxtConfig({
3
8
  ssr: false,
@@ -16,6 +21,12 @@ export default defineNuxtConfig({
16
21
  { rel: 'apple-touch-icon', sizes: '180x180', href: '/favicon/apple-touch-icon.png' },
17
22
  { rel: 'manifest', href: '/favicon/site.webmanifest' },
18
23
  ],
24
+ meta: [
25
+ {
26
+ 'http-equiv': 'Content-Security-Policy',
27
+ 'content': 'font-src \'self\' https://files.edgemarketingdesign.com https://use.typekit.net https://p.typekit.net data:;',
28
+ },
29
+ ],
19
30
  },
20
31
  },
21
32
  modules: ['@vant/nuxt', '@nuxtjs/tailwindcss', '@nuxtjs/color-mode', 'shadcn-nuxt'],
@@ -40,9 +51,11 @@ export default defineNuxtConfig({
40
51
  },
41
52
  components: {
42
53
  dirs: [
43
- { path: '~/components/formSubtypes', global: true, prefix: 'edge-form-subtypes' },
54
+ { path: '~/components' },
55
+
56
+ // Namespaced wrappers — keep them, but lower priority is fine
44
57
  { path: '~/edge/components', global: true, prefix: 'edge' },
45
- '~/components',
58
+ { path: '~/components/formSubtypes', global: true, prefix: 'edge-form-subtypes' },
46
59
  ],
47
60
  },
48
61
  vite: {
@@ -50,11 +63,15 @@ export default defineNuxtConfig({
50
63
  'process.env.DEBUG': false,
51
64
  },
52
65
  server: {
66
+ watch: {
67
+ ignored: ['**/.nuxt/**', '**/.output/**', '**/dist/**', '**/node_modules/**'],
68
+ },
53
69
  hmr: {
54
70
  port: 3000, // Make sure this port matches your Nuxt server port
55
71
  clientPort: 3000, // Ensure this matches your Nuxt server port as well
56
72
  },
57
73
  },
58
74
  },
75
+ hooks: cmsRouteHooks,
59
76
  devtools: { enabled: false },
60
77
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edgedev/create-edge-app",
3
- "version": "1.1.29",
3
+ "version": "1.2.30",
4
4
  "description": "Create Edge Starter App",
5
5
  "bin": {
6
6
  "create-edge-app": "./bin/cli.js"
@@ -0,0 +1,12 @@
1
+ # Optional shared deploy settings for all services.
2
+ # Copy to services/.deploy.shared.env and customize as needed.
3
+
4
+ # Defaults to .firebaserc project if omitted.
5
+ # PROJECT_ID=clearwater-hub
6
+
7
+ # Falls back to VITE_FIREBASE_FUNCTIONS_REGION from root .env if omitted.
8
+ # REGION=us-central1
9
+
10
+ # Comma-separated IAM members to grant Cloud Run Invoker when ALLOW_UNAUTHENTICATED=false.
11
+ # If member type is omitted, serviceAccount: is assumed by deploy-services.sh.
12
+ # INVOKER_MEMBERS=serviceAccount:201505998870-compute@developer.gserviceaccount.com
@@ -1,114 +0,0 @@
1
- <script setup>
2
- import { toTypedSchema } from '@vee-validate/zod'
3
- import * as z from 'zod'
4
- import { Loader2 } from 'lucide-vue-next'
5
- const route = useRoute()
6
- // const edgeGlobal = inject('edgeGlobal')
7
-
8
- const edgeFirebase = inject('edgeFirebase')
9
- const isAiBusy = status => status === 'queued' || status === 'running'
10
-
11
- const state = reactive({
12
- filter: '',
13
- newDocs: {
14
- sites: {
15
- name: { bindings: { 'field-type': 'text', 'label': 'Name', 'helper': 'Name' }, cols: '12', value: '' },
16
- },
17
- },
18
- })
19
-
20
- const schemas = {
21
- sites: toTypedSchema(z.object({
22
- name: z.string({
23
- required_error: 'Name is required',
24
- }).min(1, { message: 'Name is required' }),
25
- })),
26
- }
27
-
28
- const collection = computed(() => {
29
- if (route.params.collection) {
30
- return route.params.collection
31
- }
32
- return ''
33
- })
34
- const docId = computed(() => {
35
- if (route.params.docId) {
36
- return route.params.docId
37
- }
38
- return ''
39
- })
40
- definePageMeta({
41
- middleware: 'auth',
42
- })
43
-
44
- onBeforeMount(() => {
45
- // edgeGlobal.showLeftPanel(true)
46
- })
47
- const isAdmin = computed(() => {
48
- return edgeGlobal.isAdminGlobal(edgeFirebase).value
49
- })
50
- const queryField = computed(() => {
51
- if (!isAdmin.value) {
52
- return 'users'
53
- }
54
- return ''
55
- })
56
-
57
- const queryValue = computed(() => {
58
- if (!isAdmin.value) {
59
- return [edgeFirebase?.user?.uid]
60
- }
61
- return ''
62
- })
63
-
64
- const queryOperator = computed(() => {
65
- if (!isAdmin.value) {
66
- return 'array-contains-any'
67
- }
68
- return ''
69
- })
70
- </script>
71
-
72
- <template>
73
- <div
74
- v-if="edgeGlobal.edgeState.organizationDocPath"
75
- >
76
- <edge-dashboard :load-first-if-one="!isAdmin" :filter="state.filter" :query-field="queryField" :query-value="queryValue" :query-operator="queryOperator" collection="sites" class="flex-1 pt-0">
77
- <template #list="slotProps">
78
- <template v-for="item in slotProps.filtered" :key="item.docId">
79
- <edge-shad-button
80
- variant="text"
81
- class="cursor-pointer w-full flex justify-between items-center py-2 gap-3"
82
- :to="isAiBusy(item.aiBootstrapStatus) ? undefined : `/app/dashboard/sites/${item.docId}`"
83
- :disabled="isAiBusy(item.aiBootstrapStatus)"
84
- >
85
- <div>
86
- <Avatar class="cursor-pointer p-0 h-8 w-8 mr-2">
87
- <FilePenLine class="h-5 w-5" />
88
- </Avatar>
89
- </div>
90
- <div class="grow text-left">
91
- <div class="text-lg">
92
- {{ item.name }}
93
- </div>
94
- <div v-if="isAiBusy(item.aiBootstrapStatus)" class="flex items-center gap-2 text-xs text-muted-foreground">
95
- <Loader2 class="h-3 w-3 animate-spin" />
96
- <span>AI is preparing this site</span>
97
- </div>
98
- </div>
99
- <div>
100
- <edge-shad-button
101
- size="icon"
102
- class="bg-slate-600 h-7 w-7"
103
- @click.stop="slotProps.deleteItem(item.docId)"
104
- >
105
- <Trash class="h-5 w-5" />
106
- </edge-shad-button>
107
- </div>
108
- </edge-shad-button>
109
- <Separator class="dark:bg-slate-600" />
110
- </template>
111
- </template>
112
- </edge-dashboard>
113
- </div>
114
- </template>