@gadgetinc/ggt 0.4.10 → 1.0.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 (142) hide show
  1. package/README.md +165 -93
  2. package/lib/__generated__/graphql.js +66 -1
  3. package/lib/__generated__/graphql.js.map +1 -1
  4. package/lib/commands/deploy.js +328 -230
  5. package/lib/commands/deploy.js.map +1 -1
  6. package/lib/commands/dev.js +445 -0
  7. package/lib/commands/dev.js.map +1 -0
  8. package/lib/commands/list.js +27 -19
  9. package/lib/commands/list.js.map +1 -1
  10. package/lib/commands/login.js +15 -11
  11. package/lib/commands/login.js.map +1 -1
  12. package/lib/commands/logout.js +5 -5
  13. package/lib/commands/logout.js.map +1 -1
  14. package/lib/commands/open.js +200 -0
  15. package/lib/commands/open.js.map +1 -0
  16. package/lib/commands/pull.js +128 -0
  17. package/lib/commands/pull.js.map +1 -0
  18. package/lib/commands/push.js +126 -0
  19. package/lib/commands/push.js.map +1 -0
  20. package/lib/commands/root.js +46 -28
  21. package/lib/commands/root.js.map +1 -1
  22. package/lib/commands/status.js +61 -0
  23. package/lib/commands/status.js.map +1 -0
  24. package/lib/commands/version.js +6 -6
  25. package/lib/commands/version.js.map +1 -1
  26. package/lib/commands/whoami.js +6 -6
  27. package/lib/commands/whoami.js.map +1 -1
  28. package/lib/ggt.js +33 -8
  29. package/lib/ggt.js.map +1 -1
  30. package/lib/main.js +5 -0
  31. package/lib/main.js.map +1 -0
  32. package/lib/services/app/api/api.js +191 -0
  33. package/lib/services/app/api/api.js.map +1 -0
  34. package/lib/services/app/api/operation.js +12 -0
  35. package/lib/services/app/api/operation.js.map +1 -0
  36. package/lib/services/app/app.js +44 -10
  37. package/lib/services/app/app.js.map +1 -1
  38. package/lib/services/app/{edit/client.js → client.js} +29 -19
  39. package/lib/services/app/client.js.map +1 -0
  40. package/lib/services/app/edit/edit.js +67 -31
  41. package/lib/services/app/edit/edit.js.map +1 -1
  42. package/lib/services/app/edit/operation.js +4 -3
  43. package/lib/services/app/edit/operation.js.map +1 -1
  44. package/lib/services/app/{edit/error.js → error.js} +6 -6
  45. package/lib/services/app/error.js.map +1 -0
  46. package/lib/services/command/arg.js +4 -4
  47. package/lib/services/command/arg.js.map +1 -1
  48. package/lib/services/command/command.js +9 -7
  49. package/lib/services/command/command.js.map +1 -1
  50. package/lib/services/command/context.js +82 -20
  51. package/lib/services/command/context.js.map +1 -1
  52. package/lib/services/config/config.js +4 -7
  53. package/lib/services/config/config.js.map +1 -1
  54. package/lib/services/config/env.js +1 -1
  55. package/lib/services/config/env.js.map +1 -1
  56. package/lib/services/filesync/changes.js +76 -37
  57. package/lib/services/filesync/changes.js.map +1 -1
  58. package/lib/services/filesync/conflicts.js +10 -9
  59. package/lib/services/filesync/conflicts.js.map +1 -1
  60. package/lib/services/filesync/directory.js +16 -1
  61. package/lib/services/filesync/directory.js.map +1 -1
  62. package/lib/services/filesync/error.js +96 -27
  63. package/lib/services/filesync/error.js.map +1 -1
  64. package/lib/services/filesync/filesync.js +448 -490
  65. package/lib/services/filesync/filesync.js.map +1 -1
  66. package/lib/services/filesync/hashes.js +8 -5
  67. package/lib/services/filesync/hashes.js.map +1 -1
  68. package/lib/services/filesync/strategy.js +59 -0
  69. package/lib/services/filesync/strategy.js.map +1 -0
  70. package/lib/services/filesync/sync-json.js +475 -0
  71. package/lib/services/filesync/sync-json.js.map +1 -0
  72. package/lib/services/http/auth.js +30 -1
  73. package/lib/services/http/auth.js.map +1 -1
  74. package/lib/services/http/http.js +5 -0
  75. package/lib/services/http/http.js.map +1 -1
  76. package/lib/services/output/confirm.js +149 -0
  77. package/lib/services/output/confirm.js.map +1 -0
  78. package/lib/services/output/footer.js +22 -0
  79. package/lib/services/output/footer.js.map +1 -0
  80. package/lib/services/output/log/format/pretty.js +2 -1
  81. package/lib/services/output/log/format/pretty.js.map +1 -1
  82. package/lib/services/output/log/logger.js +13 -5
  83. package/lib/services/output/log/logger.js.map +1 -1
  84. package/lib/services/output/log/structured.js +2 -2
  85. package/lib/services/output/log/structured.js.map +1 -1
  86. package/lib/services/output/output.js +197 -0
  87. package/lib/services/output/output.js.map +1 -0
  88. package/lib/services/output/print.js +31 -0
  89. package/lib/services/output/print.js.map +1 -0
  90. package/lib/services/output/problems.js +84 -0
  91. package/lib/services/output/problems.js.map +1 -0
  92. package/lib/services/output/prompt.js +173 -40
  93. package/lib/services/output/prompt.js.map +1 -1
  94. package/lib/services/output/report.js +63 -19
  95. package/lib/services/output/report.js.map +1 -1
  96. package/lib/services/output/select.js +198 -0
  97. package/lib/services/output/select.js.map +1 -0
  98. package/lib/services/output/spinner.js +141 -0
  99. package/lib/services/output/spinner.js.map +1 -0
  100. package/lib/services/output/sprint.js +38 -15
  101. package/lib/services/output/sprint.js.map +1 -1
  102. package/lib/services/output/symbols.js +23 -0
  103. package/lib/services/output/symbols.js.map +1 -0
  104. package/lib/services/output/table.js +98 -0
  105. package/lib/services/output/table.js.map +1 -0
  106. package/lib/services/output/timestamp.js +12 -0
  107. package/lib/services/output/timestamp.js.map +1 -0
  108. package/lib/services/output/update.js +29 -9
  109. package/lib/services/output/update.js.map +1 -1
  110. package/lib/services/user/session.js +4 -0
  111. package/lib/services/user/session.js.map +1 -1
  112. package/lib/services/user/user.js +15 -10
  113. package/lib/services/user/user.js.map +1 -1
  114. package/lib/services/util/assert.js +11 -0
  115. package/lib/services/util/assert.js.map +1 -0
  116. package/lib/services/util/boolean.js +2 -2
  117. package/lib/services/util/boolean.js.map +1 -1
  118. package/lib/services/util/function.js +45 -7
  119. package/lib/services/util/function.js.map +1 -1
  120. package/lib/services/util/is.js +23 -2
  121. package/lib/services/util/is.js.map +1 -1
  122. package/lib/services/util/json.js +16 -13
  123. package/lib/services/util/json.js.map +1 -1
  124. package/lib/services/util/object.js +2 -2
  125. package/lib/services/util/object.js.map +1 -1
  126. package/lib/services/util/promise.js +5 -2
  127. package/lib/services/util/promise.js.map +1 -1
  128. package/lib/services/util/types.js.map +1 -1
  129. package/npm-shrinkwrap.json +3415 -2973
  130. package/package.json +47 -40
  131. package/bin/dev.cmd +0 -3
  132. package/bin/dev.js +0 -14
  133. package/bin/run.cmd +0 -3
  134. package/bin/run.js +0 -5
  135. package/lib/commands/sync.js +0 -284
  136. package/lib/commands/sync.js.map +0 -1
  137. package/lib/services/app/edit/client.js.map +0 -1
  138. package/lib/services/app/edit/error.js.map +0 -1
  139. package/lib/services/output/log/printer.js +0 -120
  140. package/lib/services/output/log/printer.js.map +0 -1
  141. package/lib/services/output/stream.js +0 -54
  142. package/lib/services/output/stream.js.map +0 -1
@@ -1,271 +1,369 @@
1
- import chalk from "chalk";
2
- import ora from "ora";
3
- import { REMOTE_SERVER_CONTRACT_STATUS_SUBSCRIPTION } from "../services/app/edit/operation.js";
4
- import { FileSync, FileSyncArgs } from "../services/filesync/filesync.js";
5
- import { select } from "../services/output/prompt.js";
1
+ import assert from "node:assert";
2
+ import terminalLink from "terminal-link";
3
+ import { PUBLISH_STATUS_SUBSCRIPTION } from "../services/app/edit/operation.js";
4
+ import { DeployDisallowedError } from "../services/filesync/error.js";
5
+ import { FileSync } from "../services/filesync/filesync.js";
6
+ import { SyncJson, loadSyncJsonDirectory } from "../services/filesync/sync-json.js";
7
+ import { confirm } from "../services/output/confirm.js";
8
+ import { output } from "../services/output/output.js";
9
+ import { println } from "../services/output/print.js";
10
+ import { ProblemSeverity, printProblems, publishIssuesToProblems } from "../services/output/problems.js";
11
+ import { reportErrorAndExit } from "../services/output/report.js";
12
+ import { spin } from "../services/output/spinner.js";
6
13
  import { sprint } from "../services/output/sprint.js";
7
- import { isCloseEvent, isGraphQLErrors } from "../services/util/is.js";
8
- export const usage = ()=>sprint`
9
- Deploy your Gadget application's development source code to production.
14
+ import { ts } from "../services/output/timestamp.js";
15
+ import { unreachable } from "../services/util/assert.js";
16
+ import { isGraphQLErrors } from "../services/util/is.js";
17
+ import { args as PushArgs } from "./push.js";
18
+ export const args = {
19
+ ...PushArgs,
20
+ "--env": {
21
+ type: String,
22
+ alias: [
23
+ "-e",
24
+ "--environment",
25
+ "--from"
26
+ ]
27
+ },
28
+ "--allow-problems": {
29
+ type: Boolean,
30
+ alias: "--allow-issues"
31
+ },
32
+ "--allow-charges": {
33
+ type: Boolean
34
+ }
35
+ };
36
+ export const usage = (ctx)=>{
37
+ if (ctx.args["-h"]) {
38
+ return sprint`
39
+ Deploy an environment to production.
40
+
41
+ Your local files must match your environment's files
42
+ before you can deploy. Changes are tracked from
43
+ the last "ggt dev", "ggt push", or "ggt pull" run locally.
44
+
45
+ {bold USAGE}
46
+ ggt deploy
47
+
48
+ {bold EXAMPLES}
49
+ $ ggt deploy
50
+ $ ggt deploy --from=staging
51
+ $ ggt deploy --from=staging --force
52
+ $ ggt deploy --from=staging --force --allow-problems
53
+
54
+ {bold FLAGS}
55
+ -a, --app=<name> The application to deploy
56
+ -e, --from=<env> The environment to deploy from
57
+ --force Discard changes to your environment's filesystem
58
+ --allow-problems Deploy regardless of any problems the environment has
59
+ --allow-charges Deploy even if doing so will add charges to your account
60
+
61
+ Run "ggt deploy --help" for more information.
62
+ `;
63
+ }
64
+ return sprint`
65
+ Deploy an environment to production.
66
+
67
+ Your local files must match your environment's files
68
+ before you can deploy. Changes are tracked from
69
+ the last "ggt dev", "ggt push", or "ggt pull" run locally.
70
+
71
+ If your local files don't match your environment's files, you will
72
+ be prompted to push your local files before you can deploy.
73
+
74
+ If your environment has un-pulled changes, and "--force" is not
75
+ passed, you will be prompted to {underline discard them} or abort the deploy.
10
76
 
11
77
  {bold USAGE}
12
- ggt deploy [DIRECTORY] [--app=<name>]
13
78
 
14
- {bold ARGUMENTS}
15
- DIRECTORY The directory to sync files to and deploy (default: ".")
79
+ ggt deploy [--app=<name>] [--from=<env>] [--force]
80
+ [--allow-problems] [--allow-charges]
81
+
82
+ {bold EXAMPLES}
83
+
84
+ $ ggt deploy
85
+ $ ggt deploy --from=staging
86
+ $ ggt deploy --from=staging --force
87
+ $ ggt deploy --from=staging --force --allow-problems
88
+ $ ggt deploy --from=staging --force --allow-problems --allow-charges
16
89
 
17
90
  {bold FLAGS}
18
- -a, --app=<name> The Gadget application to deploy
19
- --force Deploy the Gadget application regardless of any issues it may have
20
91
 
21
- {bold DESCRIPTION}
22
- Deploy allows you to deploy your current Gadget application in development to production.
92
+ -a, --app, --application=<name>
93
+ The application to deploy.
94
+
95
+ Defaults to the application within the ".gadget/sync.json"
96
+ file in the current directory or any parent directories.
23
97
 
24
- It detects if local files are up to date with remote and if the Gadget application
25
- is in a deployable state. If there are any issues, it will display them and ask if
26
- you would like to deploy anyways.
98
+ -e, --env, --environment, --from=<name>
99
+ The environment to deploy from.
27
100
 
28
- Note:
29
- If local files are not up to date or have not recently been synced with remote ones,
30
- you will be prompted to run a one-time sync to ensure the files remain consistent with
31
- what is on the remote.
32
- • You may wish to keep ggt sync running in the background before trying to run ggt deploy
101
+ Defaults to the environment within the ".gadget/sync.json"
102
+ file in the current directory or any parent directories.
33
103
 
34
- {bold EXAMPLE}
35
- $ ggt deploy ~/gadget/example --app example
104
+ -f, --force
105
+ Discard any changes made to your environment's filesystem
106
+ since the last "ggt dev", "ggt push", or "ggt pull".
36
107
 
37
- App example
38
- Editor https://example.gadget.app/edit
39
- Playground https://example.gadget.app/api/graphql/playground
40
- Docs https://docs.gadget.dev/api/example
108
+ Defaults to false.
41
109
 
42
- Endpoints
43
- https://example.gadget.app
44
- https://example--development.gadget.app
110
+ --allow-problems, --allow-issues
111
+ Deploy your environment to production regardless of any problems
112
+ it may have.
45
113
 
114
+ These problems may include:
115
+ • Gelly syntax errors
116
+ • TypeScript errors
117
+ • Models with missing fields
46
118
 
47
- Building frontend assets ...
48
- ✔ DONE
119
+ Defaults to false.
49
120
 
50
- Setting up database ...
51
- DONE
121
+ --allow-charges
122
+ Allows "ggt deploy" to continue when deploying your environment
123
+ to production will add charges to your account.
52
124
 
53
- Copying development ...
54
- ✔ DONE
125
+ Defaults to false.
55
126
 
56
- Restarting app ...
57
- DONE
127
+ --allow-unknown-directory
128
+ Allows "ggt deploy" to continue when the current directory, nor
129
+ any parent directories, contain a ".gadget/sync.json" file
130
+ within it.
58
131
 
59
- Deploy completed. Good bye!
132
+ Defaults to false.
133
+
134
+ --allow-different-app
135
+ Allows "ggt deploy" to continue with a different "--app" than the
136
+ one found within the ".gadget/sync.json" file.
137
+
138
+ Defaults to false.
139
+
140
+ Run "ggt deploy -h" for less information.
60
141
  `;
61
- export const args = {
62
- ...FileSyncArgs
63
- };
64
- export var Action;
65
- (function(Action) {
66
- Action["DEPLOY_ANYWAYS"] = "Deploy anyways";
67
- Action["SYNC_ONCE"] = "Sync once";
68
- Action["CANCEL"] = "Cancel (Ctrl+C)";
69
- })(Action || (Action = {}));
70
- const AppDeploymentStepsToAppDeployState = (step)=>{
71
- switch(step){
72
- case "NOT_STARTED":
73
- return "Deploy not started";
74
- case "STARTING":
75
- case "BUILDING_ASSETS":
76
- case "UPLOADING_ASSETS":
77
- return "Building frontend assets";
78
- case "CONVERGING_STORAGE":
79
- return "Setting up database";
80
- case "PUBLISHING_TREE":
81
- return "Copying development";
82
- case "RELOADING_SANDBOX":
83
- return "Restarting app";
84
- case "COMPLETED":
85
- return "Deploy completed";
86
- default:
87
- return "Unknown step";
88
- }
89
142
  };
90
- var AppDeploymentSteps;
91
- (function(AppDeploymentSteps) {
92
- AppDeploymentSteps["NOT_STARTED"] = "NOT_STARTED";
93
- AppDeploymentSteps["STARTING"] = "STARTING";
94
- AppDeploymentSteps["BUILDING_ASSETS"] = "BUILDING_ASSETS";
95
- AppDeploymentSteps["UPLOADING_ASSETS"] = "UPLOADING_ASSETS";
96
- AppDeploymentSteps["CONVERGING_STORAGE"] = "CONVERGING_STORAGE";
97
- AppDeploymentSteps["PUBLISHING_TREE"] = "PUBLISHING_TREE";
98
- AppDeploymentSteps["RELOADING_SANDBOX"] = "RELOADING_SANDBOX";
99
- AppDeploymentSteps["COMPLETED"] = "COMPLETED";
100
- })(AppDeploymentSteps || (AppDeploymentSteps = {}));
101
- const groupByProperty = (items, property)=>{
102
- const grouped = {};
103
- const defaultOtherIssues = "Other Issues";
104
- for (const item of items){
105
- if (item.node) {
106
- const value = item.node[property];
107
- if (value && !grouped[value]) {
108
- grouped[value] = [];
109
- grouped[value]?.push(item);
110
- } else if (value && grouped[value]) {
111
- grouped[value]?.push(item);
143
+ export const command = async (ctx)=>{
144
+ const directory = await loadSyncJsonDirectory(process.cwd());
145
+ const syncJson = await SyncJson.loadOrInit(ctx, {
146
+ directory
147
+ });
148
+ println({
149
+ ensureEmptyLineAbove: true
150
+ })`
151
+ Deploying ${syncJson.env.name} to ${terminalLink(syncJson.app.primaryDomain, `https://${syncJson.app.primaryDomain}/`)}
152
+ `;
153
+ const filesync = new FileSync(syncJson);
154
+ const hashes = await filesync.hashes(ctx);
155
+ if (!hashes.inSync && (hashes.localChangesToPush.size > 0 || !hashes.onlyDotGadgetFilesChanged)) {
156
+ // the following is true:
157
+ // 1. our local files don't match our environment's files
158
+ // 2. we have local changes to push or non .gadget/ files have changed on our environment
159
+ // therefor, we need to push before we can deploy
160
+ await filesync.print(ctx, {
161
+ hashes
162
+ });
163
+ println({
164
+ ensureEmptyLineAbove: true
165
+ })`
166
+ Your environment's files must match your local files before you can deploy.
167
+ `;
168
+ // some scenarios make the confirmation to push changes imply the
169
+ // --force flag (e.g. when both local and environment files have
170
+ // changed, or when only environment files have changed)
171
+ let implicitForce = false;
172
+ if (output.isInteractive) {
173
+ let message;
174
+ switch(true){
175
+ case hashes.bothChanged:
176
+ message = sprint`Would you like to push your local changes and {underline discard your environment's} changes now?`;
177
+ implicitForce = true;
178
+ break;
179
+ case hashes.localChangesToPush.size > 0:
180
+ message = sprint`Would you like to push your local changes now?`;
181
+ break;
182
+ case hashes.environmentChanges.size > 0:
183
+ message = sprint`Do you want to {underline discard your environment's} changes now?`;
184
+ implicitForce = true;
185
+ break;
186
+ default:
187
+ unreachable("no changes to push or discard");
112
188
  }
189
+ await confirm({
190
+ ensureEmptyLineAbove: true
191
+ })(message);
113
192
  } else {
114
- if (!grouped[defaultOtherIssues]) {
115
- grouped[defaultOtherIssues] = [];
116
- }
117
- grouped[defaultOtherIssues].push(item);
193
+ println({
194
+ ensureEmptyLineAbove: true
195
+ })`
196
+ Assuming you want to push your local files now.
197
+ `;
118
198
  }
119
- }
120
- return grouped;
121
- };
122
- /**
123
- * Runs the deploy process.
124
- */ export const command = async (ctx, firstRun = true)=>{
125
- const spinner = ora();
126
- let prevProgress = AppDeploymentStepsToAppDeployState("NOT_STARTED");
127
- let action;
128
- // deploy --force != sync --force
129
- const filesync = await FileSync.init(ctx.child({
130
- overwrite: {
131
- "--force": false
132
- }
133
- }));
134
- if (firstRun) {
135
- ctx.log.printlns`App: ${filesync.app.slug}`;
136
- }
137
- const { inSync } = await filesync.hashes();
138
- if (!inSync) {
139
- ctx.log.printlns`
140
- Local files have diverged from remote. Run a sync once to converge your files or keep {italic ggt sync} running in the background.
141
- `;
142
- action = await select(ctx, {
143
- message: "How would you like to proceed?",
144
- choices: [
145
- "Cancel (Ctrl+C)",
146
- "Sync once"
147
- ]
199
+ await filesync.push(ctx, {
200
+ hashes,
201
+ force: implicitForce || ctx.args["--force"]
148
202
  });
149
- switch(action){
150
- case "Sync once":
151
- {
152
- await filesync.sync();
153
- break;
154
- }
155
- case "Cancel (Ctrl+C)":
156
- {
157
- process.exit(0);
158
- }
159
- }
160
203
  }
161
- // subscribes to the graphql subscription that will listen and send back the server contract status
162
- const unsubscribe = filesync.edit.subscribe({
163
- subscription: REMOTE_SERVER_CONTRACT_STATUS_SUBSCRIPTION,
164
- variables: ()=>({
165
- localFilesVersion: String(filesync.filesVersion),
166
- force: ctx.args["--force"]
167
- }),
168
- onError: (error)=>{
169
- if (isCloseEvent(error.cause)) {
170
- spinner.fail("Failed");
171
- ctx.log.printlns(error.message);
172
- } else if (isGraphQLErrors(error.cause)) {
173
- const message = error.cause[0]?.message;
174
- if (message && message.includes("GGT_PAYMENT_REQUIRED")) {
175
- ctx.log.println("Production environment limit reached. Upgrade your plan to deploy");
176
- } else {
177
- ctx.log.println(`${message}`);
178
- }
179
- }
204
+ const variables = {
205
+ localFilesVersion: String(syncJson.filesVersion),
206
+ force: ctx.args["--allow-problems"],
207
+ allowCharges: ctx.args["--allow-charges"]
208
+ };
209
+ let spinner;
210
+ let currentStep = AppDeploymentSteps.NOT_STARTED;
211
+ let printedProblems = false;
212
+ const subscription = syncJson.edit.subscribe({
213
+ subscription: PUBLISH_STATUS_SUBSCRIPTION,
214
+ variables,
215
+ onError: async (error)=>{
180
216
  ctx.log.error("failed to deploy", {
181
217
  error
182
218
  });
183
- unsubscribe();
184
- return;
219
+ spinner?.fail(stepToSpinnerStart(syncJson, currentStep) + " " + ts());
220
+ if (isGraphQLErrors(error.cause)) {
221
+ const graphqlError = error.cause[0];
222
+ assert(graphqlError, "expected graphqlError to be defined");
223
+ switch(true){
224
+ case graphqlError.extensions["requiresUpgrade"]:
225
+ println({
226
+ ensureEmptyLineAbove: true
227
+ })(graphqlError.message.replace(/GGT_PAYMENT_REQUIRED:?\s*/, ""));
228
+ process.exit(1);
229
+ break;
230
+ case graphqlError.extensions["requiresAdditionalCharge"]:
231
+ println({
232
+ ensureEmptyLineAbove: true
233
+ })(graphqlError.message.replace(/GGT_PAYMENT_REQUIRED:?\s*/, ""));
234
+ await confirm({
235
+ ensureEmptyLineAbove: true
236
+ })("Do you want to continue?");
237
+ subscription.resubscribe({
238
+ ...variables,
239
+ allowCharges: true
240
+ });
241
+ return;
242
+ }
243
+ }
244
+ await reportErrorAndExit(ctx, error);
185
245
  },
186
246
  onData: async ({ publishStatus })=>{
187
- const { progress, issues, status } = publishStatus ?? {};
188
- const hasIssues = issues?.length;
189
- if (firstRun && hasIssues) {
190
- ctx.log.printlns`{underline Issues detected}`;
191
- const printIssues = (groupedIssues)=>{
192
- for (const [name, nodeArray] of Object.entries(groupedIssues)){
193
- ctx.log.println(`\n\n ${chalk.cyan(name)} ${chalk.redBright(nodeArray.length === 1 ? `${nodeArray.length} issue` : `${nodeArray.length} issues`)}${nodeArray.map((e)=>{
194
- if (!e.node) {
195
- return `\n\t ${chalk.red("✖")} ${e.message}`;
196
- }
197
- return `\n\t ${chalk.red("✖")} ${titleFormatter(e)}: ${e.nodeLabels?.map((label)=>`${chalk.bgWhite.black(label.type.toLowerCase())} ${chalk.white.bold(label.identifier)}`).join("")}`;
198
- }).join("")}`);
199
- }
200
- };
201
- const titleFormatter = (e)=>{
202
- if (e.node?.type === "SourceFile") {
203
- return `${chalk.magentaBright("Typescript")} ${e.message.replace(/[.,]+$/, "")}`;
204
- }
205
- return e.message.replace(/[.,]+$/, "");
206
- };
207
- const issuesWithNoNode = issues.filter((item)=>item.node?.apiIdentifier);
208
- const groupedByApiIdentifier = groupByProperty(issuesWithNoNode, "apiIdentifier");
209
- printIssues(groupedByApiIdentifier);
210
- const remainingItems = issues.filter((item)=>!item.node?.apiIdentifier);
211
- const groupedByName = groupByProperty(remainingItems, "name");
212
- printIssues(groupedByName);
213
- if (!ctx.args["--force"]) {
214
- unsubscribe();
215
- action = await select(ctx, {
216
- message: "Detected some issues with your app. How would you like to proceed?",
217
- choices: [
218
- "Cancel (Ctrl+C)",
219
- "Deploy anyways"
220
- ]
221
- });
222
- switch(action){
223
- case "Deploy anyways":
224
- {
225
- ctx.args["--force"] = true;
226
- await command(ctx, false);
227
- break;
228
- }
229
- case "Cancel (Ctrl+C)":
230
- {
231
- process.exit(0);
232
- }
233
- }
247
+ if (!publishStatus) {
248
+ ctx.log.warn("received empty publish status");
249
+ return;
250
+ }
251
+ const { publishStarted, progress: step, issues, status } = publishStatus;
252
+ if (!printedProblems && issues.length > 0) {
253
+ printedProblems = true;
254
+ const fatalIssues = issues.filter((issue)=>issue.severity === ProblemSeverity.Fatal);
255
+ if (fatalIssues.length > 0) {
256
+ await reportErrorAndExit(ctx, new DeployDisallowedError(publishIssuesToProblems(fatalIssues)));
234
257
  }
235
- firstRun = false;
236
- } else {
237
- const publishStatus = status ? status : undefined;
238
- const handleCompletion = (message, color)=>{
239
- spinner.stopAndPersist({
240
- symbol: color === "red" ? chalk.red("✖") : chalk.greenBright("✔"),
241
- text: color === "red" ? "Failed" : "DONE"
258
+ println({
259
+ ensureEmptyLineAbove: true
260
+ })`{bold Problems found.}`;
261
+ printProblems({
262
+ problems: publishIssuesToProblems(issues)
263
+ });
264
+ if (!publishStarted) {
265
+ await confirm("Do you want to continue?");
266
+ subscription.resubscribe({
267
+ ...variables,
268
+ force: true
242
269
  });
243
- ctx.log.printlns(color === "red" ? chalk.red(message) : chalk.green(message));
244
- if (publishStatus?.output) {
245
- ctx.log.printlns(`Cmd/Ctrl + Click: \u001b]8;;${publishStatus.output}\u0007View Logs\u001b]8;;\u0007`);
246
- }
247
- unsubscribe();
248
- };
249
- if (publishStatus && "code" in publishStatus && publishStatus.code === "Errored") {
250
- handleCompletion(publishStatus.message, "red");
251
- return;
270
+ } else {
271
+ assert(ctx.args["--allow-problems"], "expected --allow-problems to be true");
272
+ println({
273
+ ensureEmptyLineAbove: true
274
+ })`Deploying regardless of problems because {bold "--allow-problems"} was passed.`;
275
+ }
276
+ return;
277
+ }
278
+ if (status?.code === "Errored") {
279
+ subscription.unsubscribe();
280
+ spinner?.fail(stepToSpinnerStart(syncJson, currentStep) + " " + ts());
281
+ if (status.message) {
282
+ println({
283
+ ensureEmptyLineAbove: true
284
+ })`{red ${status.message}}`;
252
285
  }
253
- if (progress === "COMPLETED") {
254
- handleCompletion("Deploy completed. Good bye!", "green");
255
- return;
286
+ if (status.output) {
287
+ println({
288
+ ensureEmptyLineAbove: true
289
+ })`${terminalLink("Check logs", status.output)}`;
256
290
  }
257
- const currentProgress = AppDeploymentStepsToAppDeployState(progress);
258
- if (progress && currentProgress !== prevProgress) {
259
- if (progress !== "STARTING") {
260
- spinner.succeed("DONE");
261
- }
262
- prevProgress = currentProgress;
263
- ctx.log.printlns(`${currentProgress} ...`);
264
- spinner.start("Working ...");
291
+ return;
292
+ }
293
+ if (step === AppDeploymentSteps.COMPLETED) {
294
+ subscription.unsubscribe();
295
+ spinner?.succeed(stepToSpinnerEnd(syncJson, currentStep));
296
+ let message = sprint`{green Deploy successful!}`;
297
+ if (status?.output) {
298
+ message += ` ${terminalLink("Check logs", status.output)}.`;
299
+ }
300
+ println({
301
+ ensureEmptyLineAbove: true
302
+ })(message);
303
+ return;
304
+ }
305
+ if (step !== currentStep) {
306
+ const spinnerText = stepToSpinnerStart(syncJson, step);
307
+ if (spinnerText !== spinner?.text) {
308
+ // stop the current spinner, if any, and start a new one
309
+ spinner?.succeed(stepToSpinnerEnd(syncJson, currentStep));
310
+ const ensureEmptyLineAbove = currentStep === AppDeploymentSteps.NOT_STARTED || !output.isInteractive;
311
+ spinner = spin({
312
+ ensureEmptyLineAbove
313
+ })(spinnerText);
265
314
  }
315
+ currentStep = step;
266
316
  }
267
317
  }
268
318
  });
269
319
  };
320
+ export const AppDeploymentSteps = Object.freeze({
321
+ NOT_STARTED: "NOT_STARTED",
322
+ STARTING: "STARTING",
323
+ BUILDING_ASSETS: "BUILDING_ASSETS",
324
+ UPLOADING_ASSETS: "UPLOADING_ASSETS",
325
+ CONVERGING_STORAGE: "CONVERGING_STORAGE",
326
+ PUBLISHING_TREE: "PUBLISHING_TREE",
327
+ RELOADING_SANDBOX: "RELOADING_SANDBOX",
328
+ COMPLETED: "COMPLETED"
329
+ });
330
+ export const stepToSpinnerStart = (syncJson, step)=>{
331
+ switch(step){
332
+ case AppDeploymentSteps.NOT_STARTED:
333
+ case AppDeploymentSteps.STARTING:
334
+ case AppDeploymentSteps.BUILDING_ASSETS:
335
+ case AppDeploymentSteps.UPLOADING_ASSETS:
336
+ return "Building frontend assets.";
337
+ case AppDeploymentSteps.CONVERGING_STORAGE:
338
+ return "Setting up database.";
339
+ case AppDeploymentSteps.PUBLISHING_TREE:
340
+ return `Copying ${syncJson.env.name}.`;
341
+ case AppDeploymentSteps.RELOADING_SANDBOX:
342
+ return "Restarting app.";
343
+ case AppDeploymentSteps.COMPLETED:
344
+ return "Deploy complete!";
345
+ default:
346
+ return "Unknown step.";
347
+ }
348
+ };
349
+ export const stepToSpinnerEnd = (syncJson, step)=>{
350
+ switch(step){
351
+ case AppDeploymentSteps.NOT_STARTED:
352
+ case AppDeploymentSteps.STARTING:
353
+ case AppDeploymentSteps.BUILDING_ASSETS:
354
+ case AppDeploymentSteps.UPLOADING_ASSETS:
355
+ return `Built frontend assets. ${ts()}`;
356
+ case AppDeploymentSteps.CONVERGING_STORAGE:
357
+ return `Setup database. ${ts()}`;
358
+ case AppDeploymentSteps.PUBLISHING_TREE:
359
+ return `Copied ${syncJson.env.name}. ${ts()}`;
360
+ case AppDeploymentSteps.RELOADING_SANDBOX:
361
+ return `Restarted app. ${ts()}`;
362
+ case AppDeploymentSteps.COMPLETED:
363
+ return "Deploy successful!";
364
+ default:
365
+ return `Completed unknown step. ${ts()}`;
366
+ }
367
+ };
270
368
 
271
369
  //# sourceMappingURL=deploy.js.map