platformatic 1.7.0 → 1.8.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 (3) hide show
  1. package/lib/deploy.js +202 -19
  2. package/lib/errors.js +5 -1
  3. package/package.json +13 -13
package/lib/deploy.js CHANGED
@@ -4,6 +4,7 @@ import { isAbsolute, dirname, relative, join } from 'path'
4
4
  import { readFile } from 'fs/promises'
5
5
 
6
6
  import { request } from 'undici'
7
+ import { bold, green } from 'colorette'
7
8
  import pino from 'pino'
8
9
  import pretty from 'pino-pretty'
9
10
  import dotenv from 'dotenv'
@@ -18,6 +19,9 @@ export const DEPLOY_SERVICE_HOST = 'https://deploy.platformatic.cloud'
18
19
  const WORKSPACE_TYPES = ['static', 'dynamic']
19
20
  const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
20
21
 
22
+ const CREATE_NEW_WORKSPACE_CHOICE = Symbol('CREATE_NEW_WORKSPACE_CHOICE')
23
+ const CREATE_NEW_APPLICATION_CHOICE = Symbol('CREATE_NEW_APPLICATION_CHOICE')
24
+
21
25
  const logger = pino(pretty({
22
26
  translateTime: 'SYS:HH:MM:ss',
23
27
  ignore: 'hostname,pid'
@@ -76,7 +80,7 @@ async function askMissingWorkspaceDetails (
76
80
  }
77
81
  }
78
82
 
79
- /* c8 ignore next 26 */
83
+ /* c8 ignore next 27 */
80
84
  async function askToChooseApplication (applications) {
81
85
  const applicationChoices = applications.map((application) => {
82
86
  return {
@@ -85,7 +89,11 @@ async function askToChooseApplication (applications) {
85
89
  }
86
90
  })
87
91
  applicationChoices.push({
88
- name: 'Deploy to another application',
92
+ name: bold('Create new application'),
93
+ value: CREATE_NEW_APPLICATION_CHOICE
94
+ })
95
+ applicationChoices.push({
96
+ name: bold('Deploy to another application'),
89
97
  value: null
90
98
  })
91
99
 
@@ -93,14 +101,11 @@ async function askToChooseApplication (applications) {
93
101
  type: 'list',
94
102
  name: 'application',
95
103
  message: 'Select application to deploy:',
96
- choices: applicationChoices
104
+ choices: applicationChoices,
105
+ loop: false
97
106
  })
98
107
 
99
108
  const chosenApplication = answer.application
100
- if (chosenApplication === null) {
101
- return null
102
- }
103
-
104
109
  return chosenApplication
105
110
  }
106
111
 
@@ -113,7 +118,11 @@ async function askToChooseWorkspace (workspaces) {
113
118
  }
114
119
  })
115
120
  workspaceChoices.push({
116
- name: 'Deploy to another workspace',
121
+ name: bold('Create new workspace'),
122
+ value: CREATE_NEW_WORKSPACE_CHOICE
123
+ })
124
+ workspaceChoices.push({
125
+ name: bold('Deploy to another workspace'),
117
126
  value: null
118
127
  })
119
128
 
@@ -121,15 +130,63 @@ async function askToChooseWorkspace (workspaces) {
121
130
  type: 'list',
122
131
  name: 'workspace',
123
132
  message: 'Select workspace to deploy:',
124
- choices: workspaceChoices
133
+ choices: workspaceChoices,
134
+ loop: false
125
135
  })
126
136
 
127
137
  const chosenWorkspace = answer.workspace
128
- if (chosenWorkspace === null) {
129
- return null
138
+ return chosenWorkspace
139
+ }
140
+
141
+ /* c8 ignore next 14 */
142
+ async function askToChooseOrg (orgs) {
143
+ const orgsChoices = orgs.map((org) => {
144
+ return { name: org.name, value: org.id }
145
+ })
146
+
147
+ const answer = await inquirer.prompt({
148
+ type: 'list',
149
+ name: 'orgId',
150
+ message: 'Select organisation to publish to:',
151
+ choices: orgsChoices
152
+ })
153
+
154
+ return answer.orgId
155
+ }
156
+
157
+ /* c8 ignore next 21 */
158
+ async function askNewWorkspaceDetails () {
159
+ const answer = await inquirer.prompt([
160
+ {
161
+ type: 'input',
162
+ name: 'workspaceName',
163
+ message: 'Enter workspace name:'
164
+ },
165
+ {
166
+ type: 'list',
167
+ name: 'workspaceType',
168
+ message: 'Select workspace type:',
169
+ choices: WORKSPACE_TYPES
170
+ }
171
+ ])
172
+
173
+ return {
174
+ workspaceName: answer.workspaceName,
175
+ workspaceType: answer.workspaceType
130
176
  }
177
+ }
131
178
 
132
- return chosenWorkspace
179
+ /* c8 ignore next 12 */
180
+ async function askNewApplicationDetails () {
181
+ const answer = await inquirer.prompt({
182
+ type: 'input',
183
+ name: 'applicationName',
184
+ message: 'Enter application name:'
185
+ })
186
+
187
+ return {
188
+ applicationName: answer.applicationName
189
+ }
133
190
  }
134
191
 
135
192
  /* c8 ignore next 19 */
@@ -216,6 +273,25 @@ async function getUserApplications (deployServiceHost, userApiKey) {
216
273
  return applications
217
274
  }
218
275
 
276
+ /* c8 ignore next 18 */
277
+ async function getUserOrgs (deployServiceHost, userApiKey) {
278
+ const url = deployServiceHost + '/organisations'
279
+ const { statusCode, body } = await request(url, {
280
+ method: 'GET',
281
+ headers: {
282
+ 'content-type': 'application/json',
283
+ 'x-platformatic-user-api-key': userApiKey
284
+ }
285
+ })
286
+
287
+ if (statusCode !== 200) {
288
+ throw new errors.CouldNotFetchUserOrgsError()
289
+ }
290
+
291
+ const { orgs } = await body.json()
292
+ return orgs
293
+ }
294
+
219
295
  /* c8 ignore next 18 */
220
296
  async function getWorkspaceLabels (deployServiceHost, workspaceId, userApiKey) {
221
297
  const { statusCode, body } = await request(`${deployServiceHost}/entrypoints`, {
@@ -236,27 +312,134 @@ async function getWorkspaceLabels (deployServiceHost, workspaceId, userApiKey) {
236
312
  return entryPoints.map((entryPoint) => entryPoint.label)
237
313
  }
238
314
 
239
- /* c8 ignore next 19 */
240
- async function getUserWorkspaceDetails (deployServiceHost, userApiKey) {
315
+ /* c8 ignore next 30 */
316
+ async function createWorkspace (
317
+ deployServiceHost,
318
+ userApiKey,
319
+ appId,
320
+ workspaceName,
321
+ workspaceType
322
+ ) {
323
+ const { statusCode, body } = await request(`${deployServiceHost}/workspaces`, {
324
+ method: 'POST',
325
+ headers: {
326
+ 'Content-Type': 'application/json',
327
+ 'x-platformatic-user-api-key': userApiKey
328
+ },
329
+ body: JSON.stringify({
330
+ appId,
331
+ name: workspaceName,
332
+ type: workspaceType
333
+ })
334
+ })
335
+
336
+ if (statusCode !== 200) {
337
+ const error = await body.text()
338
+ console.error(error)
339
+ throw new errors.CouldNotCreateWorkspaceError()
340
+ }
341
+
342
+ const { id: workspaceId } = await body.json()
343
+ return workspaceId
344
+ }
345
+
346
+ /* c8 ignore next 25 */
347
+ async function createApplication (
348
+ deployServiceHost,
349
+ userApiKey,
350
+ orgId,
351
+ applicationName
352
+ ) {
353
+ const { statusCode, body } = await request(`${deployServiceHost}/applications`, {
354
+ method: 'POST',
355
+ headers: {
356
+ 'Content-Type': 'application/json',
357
+ 'x-platformatic-user-api-key': userApiKey
358
+ },
359
+ body: JSON.stringify({ orgId, name: applicationName })
360
+ })
361
+
362
+ if (statusCode !== 200) {
363
+ const error = await body.text()
364
+ console.error(error)
365
+ throw new errors.CouldNotCreateApplicationError()
366
+ }
367
+
368
+ const { id: applicationId } = await body.json()
369
+ return applicationId
370
+ }
371
+
372
+ /* c8 ignore next 16 */
373
+ async function getUserOrgDetails (deployServiceHost, userApiKey) {
374
+ const userOrgs = await getUserOrgs(deployServiceHost, userApiKey)
375
+ if (userOrgs.length === 0) {
376
+ throw new errors.NoUserOrgsError()
377
+ }
378
+
379
+ let orgId = null
380
+ if (userOrgs.length === 1) {
381
+ orgId = userOrgs[0].id
382
+ } else {
383
+ orgId = await askToChooseOrg(userOrgs)
384
+ }
385
+
386
+ return { id: orgId }
387
+ }
388
+
389
+ /* c8 ignore next 26 */
390
+ async function getUserApplicationDetails (deployServiceHost, userApiKey) {
241
391
  const applications = await getUserApplications(deployServiceHost, userApiKey)
242
392
  if (applications.length === 0) return null
243
393
 
244
394
  const application = await askToChooseApplication(applications)
245
395
  if (application === null) return null
246
396
 
247
- const workspaces = application.workspaces
248
- if (workspaces.length === 0) return null
397
+ if (application === CREATE_NEW_APPLICATION_CHOICE) {
398
+ const { id: orgId } = await getUserOrgDetails(deployServiceHost, userApiKey)
399
+ const { applicationName } = await askNewApplicationDetails()
400
+ const applicationId = await createApplication(
401
+ deployServiceHost,
402
+ userApiKey,
403
+ orgId,
404
+ applicationName
405
+ )
406
+ return { id: applicationId, name: applicationName, workspaces: [] }
407
+ }
408
+ return {
409
+ id: application.id,
410
+ name: application.name,
411
+ workspaces: application.workspaces
412
+ }
413
+ }
414
+
415
+ /* c8 ignore next 24 */
416
+ async function getUserWorkspaceDetails (deployServiceHost, userApiKey) {
417
+ const application = await getUserApplicationDetails(deployServiceHost, userApiKey)
418
+ if (application === null) return null
249
419
 
250
- const workspaceDetails = await askToChooseWorkspace(workspaces)
420
+ const workspaceDetails = await askToChooseWorkspace(application.workspaces)
251
421
  if (workspaceDetails === null) return null
252
422
 
423
+ if (workspaceDetails === CREATE_NEW_WORKSPACE_CHOICE) {
424
+ const { workspaceName, workspaceType } = await askNewWorkspaceDetails()
425
+ const workspaceId = await createWorkspace(
426
+ deployServiceHost,
427
+ userApiKey,
428
+ application.id,
429
+ workspaceName,
430
+ workspaceType
431
+ )
432
+ return { workspaceId, workspaceType }
433
+ }
434
+
253
435
  return {
254
- workspaceType: workspaceDetails.type,
255
- workspaceId: workspaceDetails.id
436
+ workspaceId: workspaceDetails.id,
437
+ workspaceType: workspaceDetails.type
256
438
  }
257
439
  }
258
440
 
259
441
  export async function deploy (argv) {
442
+ console.log('This application will be deployed to ' + bold(green('Platformatic Cloud')) + '. To change the target use the --deploy-service-host flag')
260
443
  try {
261
444
  const args = parseArgs(argv, {
262
445
  alias: {
package/lib/errors.js CHANGED
@@ -9,7 +9,11 @@ const errors = {
9
9
  InvalidWorkspaceIdError: createError(`${ERROR_PREFIX}_INVALID_WORKSPACE_ID`, 'Invalid workspace id provided. Workspace id must be a valid uuid.'),
10
10
  CouldNotFindWorkspaceKeysError: createError(`${ERROR_PREFIX}_COULD_NOT_FIND_WORKSPACE_KEYS`, 'Could not find workspace keys in provided file.'),
11
11
  CouldNotFetchUserApplicationsError: createError(`${ERROR_PREFIX}_COULD_NOT_FETCH_USER_APPLICATIONS`, 'Could not fetch user applications'),
12
- CouldNotFetchDeployLabelsError: createError(`${ERROR_PREFIX}_COULD_NOT_FETCH_DEPLOY_LABELS`, 'Could not fetch deploy labels')
12
+ CouldNotFetchDeployLabelsError: createError(`${ERROR_PREFIX}_COULD_NOT_FETCH_DEPLOY_LABELS`, 'Could not fetch deploy labels'),
13
+ CouldNotCreateWorkspaceError: createError(`${ERROR_PREFIX}_COULD_NOT_CREATE_WORKSPACE`, 'Could not create workspace'),
14
+ CouldNotCreateApplicationError: createError(`${ERROR_PREFIX}_COULD_NOT_CREATE_APPLICATION`, 'Could not create application'),
15
+ CouldNotFetchUserOrgsError: createError(`${ERROR_PREFIX}_COULD_NOT_FETCH_USER_ORGS`, 'Could not fetch user ogranisations'),
16
+ NoUserOrgsError: createError(`${ERROR_PREFIX}_NO_USER_ORGS`, 'User does not have any organisations.')
13
17
  }
14
18
 
15
19
  export default errors
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "platformatic",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Platformatic CLI",
5
5
  "main": "cli.js",
6
6
  "type": "module",
@@ -49,18 +49,18 @@
49
49
  "pino": "^8.15.3",
50
50
  "pino-pretty": "^10.2.0",
51
51
  "undici": "^5.25.4",
52
- "@platformatic/authenticate": "1.7.0",
53
- "@platformatic/client-cli": "1.7.0",
54
- "@platformatic/composer": "1.7.0",
55
- "@platformatic/config": "1.7.0",
56
- "@platformatic/db": "1.7.0",
57
- "@platformatic/deploy-client": "1.7.0",
58
- "@platformatic/frontend-template": "1.7.0",
59
- "@platformatic/metaconfig": "1.7.0",
60
- "@platformatic/runtime": "1.7.0",
61
- "@platformatic/service": "1.7.0",
62
- "@platformatic/utils": "^1.7.0",
63
- "create-platformatic": "1.7.0"
52
+ "@platformatic/authenticate": "1.8.0",
53
+ "@platformatic/client-cli": "1.8.0",
54
+ "@platformatic/composer": "1.8.0",
55
+ "@platformatic/config": "1.8.0",
56
+ "@platformatic/db": "1.8.0",
57
+ "@platformatic/deploy-client": "1.8.0",
58
+ "@platformatic/frontend-template": "1.8.0",
59
+ "@platformatic/runtime": "1.8.0",
60
+ "@platformatic/metaconfig": "1.8.0",
61
+ "@platformatic/service": "1.8.0",
62
+ "@platformatic/utils": "^1.8.0",
63
+ "create-platformatic": "1.8.0"
64
64
  },
65
65
  "scripts": {
66
66
  "test": "standard | snazzy && c8 node ./test/runner.js",