@slingr/cli 0.0.2 → 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 -27
  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,330 @@
1
+ #!/usr/bin/env node
2
+ // setup-gcp.js - One-time GCP infrastructure setup for {{APP_NAME}}.
3
+ // Run this once per GCP project before the first deployment. Idempotent.
4
+ //
5
+ // Prerequisites:
6
+ // - gcloud init (sets authenticated account + default project)
7
+ // - Billing enabled on the project
8
+ // - Owner or Editor role on the project
9
+ //
10
+ // Usage:
11
+ // node ops/scripts/setup-gcp.js
12
+ //
13
+ // Override project or region if needed:
14
+ // PROJECT_ID=my-project REGION=europe-west1 node ops/scripts/setup-gcp.js
15
+
16
+ const { getProjectIdFromGcloud, prompt, run, runCapture, step } = require('./_utils');
17
+
18
+ function runIdempotent(args, message) {
19
+ const result = run('gcloud', args, { allowFailure: true, suppressOutput: true });
20
+ if ((result.status ?? 1) !== 0) {
21
+ console.log(` (${message})`);
22
+ }
23
+ }
24
+
25
+ async function main() {
26
+ const projectId = process.env.PROJECT_ID || getProjectIdFromGcloud();
27
+ const region = process.env.REGION || 'us-central1';
28
+
29
+ if (!projectId || projectId === '(unset)') {
30
+ throw new Error('No active GCP project. Run gcloud init first, or pass PROJECT_ID as an env var.');
31
+ }
32
+
33
+ const dbInstanceName = '{{APP_NAME}}-db';
34
+ const dbName = '{{APP_NAME}}';
35
+ const dbUser = '{{#if_postgres}}postgres{{/if_postgres}}{{#if_mysql}}root{{/if_mysql}}';
36
+ const arRepoName = 'slingr-apps';
37
+ const backendSaName = '{{APP_NAME}}-backend';
38
+ const backendService = '{{APP_NAME}}-backend';
39
+ const frontendBucket = `${projectId}-{{APP_NAME}}-frontend`;
40
+ const backendBucketRes = '{{APP_NAME}}-frontend-bucket';
41
+ const backendServiceRes = '{{APP_NAME}}-backend-service';
42
+ const negName = '{{APP_NAME}}-backend-neg';
43
+ const urlMapName = '{{APP_NAME}}-url-map';
44
+ const httpProxyName = '{{APP_NAME}}-http-proxy';
45
+ const fwdRuleName = '{{APP_NAME}}-http-rule';
46
+
47
+ const backendSa = `${backendSaName}@${projectId}.iam.gserviceaccount.com`;
48
+ const projectNumber = runCapture('gcloud', ['projects', 'describe', projectId, '--format=value(projectNumber)']);
49
+ const cloudbuildSa = `${projectNumber}@cloudbuild.gserviceaccount.com`;
50
+
51
+ step(`Setting active project to ${projectId}`);
52
+ run('gcloud', ['config', 'set', 'project', projectId]);
53
+
54
+ step('Enabling GCP APIs...');
55
+ run('gcloud', [
56
+ 'services',
57
+ 'enable',
58
+ 'run.googleapis.com',
59
+ 'sqladmin.googleapis.com',
60
+ 'artifactregistry.googleapis.com',
61
+ 'cloudbuild.googleapis.com',
62
+ 'secretmanager.googleapis.com',
63
+ 'storage.googleapis.com',
64
+ 'compute.googleapis.com',
65
+ ]);
66
+
67
+ step(`Creating Artifact Registry repository '${arRepoName}'...`);
68
+ runIdempotent(
69
+ [
70
+ 'artifacts',
71
+ 'repositories',
72
+ 'create',
73
+ arRepoName,
74
+ '--repository-format=docker',
75
+ `--location=${region}`,
76
+ '--description=Docker images for Slingr apps',
77
+ '--quiet',
78
+ ],
79
+ 'already exists'
80
+ );
81
+
82
+ run('gcloud', ['auth', 'configure-docker', `${region}-docker.pkg.dev`, '--quiet']);
83
+
84
+ step(`Creating Cloud SQL instance '${dbInstanceName}'...`);
85
+ runIdempotent(
86
+ [
87
+ 'sql',
88
+ 'instances',
89
+ 'create',
90
+ dbInstanceName,
91
+ '--database-version={{#if_postgres}}POSTGRES_15{{/if_postgres}}{{#if_mysql}}MYSQL_8_0{{/if_mysql}}',
92
+ '--tier=db-f1-micro',
93
+ `--region=${region}`,
94
+ '--storage-auto-increase',
95
+ '--no-backup',
96
+ '--quiet',
97
+ ],
98
+ 'already exists'
99
+ );
100
+
101
+ step(`Creating database '${dbName}'...`);
102
+ runIdempotent(
103
+ ['sql', 'databases', 'create', dbName, `--instance=${dbInstanceName}`, '--quiet'],
104
+ 'already exists'
105
+ );
106
+
107
+ step(`Setting password for Cloud SQL user '${dbUser}'...`);
108
+ console.log(' This password will also be stored in Secret Manager by setup-secrets.js - use the same value.');
109
+ const setupDbPass = await prompt(' Enter DB password (input hidden): ', { hidden: true });
110
+ if (!setupDbPass) {
111
+ throw new Error('DB password cannot be empty.');
112
+ }
113
+ run('gcloud', [
114
+ 'sql',
115
+ 'users',
116
+ 'set-password',
117
+ dbUser,
118
+ `--instance=${dbInstanceName}`,
119
+ `--password=${setupDbPass}`,
120
+ `--project=${projectId}`,
121
+ ]);
122
+
123
+ step(`Creating service account '${backendSaName}'...`);
124
+ runIdempotent(
125
+ [
126
+ 'iam',
127
+ 'service-accounts',
128
+ 'create',
129
+ backendSaName,
130
+ '--display-name={{APP_NAME}} Backend Cloud Run SA',
131
+ '--quiet',
132
+ ],
133
+ 'already exists'
134
+ );
135
+
136
+ for (const role of ['roles/cloudsql.client', 'roles/secretmanager.secretAccessor']) {
137
+ run('gcloud', [
138
+ 'projects',
139
+ 'add-iam-policy-binding',
140
+ projectId,
141
+ `--member=serviceAccount:${backendSa}`,
142
+ `--role=${role}`,
143
+ '--quiet',
144
+ ]);
145
+ }
146
+
147
+ step('Granting Cloud Build service account permissions...');
148
+ for (const role of [
149
+ 'roles/run.admin',
150
+ 'roles/iam.serviceAccountUser',
151
+ 'roles/secretmanager.secretAccessor',
152
+ 'roles/artifactregistry.writer',
153
+ 'roles/cloudsql.client',
154
+ 'roles/storage.objectAdmin',
155
+ ]) {
156
+ run('gcloud', [
157
+ 'projects',
158
+ 'add-iam-policy-binding',
159
+ projectId,
160
+ `--member=serviceAccount:${cloudbuildSa}`,
161
+ `--role=${role}`,
162
+ '--quiet',
163
+ ]);
164
+ }
165
+
166
+ run('gcloud', [
167
+ 'iam',
168
+ 'service-accounts',
169
+ 'add-iam-policy-binding',
170
+ backendSa,
171
+ `--member=serviceAccount:${cloudbuildSa}`,
172
+ '--role=roles/iam.serviceAccountUser',
173
+ '--quiet',
174
+ ]);
175
+
176
+ step(`Creating GCS bucket 'gs://${frontendBucket}'...`);
177
+ runIdempotent(
178
+ [
179
+ 'storage',
180
+ 'buckets',
181
+ 'create',
182
+ `gs://${frontendBucket}`,
183
+ `--location=${region}`,
184
+ '--uniform-bucket-level-access',
185
+ '--quiet',
186
+ ],
187
+ 'already exists'
188
+ );
189
+
190
+ run('gcloud', [
191
+ 'storage',
192
+ 'buckets',
193
+ 'add-iam-policy-binding',
194
+ `gs://${frontendBucket}`,
195
+ '--member=allUsers',
196
+ '--role=roles/storage.objectViewer',
197
+ '--quiet',
198
+ ]);
199
+
200
+ run('gcloud', [
201
+ 'storage',
202
+ 'buckets',
203
+ 'update',
204
+ `gs://${frontendBucket}`,
205
+ '--web-main-page-suffix=index.html',
206
+ '--web-error-page=index.html',
207
+ ]);
208
+
209
+ step('Creating Load Balancer...');
210
+
211
+ runIdempotent(
212
+ [
213
+ 'compute',
214
+ 'network-endpoint-groups',
215
+ 'create',
216
+ negName,
217
+ `--region=${region}`,
218
+ '--network-endpoint-type=serverless',
219
+ `--cloud-run-service=${backendService}`,
220
+ '--quiet',
221
+ ],
222
+ 'NEG already exists'
223
+ );
224
+
225
+ runIdempotent(
226
+ ['compute', 'backend-services', 'create', backendServiceRes, '--load-balancing-scheme=EXTERNAL_MANAGED', '--global', '--quiet'],
227
+ 'backend service already exists'
228
+ );
229
+
230
+ runIdempotent(
231
+ [
232
+ 'compute',
233
+ 'backend-services',
234
+ 'add-backend',
235
+ backendServiceRes,
236
+ `--network-endpoint-group=${negName}`,
237
+ `--network-endpoint-group-region=${region}`,
238
+ '--global',
239
+ '--quiet',
240
+ ],
241
+ 'backend already added'
242
+ );
243
+
244
+ runIdempotent(
245
+ [
246
+ 'compute',
247
+ 'backend-buckets',
248
+ 'create',
249
+ backendBucketRes,
250
+ `--gcs-bucket-name=${frontendBucket}`,
251
+ '--enable-cdn',
252
+ '--quiet',
253
+ ],
254
+ 'backend bucket already exists'
255
+ );
256
+
257
+ runIdempotent(
258
+ ['compute', 'url-maps', 'create', urlMapName, `--default-backend-bucket=${backendBucketRes}`, '--quiet'],
259
+ 'URL map already exists'
260
+ );
261
+
262
+ runIdempotent(
263
+ [
264
+ 'compute',
265
+ 'url-maps',
266
+ 'add-path-matcher',
267
+ urlMapName,
268
+ '--path-matcher-name=api-matcher',
269
+ `--default-backend-bucket=${backendBucketRes}`,
270
+ `--backend-service-path-rules=/graphql=${backendServiceRes},/auth/*=${backendServiceRes}`,
271
+ '--new-hosts=*',
272
+ '--quiet',
273
+ ],
274
+ 'path matcher already exists'
275
+ );
276
+
277
+ runIdempotent(
278
+ ['compute', 'target-http-proxies', 'create', httpProxyName, `--url-map=${urlMapName}`, '--quiet'],
279
+ 'HTTP proxy already exists'
280
+ );
281
+
282
+ runIdempotent(
283
+ [
284
+ 'compute',
285
+ 'forwarding-rules',
286
+ 'create',
287
+ fwdRuleName,
288
+ '--load-balancing-scheme=EXTERNAL_MANAGED',
289
+ '--global',
290
+ `--target-http-proxy=${httpProxyName}`,
291
+ '--ports=80',
292
+ '--quiet',
293
+ ],
294
+ 'forwarding rule already exists'
295
+ );
296
+
297
+ const instanceConnName = `${projectId}:${region}:${dbInstanceName}`;
298
+ const lbIp =
299
+ runCapture(
300
+ 'gcloud',
301
+ ['compute', 'forwarding-rules', 'describe', fwdRuleName, '--global', '--format=value(IPAddress)', `--project=${projectId}`],
302
+ { allowFailure: true }
303
+ ) || '<pending>';
304
+
305
+ console.log('');
306
+ console.log('✓ Setup complete.');
307
+ console.log('');
308
+ console.log(` GCS bucket : gs://${frontendBucket}`);
309
+ console.log(` LB IP : ${lbIp}`);
310
+ console.log('');
311
+ console.log('Next steps:');
312
+ console.log(' 1. Run setup-secrets.js to store credentials in Secret Manager.');
313
+ console.log(' 2. Create the Cloud Build trigger:');
314
+ console.log('');
315
+ console.log(' gcloud builds triggers create github \\\\');
316
+ console.log(` --project=${projectId} \\\\`);
317
+ console.log(' --name={{APP_NAME}}-deploy-develop \\\\');
318
+ console.log(' --repo-name=<YOUR_REPO> \\\\');
319
+ console.log(' --repo-owner=<YOUR_ORG> \\\\');
320
+ console.log(" --branch-pattern='^develop$' \\\\");
321
+ console.log(' --build-config=ops/cloudbuild.yaml \\\\');
322
+ console.log(
323
+ ` --substitutions=_PROJECT_ID=${projectId},_REGION=${region},_AR_REPO=${arRepoName},_BACKEND_SERVICE={{APP_NAME}}-backend,_BACKEND_SA=${backendSa},_CLOUDSQL_INSTANCE=${instanceConnName},_DB_SECRET_NAME={{APP_NAME}}-db-password,_JWT_SECRET_NAME={{APP_NAME}}-jwt-secret`
324
+ );
325
+ }
326
+
327
+ main().catch(error => {
328
+ console.error(`ERROR: ${error.message}`);
329
+ process.exit(1);
330
+ });
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ // setup-secrets.js - Create or rotate Secret Manager secrets for {{APP_NAME}}.
3
+ // Re-run any time you need to rotate credentials.
4
+ //
5
+ // Prerequisites: setup-gcp.js completed, gcloud init run
6
+ //
7
+ // Usage: node ops/scripts/setup-secrets.js
8
+ // Override project if needed: PROJECT_ID=my-project node ops/scripts/setup-secrets.js
9
+
10
+ const crypto = require('crypto');
11
+ const { getProjectIdFromGcloud, prompt, run, step } = require('./_utils');
12
+
13
+ async function createOrUpdateSecret(projectId, backendSa, secretName, secretValue) {
14
+ const exists = run('gcloud', ['secrets', 'describe', secretName, `--project=${projectId}`], {
15
+ allowFailure: true,
16
+ suppressOutput: true,
17
+ }).status === 0;
18
+
19
+ if (exists) {
20
+ console.log(` Rotating '${secretName}'...`);
21
+ run('gcloud', ['secrets', 'versions', 'add', secretName, '--data-file=-', `--project=${projectId}`], {
22
+ input: secretValue,
23
+ });
24
+ } else {
25
+ console.log(` Creating '${secretName}'...`);
26
+ run(
27
+ 'gcloud',
28
+ ['secrets', 'create', secretName, '--replication-policy=automatic', '--data-file=-', `--project=${projectId}`],
29
+ { input: secretValue }
30
+ );
31
+ }
32
+
33
+ run('gcloud', [
34
+ 'secrets',
35
+ 'add-iam-policy-binding',
36
+ secretName,
37
+ `--member=serviceAccount:${backendSa}`,
38
+ '--role=roles/secretmanager.secretAccessor',
39
+ `--project=${projectId}`,
40
+ '--quiet',
41
+ ]);
42
+ }
43
+
44
+ async function main() {
45
+ const projectId = process.env.PROJECT_ID || getProjectIdFromGcloud();
46
+ const backendSaName = '{{APP_NAME}}-backend';
47
+
48
+ if (!projectId || projectId === '(unset)') {
49
+ throw new Error('No active GCP project. Run gcloud init first, or pass PROJECT_ID as an env var.');
50
+ }
51
+
52
+ const backendSa = `${backendSaName}@${projectId}.iam.gserviceaccount.com`;
53
+
54
+ step('DB password');
55
+ const dbPassword = await prompt(' Enter DB password (input hidden): ', { hidden: true });
56
+ if (!dbPassword) {
57
+ throw new Error('password cannot be empty.');
58
+ }
59
+ await createOrUpdateSecret(projectId, backendSa, '{{APP_NAME}}-db-password', dbPassword);
60
+
61
+ step('JWT signing secret');
62
+ let jwtSecret = await prompt(' Enter JWT secret (Enter to auto-generate): ', { hidden: true });
63
+ if (!jwtSecret) {
64
+ jwtSecret = crypto.randomBytes(48).toString('base64');
65
+ console.log(' Auto-generated.');
66
+ }
67
+ await createOrUpdateSecret(projectId, backendSa, '{{APP_NAME}}-jwt-secret', jwtSecret);
68
+
69
+ console.log('');
70
+ console.log(`✓ Secrets stored. View at: https://console.cloud.google.com/security/secret-manager?project=${projectId}`);
71
+ }
72
+
73
+ main().catch(error => {
74
+ console.error(`ERROR: ${error.message}`);
75
+ process.exit(1);
76
+ });
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ // test-prod-local.js - Build and run the full production stack locally.
3
+ //
4
+ // Usage:
5
+ // node ops/scripts/test-prod-local.js
6
+ //
7
+ // To skip rebuilding the framework (monorepo mode only):
8
+ // SKIP_FRAMEWORK_BUILD=1 node ops/scripts/test-prod-local.js
9
+
10
+ const path = require('path');
11
+ const { run, runCapture, step } = require('./_utils');
12
+
13
+ function main() {
14
+ const appDir = path.resolve(__dirname, '..', '..');
15
+ const repoRoot = runCapture('git', ['rev-parse', '--show-toplevel'], { cwd: appDir });
16
+
17
+ // In a monorepo the app lives inside the framework repo — build the framework
18
+ // from source. In standalone mode the framework is already installed as an
19
+ // npm dependency so there is nothing to build.
20
+ const isMonorepo = repoRoot !== appDir;
21
+
22
+ step('[1/4] Building framework and CLI...');
23
+ if (!isMonorepo) {
24
+ console.log(' (standalone mode — framework installed as npm dependency, skipping)');
25
+ } else if (process.env.SKIP_FRAMEWORK_BUILD === '1') {
26
+ console.log(' (skipped - SKIP_FRAMEWORK_BUILD=1)');
27
+ } else {
28
+ run('pnpm', ['run', 'build:all'], { cwd: repoRoot });
29
+ }
30
+
31
+ step('[2/4] Generating view metadata and GraphQL types...');
32
+ run('npx', ['slingr', 'sync-metadata'], { cwd: appDir });
33
+
34
+ step('[3/4] Building frontend production bundle...');
35
+ run('npm', ['run', 'build'], { cwd: path.join(appDir, 'frontend') });
36
+
37
+ step('[4/4] Starting production stack (Ctrl-C to stop)...');
38
+ run('docker', ['compose', '-f', 'docker-compose.yml', '-f', 'docker-compose.prod-test.yml', 'up', '--build'], { cwd: appDir });
39
+
40
+ console.log('');
41
+ console.log('Stack stopped.');
42
+ }
43
+
44
+ try {
45
+ main();
46
+ } catch (error) {
47
+ console.error(`ERROR: ${error.message}`);
48
+ process.exit(1);
49
+ }
@@ -1,38 +1,50 @@
1
- {
2
- "name": "{{APP_NAME}}",
3
- "version": "1.0.0",
4
- "description": "{{DESCRIPTION}}",
5
- "main": "dist/index.js",
6
- "scripts": {
7
- "build": "tsc",
8
- "dev": "ts-node src/index.ts",
9
- "test": "jest",
10
- "test:watch": "jest --watch"
11
- },
12
- "keywords": [
13
- "slingr",
14
- "{{APP_KEYWORD}}"
15
- ],
16
- "author": "",
17
- "license": "MIT",
18
- "dependencies": {
19
- "graphql-request": "^7.3.0",
20
- "slingr-framework": "github:slingr-stack/framework"{{DB_DRIVER_DEPENDENCY}}
21
- },
22
- "devDependencies": {
23
- "@graphql-codegen/cli": "^6.0.0",
24
- "@graphql-codegen/typescript": "^5.0.2",
25
- "@graphql-codegen/typescript-graphql-request": "^6.3.0",
26
- "@graphql-codegen/typescript-operations": "^5.0.2",
27
- "@types/jest": "^29.5.12",
28
- "@types/node": "^24.3.0",
29
- "jest": "^30.2.0",
30
- "ts-jest": "^29.4.5",
31
- "ts-node": "^10.9.2",
32
- "typescript": "^5.9.2"
33
- },
34
- "overrides": {
35
- "glob": "^11.0.0",
36
- "node-gyp": "^11.0.0"
37
- }
38
- }
1
+ {
2
+ "name": "{{APP_NAME}}",
3
+ "version": "1.0.0",
4
+ "description": "{{DESCRIPTION}}",
5
+ "scripts": {
6
+ "build": "tsc && tsc-alias -p tsconfig.json --resolve-full-paths",
7
+ "dev": "ts-node src/App.ts",
8
+ "setup": "pnpm install && pnpm dlx slingr gql generate-schema && pnpm dlx slingr gql generate-sdk",
9
+ "test": "jest",
10
+ "test:watch": "jest --watch"
11
+ },
12
+ "keywords": [
13
+ "slingr",
14
+ "{{APP_KEYWORD}}"
15
+ ],
16
+ "author": "",
17
+ "license": "MIT",
18
+ "dependencies": {
19
+ "graphql-request": "^7.3.0",
20
+ "graphql": "^16.12.0",
21
+ "@slingr/framework-backend": "latest"{{DB_DRIVER_DEPENDENCY}}
22
+ },
23
+ "devDependencies": {
24
+ "@graphql-codegen/cli": "^6.0.0",
25
+ "@graphql-codegen/typescript": "^5.0.2",
26
+ "@graphql-codegen/typescript-graphql-request": "^6.3.0",
27
+ "@graphql-codegen/typescript-operations": "^5.0.2",
28
+ "@casl/ability": "6.7.3",
29
+ "@types/bcryptjs": "^2.4.6",
30
+ "@types/jsonwebtoken": "^9.0.10",
31
+ "@types/jest": "^29.5.12",
32
+ "@types/node": "^24.3.0",
33
+ "bcryptjs": "^3.0.2",
34
+ "jest": "^30.2.0",
35
+ "jsonwebtoken": "^9.0.2",
36
+ "ts-jest": "^29.4.5",
37
+ "ts-node": "^10.9.2",
38
+ "tsconfig-paths": "^4.2.0",
39
+ "tsc-alias": "^1.8.10",
40
+ "tsx": "^4.21.0",
41
+ "typescript": "^5.9.2"
42
+ },
43
+ "engines": {
44
+ "node": ">=20.0.0"
45
+ },
46
+ "overrides": {
47
+ "glob": "^11.0.0",
48
+ "node-gyp": "^11.0.0"
49
+ }
50
+ }
@@ -0,0 +1,3 @@
1
+ packages:
2
+ - backend
3
+ - frontend