@slingr/cli 0.0.3 → 0.0.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.
Files changed (251) hide show
  1. package/LICENSE.txt +202 -0
  2. package/README.md +490 -319
  3. package/bin/dev.cmd +2 -2
  4. package/bin/dev.js +5 -5
  5. package/bin/run.cmd +2 -2
  6. package/bin/run.js +4 -4
  7. package/bin/slingr +1 -0
  8. package/dist/commands/build.d.ts +20 -0
  9. package/dist/commands/build.d.ts.map +1 -0
  10. package/dist/commands/build.js +206 -0
  11. package/dist/commands/build.js.map +1 -0
  12. package/dist/commands/create-app.d.ts +0 -1
  13. package/dist/commands/create-app.d.ts.map +1 -1
  14. package/dist/commands/create-app.js +38 -57
  15. package/dist/commands/create-app.js.map +1 -1
  16. package/dist/commands/debug.d.ts +28 -0
  17. package/dist/commands/debug.d.ts.map +1 -0
  18. package/dist/commands/debug.js +474 -0
  19. package/dist/commands/debug.js.map +1 -0
  20. package/dist/commands/ds.d.ts +14 -1
  21. package/dist/commands/ds.d.ts.map +1 -1
  22. package/dist/commands/ds.js +450 -121
  23. package/dist/commands/ds.js.map +1 -1
  24. package/dist/commands/gql.d.ts +1 -1
  25. package/dist/commands/gql.d.ts.map +1 -1
  26. package/dist/commands/gql.js +190 -184
  27. package/dist/commands/gql.js.map +1 -1
  28. package/dist/commands/infra/down.d.ts.map +1 -1
  29. package/dist/commands/infra/down.js +8 -7
  30. package/dist/commands/infra/down.js.map +1 -1
  31. package/dist/commands/infra/up.d.ts.map +1 -1
  32. package/dist/commands/infra/up.js +8 -7
  33. package/dist/commands/infra/up.js.map +1 -1
  34. package/dist/commands/infra/update.d.ts +1 -0
  35. package/dist/commands/infra/update.d.ts.map +1 -1
  36. package/dist/commands/infra/update.js +33 -69
  37. package/dist/commands/infra/update.js.map +1 -1
  38. package/dist/commands/run.d.ts +29 -2
  39. package/dist/commands/run.d.ts.map +1 -1
  40. package/dist/commands/run.js +628 -130
  41. package/dist/commands/run.js.map +1 -1
  42. package/dist/commands/setup.d.ts +1 -1
  43. package/dist/commands/setup.d.ts.map +1 -1
  44. package/dist/commands/setup.js +34 -71
  45. package/dist/commands/setup.js.map +1 -1
  46. package/dist/commands/sync-metadata.d.ts +15 -0
  47. package/dist/commands/sync-metadata.d.ts.map +1 -0
  48. package/dist/commands/sync-metadata.js +225 -0
  49. package/dist/commands/sync-metadata.js.map +1 -0
  50. package/dist/commands/users.d.ts +30 -0
  51. package/dist/commands/users.d.ts.map +1 -0
  52. package/dist/commands/users.js +472 -0
  53. package/dist/commands/users.js.map +1 -0
  54. package/dist/commands/views.d.ts +11 -0
  55. package/dist/commands/views.d.ts.map +1 -0
  56. package/dist/commands/views.js +73 -0
  57. package/dist/commands/views.js.map +1 -0
  58. package/dist/projectStructure.d.ts +2 -2
  59. package/dist/projectStructure.d.ts.map +1 -1
  60. package/dist/projectStructure.js +281 -69
  61. package/dist/projectStructure.js.map +1 -1
  62. package/dist/scripts/generate-metadata.d.ts +13 -0
  63. package/dist/scripts/generate-metadata.d.ts.map +1 -0
  64. package/dist/scripts/generate-metadata.js +412 -0
  65. package/dist/scripts/generate-metadata.js.map +1 -0
  66. package/dist/scripts/generate-metadata.ts +498 -0
  67. package/dist/scripts/generate-schema.d.ts +1 -1
  68. package/dist/scripts/generate-schema.js +168 -74
  69. package/dist/scripts/generate-schema.js.map +1 -1
  70. package/dist/scripts/generate-schema.ts +258 -143
  71. package/dist/templates/.env.template +23 -0
  72. package/dist/templates/.firebaserc.template +5 -0
  73. package/dist/templates/.github/copilot-instructions.md.template +652 -17
  74. package/dist/templates/backend/Dockerfile.template +30 -0
  75. package/dist/templates/config/datasource.ts.template +12 -9
  76. package/dist/templates/config/jest.config.ts +30 -30
  77. package/dist/templates/config/jest.setup.ts +1 -1
  78. package/dist/templates/config/tsconfig.json.template +50 -29
  79. package/dist/templates/dataSources/mysql.ts.template +16 -13
  80. package/dist/templates/dataSources/postgres.ts.template +15 -13
  81. package/dist/templates/dataset-generator-script.ts.template +139 -139
  82. package/dist/templates/datasets/mysql-default/.slingr-schema.json.template +5 -0
  83. package/dist/templates/datasets/mysql-default/Address.jsonl.template +3 -3
  84. package/dist/templates/datasets/mysql-default/App.jsonl.template +4 -4
  85. package/dist/templates/datasets/mysql-default/Company.jsonl.template +3 -3
  86. package/dist/templates/datasets/mysql-default/Person.jsonl.template +2 -2
  87. package/dist/templates/datasets/mysql-default/User.jsonl.template +1 -0
  88. package/dist/templates/datasets/mysql-default/instructions.md.template +1 -0
  89. package/dist/templates/datasets/postgres-default/.slingr-schema.json.template +5 -0
  90. package/dist/templates/datasets/postgres-default/Address.jsonl.template +3 -3
  91. package/dist/templates/datasets/postgres-default/App.jsonl.template +4 -4
  92. package/dist/templates/datasets/postgres-default/Company.jsonl.template +3 -3
  93. package/dist/templates/datasets/postgres-default/Person.jsonl.template +2 -2
  94. package/dist/templates/datasets/postgres-default/User.jsonl.template +1 -0
  95. package/dist/templates/datasets/postgres-default/instructions.md.template +1 -0
  96. package/dist/templates/docker-compose.prod-test.yml.template +32 -0
  97. package/dist/templates/docker-compose.yml.template +24 -0
  98. package/dist/templates/docs/app-description.md.template +33 -33
  99. package/dist/templates/firebase.json.template +68 -0
  100. package/dist/templates/frontend/.umirc.ts.template +23 -0
  101. package/dist/templates/frontend/package.json.template +45 -0
  102. package/dist/templates/frontend/public/config.json +6 -0
  103. package/dist/templates/frontend/public/logo.svg +6 -0
  104. package/dist/templates/frontend/src/app.tsx.template +44 -0
  105. package/dist/templates/frontend/src/global.less.template +117 -0
  106. package/dist/templates/frontend/src/layouts/MainLayout.tsx.template +75 -0
  107. package/dist/templates/frontend/src/types/graphql-augmentation.d.ts.template +44 -0
  108. package/dist/templates/frontend/src/views/customViews/user/UserCreateView.tsx.template +18 -0
  109. package/dist/templates/frontend/src/views/customViews/user/UserEditView.tsx.template +29 -0
  110. package/dist/templates/frontend/src/views/customViews/user/UserReadView.tsx.template +24 -0
  111. package/dist/templates/frontend/src/views/customViews/user/UserTableView.tsx.template +38 -0
  112. package/dist/templates/frontend/src/views/customViews/welcome.tsx.template +34 -0
  113. package/dist/templates/frontend/tsconfig.json.template +50 -0
  114. package/dist/templates/gql/codegen.yml.template +25 -25
  115. package/dist/templates/gql/index.ts.template +17 -24
  116. package/dist/templates/gql/operations.graphql.template +30 -30
  117. package/dist/templates/ops/README.md.template +1045 -0
  118. package/dist/templates/ops/cloudbuild.yaml.template +161 -0
  119. package/dist/templates/ops/scripts/_utils.js.template +217 -0
  120. package/dist/templates/ops/scripts/deploy.js.template +145 -0
  121. package/dist/templates/ops/scripts/setup-gcp.js.template +330 -0
  122. package/dist/templates/ops/scripts/setup-secrets.js.template +76 -0
  123. package/dist/templates/ops/scripts/test-prod-local.js.template +49 -0
  124. package/dist/templates/package.json.template +50 -38
  125. package/dist/templates/pnpm-workspace.yaml.template +3 -0
  126. package/dist/templates/prompt-analysis.md.template +110 -110
  127. package/dist/templates/prompt-script-generation.md.template +258 -258
  128. package/dist/templates/src/Address.ts.template +28 -31
  129. package/dist/templates/src/App.ts.template +17 -61
  130. package/dist/templates/src/Company.ts.template +41 -47
  131. package/dist/templates/src/Models.test.ts.template +654 -654
  132. package/dist/templates/src/Person.test.ts.template +289 -289
  133. package/dist/templates/src/Person.ts.template +90 -105
  134. package/dist/templates/src/actions/index.ts.template +11 -11
  135. package/dist/templates/src/auth/permissions.ts.template +34 -0
  136. package/dist/templates/src/data/App.ts.template +48 -0
  137. package/dist/templates/src/data/User.ts.template +35 -0
  138. package/dist/templates/src/types/gql.d.ts.template +17 -17
  139. package/dist/templates/vscode/extensions.json +4 -3
  140. package/dist/templates/vscode/settings.json +17 -11
  141. package/dist/templates/workspace-package.json.template +21 -0
  142. package/dist/utils/buildCache.d.ts +12 -0
  143. package/dist/utils/buildCache.d.ts.map +1 -0
  144. package/dist/utils/buildCache.js +102 -0
  145. package/dist/utils/buildCache.js.map +1 -0
  146. package/dist/utils/checkFramework.d.ts +27 -0
  147. package/dist/utils/checkFramework.d.ts.map +1 -0
  148. package/dist/utils/checkFramework.js +104 -0
  149. package/dist/utils/checkFramework.js.map +1 -0
  150. package/dist/utils/datasourceParser.d.ts +11 -0
  151. package/dist/utils/datasourceParser.d.ts.map +1 -1
  152. package/dist/utils/datasourceParser.js +154 -56
  153. package/dist/utils/datasourceParser.js.map +1 -1
  154. package/dist/utils/dockerManager.d.ts +25 -0
  155. package/dist/utils/dockerManager.d.ts.map +1 -0
  156. package/dist/utils/dockerManager.js +281 -0
  157. package/dist/utils/dockerManager.js.map +1 -0
  158. package/dist/utils/infraFileParser.d.ts +26 -0
  159. package/dist/utils/infraFileParser.d.ts.map +1 -0
  160. package/dist/utils/infraFileParser.js +75 -0
  161. package/dist/utils/infraFileParser.js.map +1 -0
  162. package/dist/utils/jsonlLoader.d.ts +91 -12
  163. package/dist/utils/jsonlLoader.d.ts.map +1 -1
  164. package/dist/utils/jsonlLoader.js +674 -63
  165. package/dist/utils/jsonlLoader.js.map +1 -1
  166. package/dist/utils/model-analyzer.d.ts.map +1 -1
  167. package/dist/utils/model-analyzer.js +67 -13
  168. package/dist/utils/model-analyzer.js.map +1 -1
  169. package/dist/utils/userManagement.d.ts +57 -0
  170. package/dist/utils/userManagement.d.ts.map +1 -0
  171. package/dist/utils/userManagement.js +288 -0
  172. package/dist/utils/userManagement.js.map +1 -0
  173. package/dist/utils/viewsGenerator.d.ts +15 -0
  174. package/dist/utils/viewsGenerator.d.ts.map +1 -0
  175. package/dist/utils/viewsGenerator.js +311 -0
  176. package/dist/utils/viewsGenerator.js.map +1 -0
  177. package/oclif.manifest.json +445 -20
  178. package/package.json +29 -26
  179. package/src/templates/.env.template +23 -0
  180. package/src/templates/.firebaserc.template +5 -0
  181. package/src/templates/.github/copilot-instructions.md.template +652 -17
  182. package/src/templates/backend/Dockerfile.template +30 -0
  183. package/src/templates/config/datasource.ts.template +12 -9
  184. package/src/templates/config/jest.config.ts +30 -30
  185. package/src/templates/config/jest.setup.ts +1 -1
  186. package/src/templates/config/tsconfig.json.template +50 -29
  187. package/src/templates/dataSources/mysql.ts.template +16 -13
  188. package/src/templates/dataSources/postgres.ts.template +15 -13
  189. package/src/templates/dataset-generator-script.ts.template +139 -139
  190. package/src/templates/datasets/mysql-default/.slingr-schema.json.template +5 -0
  191. package/src/templates/datasets/mysql-default/Address.jsonl.template +3 -3
  192. package/src/templates/datasets/mysql-default/App.jsonl.template +4 -4
  193. package/src/templates/datasets/mysql-default/Company.jsonl.template +3 -3
  194. package/src/templates/datasets/mysql-default/Person.jsonl.template +2 -2
  195. package/src/templates/datasets/mysql-default/User.jsonl.template +1 -0
  196. package/src/templates/datasets/mysql-default/instructions.md.template +1 -0
  197. package/src/templates/datasets/postgres-default/.slingr-schema.json.template +5 -0
  198. package/src/templates/datasets/postgres-default/Address.jsonl.template +3 -3
  199. package/src/templates/datasets/postgres-default/App.jsonl.template +4 -4
  200. package/src/templates/datasets/postgres-default/Company.jsonl.template +3 -3
  201. package/src/templates/datasets/postgres-default/Person.jsonl.template +2 -2
  202. package/src/templates/datasets/postgres-default/User.jsonl.template +1 -0
  203. package/src/templates/datasets/postgres-default/instructions.md.template +1 -0
  204. package/src/templates/docker-compose.prod-test.yml.template +32 -0
  205. package/src/templates/docker-compose.yml.template +24 -0
  206. package/src/templates/docs/app-description.md.template +33 -33
  207. package/src/templates/firebase.json.template +68 -0
  208. package/src/templates/frontend/.umirc.ts.template +23 -0
  209. package/src/templates/frontend/package.json.template +45 -0
  210. package/src/templates/frontend/public/config.json +6 -0
  211. package/src/templates/frontend/public/logo.svg +6 -0
  212. package/src/templates/frontend/src/app.tsx.template +44 -0
  213. package/src/templates/frontend/src/global.less.template +117 -0
  214. package/src/templates/frontend/src/layouts/MainLayout.tsx.template +75 -0
  215. package/src/templates/frontend/src/types/graphql-augmentation.d.ts.template +44 -0
  216. package/src/templates/frontend/src/views/customViews/user/UserCreateView.tsx.template +18 -0
  217. package/src/templates/frontend/src/views/customViews/user/UserEditView.tsx.template +29 -0
  218. package/src/templates/frontend/src/views/customViews/user/UserReadView.tsx.template +24 -0
  219. package/src/templates/frontend/src/views/customViews/user/UserTableView.tsx.template +38 -0
  220. package/src/templates/frontend/src/views/customViews/welcome.tsx.template +34 -0
  221. package/src/templates/frontend/tsconfig.json.template +50 -0
  222. package/src/templates/gql/codegen.yml.template +25 -25
  223. package/src/templates/gql/index.ts.template +17 -24
  224. package/src/templates/gql/operations.graphql.template +30 -30
  225. package/src/templates/ops/README.md.template +1045 -0
  226. package/src/templates/ops/cloudbuild.yaml.template +161 -0
  227. package/src/templates/ops/scripts/_utils.js.template +217 -0
  228. package/src/templates/ops/scripts/deploy.js.template +145 -0
  229. package/src/templates/ops/scripts/setup-gcp.js.template +330 -0
  230. package/src/templates/ops/scripts/setup-secrets.js.template +76 -0
  231. package/src/templates/ops/scripts/test-prod-local.js.template +49 -0
  232. package/src/templates/package.json.template +50 -38
  233. package/src/templates/pnpm-workspace.yaml.template +3 -0
  234. package/src/templates/prompt-analysis.md.template +110 -110
  235. package/src/templates/prompt-script-generation.md.template +258 -258
  236. package/src/templates/src/Address.ts.template +28 -31
  237. package/src/templates/src/App.ts.template +17 -61
  238. package/src/templates/src/Company.ts.template +41 -47
  239. package/src/templates/src/Models.test.ts.template +654 -654
  240. package/src/templates/src/Person.test.ts.template +289 -289
  241. package/src/templates/src/Person.ts.template +90 -105
  242. package/src/templates/src/actions/index.ts.template +11 -11
  243. package/src/templates/src/auth/permissions.ts.template +34 -0
  244. package/src/templates/src/data/App.ts.template +48 -0
  245. package/src/templates/src/data/User.ts.template +35 -0
  246. package/src/templates/src/types/gql.d.ts.template +17 -17
  247. package/src/templates/vscode/extensions.json +4 -3
  248. package/src/templates/vscode/settings.json +17 -11
  249. package/src/templates/workspace-package.json.template +21 -0
  250. package/dist/templates/src/index.ts +0 -66
  251. package/src/templates/src/index.ts +0 -66
@@ -0,0 +1,161 @@
1
+ # Cloud Build pipeline for {{APP_NAME}}
2
+ # Triggered on push to the `develop` branch.
3
+ #
4
+ # Required substitutions (set in the Cloud Build trigger):
5
+ # _PROJECT_ID GCP project ID
6
+ # _REGION Cloud Run region (e.g. us-central1)
7
+ # _AR_REPO Artifact Registry repo name
8
+ # _BACKEND_SERVICE Cloud Run backend service name
9
+ # _BACKEND_SA Backend service account email
10
+ # _CLOUDSQL_INSTANCE Full instance connection name (PROJECT:REGION:INSTANCE)
11
+ # _DB_SECRET_NAME Secret Manager secret name for DB password
12
+ # _JWT_SECRET_NAME Secret Manager secret name for JWT signing key
13
+ # _FIREBASE_PROJECT Firebase / GCP project ID
14
+
15
+ substitutions:
16
+ _REGION: us-central1
17
+ _AR_REPO: slingr-apps
18
+ _BACKEND_SERVICE: {{APP_NAME}}-backend
19
+
20
+ options:
21
+ logging: CLOUD_LOGGING_ONLY
22
+
23
+ steps:
24
+ # ── Backend ──────────────────────────────────────────────────────────────
25
+
26
+ - id: pull-backend-cache
27
+ name: gcr.io/cloud-builders/docker
28
+ entrypoint: bash
29
+ args:
30
+ - -c
31
+ - |
32
+ docker pull ${_REGION}-docker.pkg.dev/${_PROJECT_ID}/${_AR_REPO}/${_BACKEND_SERVICE}:latest \
33
+ || echo "No cache found, proceeding without it."
34
+
35
+ - id: build-backend
36
+ name: gcr.io/cloud-builders/docker
37
+ args:
38
+ - build
39
+ - --cache-from=${_REGION}-docker.pkg.dev/${_PROJECT_ID}/${_AR_REPO}/${_BACKEND_SERVICE}:latest
40
+ - -f
41
+ - backend/Dockerfile
42
+ - -t
43
+ - ${_REGION}-docker.pkg.dev/${_PROJECT_ID}/${_AR_REPO}/${_BACKEND_SERVICE}:$COMMIT_SHA
44
+ - -t
45
+ - ${_REGION}-docker.pkg.dev/${_PROJECT_ID}/${_AR_REPO}/${_BACKEND_SERVICE}:latest
46
+ - .
47
+ waitFor:
48
+ - pull-backend-cache
49
+
50
+ - id: push-backend
51
+ name: gcr.io/cloud-builders/docker
52
+ args:
53
+ - push
54
+ - --all-tags
55
+ - ${_REGION}-docker.pkg.dev/${_PROJECT_ID}/${_AR_REPO}/${_BACKEND_SERVICE}
56
+ waitFor:
57
+ - build-backend
58
+
59
+ # ── Schema sync (Cloud Run Job) ────────────────────────────────────────────
60
+ - id: schema-sync
61
+ name: gcr.io/google.com/cloudsdktool/cloud-sdk:alpine
62
+ entrypoint: bash
63
+ args:
64
+ - -c
65
+ - |
66
+ set -e
67
+ IMAGE="${_REGION}-docker.pkg.dev/${_PROJECT_ID}/${_AR_REPO}/${_BACKEND_SERVICE}:$COMMIT_SHA"
68
+ JOB_NAME="{{APP_NAME}}-migrate"
69
+
70
+ gcloud run jobs create "$$JOB_NAME" \
71
+ --image="$$IMAGE" \
72
+ --region=${_REGION} \
73
+ --service-account=${_BACKEND_SA} \
74
+ --add-cloudsql-instances=${_CLOUDSQL_INSTANCE} \
75
+ --set-secrets="DB_PASSWORD=${_DB_SECRET_NAME}:latest" \
76
+ --set-env-vars="DB_HOST=/cloudsql/${_CLOUDSQL_INSTANCE},DB_USER=postgres,DB_NAME={{APP_NAME}},DB_SYNCHRONIZE=true,NODE_ENV=migration" \
77
+ --max-retries=1 \
78
+ --project=${_PROJECT_ID} \
79
+ 2>/dev/null \
80
+ || gcloud run jobs update "$$JOB_NAME" \
81
+ --image="$$IMAGE" \
82
+ --region=${_REGION} \
83
+ --service-account=${_BACKEND_SA} \
84
+ --add-cloudsql-instances=${_CLOUDSQL_INSTANCE} \
85
+ --set-secrets="DB_PASSWORD=${_DB_SECRET_NAME}:latest" \
86
+ --set-env-vars="DB_HOST=/cloudsql/${_CLOUDSQL_INSTANCE},DB_USER=postgres,DB_NAME={{APP_NAME}},DB_SYNCHRONIZE=true,NODE_ENV=migration" \
87
+ --max-retries=1 \
88
+ --project=${_PROJECT_ID}
89
+
90
+ gcloud run jobs execute "$$JOB_NAME" \
91
+ --region=${_REGION} \
92
+ --wait \
93
+ --project=${_PROJECT_ID}
94
+ waitFor:
95
+ - push-backend
96
+
97
+ # ── Deploy backend ─────────────────────────────────────────────────────────
98
+ - id: deploy-backend
99
+ name: gcr.io/google.com/cloudsdktool/cloud-sdk:alpine
100
+ entrypoint: gcloud
101
+ args:
102
+ - run
103
+ - deploy
104
+ - ${_BACKEND_SERVICE}
105
+ - --image=${_REGION}-docker.pkg.dev/${_PROJECT_ID}/${_AR_REPO}/${_BACKEND_SERVICE}:$COMMIT_SHA
106
+ - --region=${_REGION}
107
+ - --platform=managed
108
+ - --service-account=${_BACKEND_SA}
109
+ - --add-cloudsql-instances=${_CLOUDSQL_INSTANCE}
110
+ - --set-secrets=DB_PASSWORD=${_DB_SECRET_NAME}:latest,JWT_SECRET=${_JWT_SECRET_NAME}:latest
111
+ - --set-env-vars=DB_HOST=/cloudsql/${_CLOUDSQL_INSTANCE},DB_USER=postgres,DB_NAME={{APP_NAME}},DB_SYNCHRONIZE=false,NODE_ENV=production,HOST=0.0.0.0
112
+ - --allow-unauthenticated
113
+ - --project=${_PROJECT_ID}
114
+ waitFor:
115
+ - schema-sync
116
+
117
+ # ── Build & deploy frontend (Firebase Hosting) ─────────────────────────────
118
+ - id: build-frontend
119
+ name: node:20-alpine
120
+ entrypoint: sh
121
+ args:
122
+ - -c
123
+ - |
124
+ set -e
125
+ apk add --no-cache git
126
+
127
+ # Install app dependencies (framework comes from npm registry)
128
+ npm install
129
+
130
+ # Generate view registries, GraphQL schema & SDK types
131
+ npx slingr sync-metadata
132
+
133
+ # Build the frontend
134
+ cd frontend
135
+ npm ci
136
+ npm run build
137
+ waitFor:
138
+ - push-backend
139
+
140
+ - id: deploy-frontend
141
+ name: node:20-alpine
142
+ entrypoint: sh
143
+ args:
144
+ - -c
145
+ - |
146
+ set -e
147
+ npm install -g firebase-tools
148
+ firebase deploy --only hosting \
149
+ --project=${_FIREBASE_PROJECT} \
150
+ --non-interactive \
151
+ --token="$$FIREBASE_TOKEN"
152
+ secretEnv:
153
+ - FIREBASE_TOKEN
154
+ waitFor:
155
+ - build-frontend
156
+ - deploy-backend
157
+
158
+ availableSecrets:
159
+ secretManager:
160
+ - versionName: projects/${_PROJECT_ID}/secrets/firebase-ci-token/versions/latest
161
+ env: FIREBASE_TOKEN
@@ -0,0 +1,217 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawn, spawnSync } = require('child_process');
4
+ const net = require('net');
5
+ const readline = require('readline');
6
+
7
+ function escapeWindowsArg(arg) {
8
+ if (/^[A-Za-z0-9_./:-]+$/.test(arg)) {
9
+ return arg;
10
+ }
11
+
12
+ return `"${arg.replace(/"/g, '\\"')}"`;
13
+ }
14
+
15
+ function buildCommand(command, args = []) {
16
+ if (process.platform !== 'win32') {
17
+ return { command, args };
18
+ }
19
+
20
+ const fullCommand = [command, ...args.map(escapeWindowsArg)].join(' ');
21
+ return {
22
+ command: 'cmd.exe',
23
+ args: ['/d', '/s', '/c', fullCommand],
24
+ };
25
+ }
26
+
27
+ function run(command, args = [], options = {}) {
28
+ const {
29
+ allowFailure = false,
30
+ cwd,
31
+ env,
32
+ input,
33
+ suppressOutput = false,
34
+ } = options;
35
+
36
+ const stdio = suppressOutput
37
+ ? ['pipe', 'pipe', 'pipe']
38
+ : input !== undefined
39
+ ? ['pipe', 'inherit', 'inherit']
40
+ : 'inherit';
41
+
42
+ const executable = buildCommand(command, args);
43
+
44
+ const result = spawnSync(executable.command, executable.args, {
45
+ cwd,
46
+ env,
47
+ stdio,
48
+ encoding: 'utf8',
49
+ input,
50
+ });
51
+
52
+ if (result.error) {
53
+ throw result.error;
54
+ }
55
+
56
+ if ((result.status ?? 1) !== 0 && !allowFailure) {
57
+ throw new Error(`Command failed (${result.status}): ${command} ${args.join(' ')}`);
58
+ }
59
+
60
+ return result;
61
+ }
62
+
63
+ function runCapture(command, args = [], options = {}) {
64
+ const { allowFailure = false, cwd, env } = options;
65
+
66
+ const executable = buildCommand(command, args);
67
+
68
+ const result = spawnSync(executable.command, executable.args, {
69
+ cwd,
70
+ env,
71
+ stdio: ['ignore', 'pipe', 'pipe'],
72
+ encoding: 'utf8',
73
+ });
74
+
75
+ if (result.error) {
76
+ throw result.error;
77
+ }
78
+
79
+ if ((result.status ?? 1) !== 0) {
80
+ if (allowFailure) {
81
+ return '';
82
+ }
83
+
84
+ const stderr = (result.stderr || '').trim();
85
+ throw new Error(`Command failed (${result.status}): ${command} ${args.join(' ')}${stderr ? `\n${stderr}` : ''}`);
86
+ }
87
+
88
+ return (result.stdout || '').trim();
89
+ }
90
+
91
+ function step(label) {
92
+ console.log(`==> ${label}`);
93
+ }
94
+
95
+ function sleep(ms) {
96
+ return new Promise(resolve => setTimeout(resolve, ms));
97
+ }
98
+
99
+ function waitForPort(host, port, timeoutMs = 15000, intervalMs = 1000) {
100
+ const start = Date.now();
101
+
102
+ return new Promise(async (resolve, reject) => {
103
+ while (Date.now() - start < timeoutMs) {
104
+ const connected = await new Promise(res => {
105
+ const socket = net.connect({ host, port });
106
+
107
+ socket.once('connect', () => {
108
+ socket.end();
109
+ res(true);
110
+ });
111
+
112
+ socket.once('error', () => {
113
+ socket.destroy();
114
+ res(false);
115
+ });
116
+ });
117
+
118
+ if (connected) {
119
+ resolve();
120
+ return;
121
+ }
122
+
123
+ await sleep(intervalMs);
124
+ }
125
+
126
+ reject(new Error(`Timed out waiting for ${host}:${port}`));
127
+ });
128
+ }
129
+
130
+ function prompt(message, options = {}) {
131
+ const { hidden = false } = options;
132
+
133
+ if (!hidden) {
134
+ const rl = readline.createInterface({
135
+ input: process.stdin,
136
+ output: process.stdout,
137
+ });
138
+
139
+ return new Promise(resolve => {
140
+ rl.question(message, answer => {
141
+ rl.close();
142
+ resolve(answer);
143
+ });
144
+ });
145
+ }
146
+
147
+ return new Promise((resolve, reject) => {
148
+ const stdin = process.stdin;
149
+ let value = '';
150
+
151
+ process.stdout.write(message);
152
+
153
+ const cleanup = () => {
154
+ stdin.removeListener('data', onData);
155
+ if (stdin.isTTY) {
156
+ stdin.setRawMode(false);
157
+ }
158
+ stdin.pause();
159
+ process.stdout.write('\n');
160
+ };
161
+
162
+ const onData = chunk => {
163
+ const char = chunk.toString('utf8');
164
+
165
+ if (char === '\u0003') {
166
+ cleanup();
167
+ reject(new Error('Prompt cancelled.'));
168
+ return;
169
+ }
170
+
171
+ if (char === '\r' || char === '\n') {
172
+ cleanup();
173
+ resolve(value);
174
+ return;
175
+ }
176
+
177
+ if (char === '\u0008' || char === '\u007f') {
178
+ value = value.slice(0, -1);
179
+ return;
180
+ }
181
+
182
+ value += char;
183
+ };
184
+
185
+ stdin.setEncoding('utf8');
186
+ stdin.resume();
187
+ if (stdin.isTTY) {
188
+ stdin.setRawMode(true);
189
+ }
190
+ stdin.on('data', onData);
191
+ });
192
+ }
193
+
194
+ function getProjectIdFromGcloud() {
195
+ return runCapture('gcloud', ['config', 'get-value', 'project'], { allowFailure: true });
196
+ }
197
+
198
+ function resolveCommand(command) {
199
+ if (process.platform === 'win32') {
200
+ return 'cmd.exe';
201
+ }
202
+
203
+ return command;
204
+ }
205
+
206
+ module.exports = {
207
+ getProjectIdFromGcloud,
208
+ prompt,
209
+ run,
210
+ runCapture,
211
+ sleep,
212
+ step,
213
+ waitForPort,
214
+ resolveCommand,
215
+ buildCommand,
216
+ spawn,
217
+ };
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env node
2
+ // deploy.js - Manual deployment of {{APP_NAME}} to GCP.
3
+ // Mirrors the Cloud Build pipeline. Run from the app root directory.
4
+ //
5
+ // Prerequisites: setup-gcp.js + setup-secrets.js completed,
6
+ // gcloud init run, Docker running.
7
+ //
8
+ // Usage: node ops/scripts/deploy.js [git-tag]
9
+ // Override project or region: PROJECT_ID=my-project REGION=europe-west1 node ops/scripts/deploy.js
10
+
11
+ const path = require('path');
12
+ const { getProjectIdFromGcloud, run, runCapture, step } = require('./_utils');
13
+
14
+ function main() {
15
+ const projectId = process.env.PROJECT_ID || getProjectIdFromGcloud();
16
+ const region = process.env.REGION || 'us-central1';
17
+
18
+ if (!projectId || projectId === '(unset)') {
19
+ throw new Error('No active GCP project. Run gcloud init first, or pass PROJECT_ID as an env var.');
20
+ }
21
+
22
+ const arRepo = 'slingr-apps';
23
+ const backendService = '{{APP_NAME}}-backend';
24
+ const backendSa = `{{APP_NAME}}-backend@${projectId}.iam.gserviceaccount.com`;
25
+ const cloudsqlInstance = `${projectId}:${region}:{{APP_NAME}}-db`;
26
+ const dbSecretName = '{{APP_NAME}}-db-password';
27
+ const jwtSecretName = '{{APP_NAME}}-jwt-secret';
28
+ const frontendBucket = `${projectId}-{{APP_NAME}}-frontend`;
29
+ const fwdRuleName = '{{APP_NAME}}-http-rule';
30
+
31
+ const appDir = path.resolve(__dirname, '..', '..');
32
+ const repoRoot = runCapture('git', ['rev-parse', '--show-toplevel'], { cwd: appDir });
33
+
34
+ // In a monorepo the app lives inside the framework repo — build the framework
35
+ // from source. In standalone mode the framework is already installed as an
36
+ // npm dependency so there is nothing to build.
37
+ const isMonorepo = repoRoot !== appDir;
38
+
39
+ const imageBase = `${region}-docker.pkg.dev/${projectId}/${arRepo}/${backendService}`;
40
+ const tag = process.argv[2] || runCapture('git', ['rev-parse', '--short', 'HEAD'], { cwd: appDir });
41
+
42
+ console.log(`==> Deploying {{APP_NAME}} (tag: ${tag})`);
43
+
44
+ step('Building framework and CLI...');
45
+ if (isMonorepo) {
46
+ run('pnpm', ['run', 'build:all'], { cwd: repoRoot });
47
+ } else {
48
+ console.log(' (standalone mode — framework installed as npm dependency, skipping)');
49
+ }
50
+
51
+ step('Generating metadata (views, GraphQL types)...');
52
+ run('npx', ['slingr', 'sync-metadata'], { cwd: appDir });
53
+
54
+ step('Building app (backend + frontend)...');
55
+ run('npm', ['run', 'build:all'], { cwd: appDir });
56
+
57
+ run('docker', ['pull', `${imageBase}:latest`], { allowFailure: true, suppressOutput: true });
58
+ run('docker', [
59
+ 'build',
60
+ `--cache-from=${imageBase}:latest`,
61
+ '-f',
62
+ 'backend/Dockerfile',
63
+ '-t',
64
+ `${imageBase}:${tag}`,
65
+ '-t',
66
+ `${imageBase}:latest`,
67
+ '.',
68
+ ], { cwd: appDir });
69
+ run('docker', ['push', `${imageBase}:${tag}`]);
70
+ run('docker', ['push', `${imageBase}:latest`]);
71
+
72
+ const jobName = '{{APP_NAME}}-migrate';
73
+ const migrateArgs = [
74
+ `--image=${imageBase}:${tag}`,
75
+ `--region=${region}`,
76
+ `--service-account=${backendSa}`,
77
+ `--set-cloudsql-instances=${cloudsqlInstance}`,
78
+ `--set-secrets=DB_PASSWORD=${dbSecretName}:latest`,
79
+ `--set-env-vars=DB_HOST=/cloudsql/${cloudsqlInstance},DB_USER=postgres,DB_NAME={{APP_NAME}},DB_SYNCHRONIZE=true,NODE_ENV=migration`,
80
+ '--max-retries=1',
81
+ `--project=${projectId}`,
82
+ ];
83
+
84
+ const jobExists = run(
85
+ 'gcloud',
86
+ ['run', 'jobs', 'describe', jobName, `--region=${region}`, `--project=${projectId}`],
87
+ { allowFailure: true, suppressOutput: true }
88
+ ).status === 0;
89
+
90
+ if (jobExists) {
91
+ run('gcloud', ['run', 'jobs', 'update', jobName, ...migrateArgs]);
92
+ } else {
93
+ run('gcloud', ['run', 'jobs', 'create', jobName, ...migrateArgs]);
94
+ }
95
+
96
+ run('gcloud', ['run', 'jobs', 'execute', jobName, `--region=${region}`, '--wait', `--project=${projectId}`]);
97
+
98
+ run('gcloud', [
99
+ 'run',
100
+ 'deploy',
101
+ backendService,
102
+ `--image=${imageBase}:${tag}`,
103
+ `--region=${region}`,
104
+ '--platform=managed',
105
+ `--service-account=${backendSa}`,
106
+ `--add-cloudsql-instances=${cloudsqlInstance}`,
107
+ `--set-secrets=DB_PASSWORD=${dbSecretName}:latest,JWT_SECRET=${jwtSecretName}:latest`,
108
+ `--set-env-vars=DB_HOST=/cloudsql/${cloudsqlInstance},DB_PORT=5432,DB_USER=postgres,DB_NAME={{APP_NAME}},DB_SYNCHRONIZE=false,NODE_ENV=production,HOST=0.0.0.0`,
109
+ '--allow-unauthenticated',
110
+ `--project=${projectId}`,
111
+ ]);
112
+
113
+ run('gsutil', ['-m', 'rsync', '-r', '-d', 'frontend/dist', `gs://${frontendBucket}`], { cwd: appDir });
114
+
115
+ console.log('');
116
+ console.log('✓ Deployment complete.');
117
+ const backendUrl = runCapture('gcloud', [
118
+ 'run',
119
+ 'services',
120
+ 'describe',
121
+ backendService,
122
+ `--region=${region}`,
123
+ `--project=${projectId}`,
124
+ '--format=value(status.url)',
125
+ ]);
126
+ const lbIp = runCapture('gcloud', [
127
+ 'compute',
128
+ 'forwarding-rules',
129
+ 'describe',
130
+ fwdRuleName,
131
+ '--global',
132
+ '--format=value(IPAddress)',
133
+ `--project=${projectId}`,
134
+ ]);
135
+
136
+ console.log(` Backend : ${backendUrl}`);
137
+ console.log(` Frontend : http://${lbIp} (or your custom domain if configured)`);
138
+ }
139
+
140
+ try {
141
+ main();
142
+ } catch (error) {
143
+ console.error(`ERROR: ${error.message}`);
144
+ process.exit(1);
145
+ }