@superblocksteam/cli 1.13.0 → 1.14.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.
package/README.md CHANGED
@@ -12,7 +12,7 @@ $ npm install -g @superblocksteam/cli
12
12
  $ superblocks COMMAND
13
13
  running command...
14
14
  $ superblocks (--version)
15
- @superblocksteam/cli/1.13.0 linux-x64 node-v20.19.0
15
+ @superblocksteam/cli/1.14.0 linux-x64 node-v20.19.0
16
16
  $ superblocks --help [COMMAND]
17
17
  USAGE
18
18
  $ superblocks COMMAND
@@ -27,6 +27,7 @@ USAGE
27
27
  * [`superblocks components upload`](#superblocks-components-upload)
28
28
  * [`superblocks components watch`](#superblocks-components-watch)
29
29
  * [`superblocks config set PROPERTY VALUE`](#superblocks-config-set-property-value)
30
+ * [`superblocks deploy [RESOURCE_PATH]`](#superblocks-deploy-resource_path)
30
31
  * [`superblocks help [COMMAND]`](#superblocks-help-command)
31
32
  * [`superblocks init [RESOURCE_URL]`](#superblocks-init-resource_url)
32
33
  * [`superblocks login`](#superblocks-login)
@@ -150,6 +151,33 @@ EXAMPLES
150
151
  $ superblocks config set domain app.superblocks.com
151
152
  ```
152
153
 
154
+ ## `superblocks deploy [RESOURCE_PATH]`
155
+
156
+ Deploy resources to Superblocks
157
+
158
+ ```
159
+ USAGE
160
+ $ superblocks deploy [RESOURCE_PATH] [-c <value>]
161
+
162
+ ARGUMENTS
163
+ RESOURCE_PATH The path to the resource to deploy
164
+
165
+ FLAGS
166
+ -c, --commit-id=<value> The commit ID to deploy
167
+
168
+ DESCRIPTION
169
+ Deploy resources to Superblocks
170
+
171
+ EXAMPLES
172
+ $ superblocks deploy
173
+
174
+ $ superblocks deploy apps/my-app
175
+
176
+ $ superblocks deploy apps/my-app --commit-id commit-id
177
+
178
+ $ superblocks deploy --commit-id commit-id
179
+ ```
180
+
153
181
  ## `superblocks help [COMMAND]`
154
182
 
155
183
  Display help for superblocks.
@@ -7,7 +7,7 @@
7
7
  "lint:fix": "npx eslint . --fix"
8
8
  },
9
9
  "dependencies": {
10
- "@superblocksteam/custom-components": "1.13.0",
10
+ "@superblocksteam/custom-components": "1.14.0",
11
11
  "react": "^18",
12
12
  "react-dom": "^18"
13
13
  },
@@ -313,6 +313,6 @@ export default class CreateComponent extends AuthenticatedApplicationCommand {
313
313
  const headers = {
314
314
  [COMPONENT_EVENT_HEADER]: ComponentEvent.CREATE,
315
315
  };
316
- this.registerComponents(headers);
316
+ await this.registerComponents(headers);
317
317
  }
318
318
  }
@@ -0,0 +1,14 @@
1
+ import { AuthenticatedCommand } from "../common/authenticated-command.mjs";
2
+ export default class Deploy extends AuthenticatedCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ "commit-id": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ };
8
+ static args: {
9
+ resource_path: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
10
+ };
11
+ run(): Promise<void>;
12
+ private createTasks;
13
+ private getResourceIdsToDeploy;
14
+ }
@@ -0,0 +1,302 @@
1
+ import path from "node:path";
2
+ import { Args, Flags } from "@oclif/core";
3
+ import { ValidateGitSetupError } from "@superblocksteam/sdk";
4
+ import { ComponentEvent, getSuperblocksMonorepoConfigJson, getSuperblocksResourceConfigIfExists, } from "@superblocksteam/util";
5
+ import { ForbiddenError, BadRequestError, NotFoundError, } from "@superblocksteam/util";
6
+ import { green, yellow } from "colorette";
7
+ import { Listr } from "listr2";
8
+ import { isEmpty } from "lodash-es";
9
+ import { AuthenticatedCommand } from "../common/authenticated-command.mjs";
10
+ import { atLeastOneSelection, isCI, MULTI_SELECT_PROMPT_HELP, } from "../common/version-control.mjs";
11
+ var DeploymentStatus;
12
+ (function (DeploymentStatus) {
13
+ DeploymentStatus["SUCCESS"] = "SUCCESS";
14
+ DeploymentStatus["FAILED"] = "FAILED";
15
+ DeploymentStatus["SKIPPED"] = "SKIPPED";
16
+ })(DeploymentStatus || (DeploymentStatus = {}));
17
+ const BATCH_DEPLOY_SIZE = 5;
18
+ export default class Deploy extends AuthenticatedCommand {
19
+ static description = "Deploy resources to Superblocks";
20
+ static examples = [
21
+ "<%= config.bin %> <%= command.id %>",
22
+ "<%= config.bin %> <%= command.id %> apps/my-app",
23
+ "<%= config.bin %> <%= command.id %> apps/my-app --commit-id commit-id",
24
+ "<%= config.bin %> <%= command.id %> --commit-id commit-id",
25
+ ];
26
+ static flags = {
27
+ "commit-id": Flags.string({
28
+ char: "c",
29
+ description: "The commit ID to deploy",
30
+ required: false,
31
+ }),
32
+ };
33
+ static args = {
34
+ resource_path: Args.string({
35
+ description: "The path to the resource to deploy",
36
+ required: false,
37
+ }),
38
+ };
39
+ async run() {
40
+ const { flags, args } = await this.parse(Deploy);
41
+ const tasks = this.createTasks(args.resource_path, flags["commit-id"]);
42
+ await tasks.run();
43
+ }
44
+ createTasks(resourcePath, commitId) {
45
+ const tasks = new Listr([
46
+ {
47
+ title: "Checking for existing Superblocks project...",
48
+ task: async (ctx) => {
49
+ ctx.resourcePath = resourcePath;
50
+ ctx.commitId = commitId;
51
+ ctx.resourceIdsToSkip = new Set();
52
+ try {
53
+ [
54
+ ctx.existingSuperblocksRootConfig,
55
+ ctx.superblocksRootConfigPath,
56
+ ] = await getSuperblocksMonorepoConfigJson(true);
57
+ ctx.existingSuperblocksResourceConfig =
58
+ await getSuperblocksResourceConfigIfExists();
59
+ ctx.superblocksRootPath = path.resolve(path.dirname(ctx.superblocksRootConfigPath), "..");
60
+ }
61
+ catch {
62
+ this.error("No Superblocks project found in the current folder hierarchy. Run 'superblocks init' to initialize a new project.");
63
+ }
64
+ },
65
+ },
66
+ {
67
+ title: "Determining what resources to deploy...",
68
+ task: async (ctx, task) => {
69
+ const resourceIdsToDeploy = await this.getResourceIdsToDeploy(ctx, task, resourcePath);
70
+ ctx.resourcesToDeploy = resourceIdsToDeploy.map((resourceId) => {
71
+ const resourceConfig = ctx.existingSuperblocksRootConfig?.resources[resourceId];
72
+ return {
73
+ resourceId: resourceId,
74
+ resourceType: resourceConfig?.resourceType,
75
+ location: resourceConfig?.location,
76
+ };
77
+ });
78
+ },
79
+ },
80
+ {
81
+ title: "Validating git configuration...",
82
+ task: async (ctx, task) => {
83
+ const subtasks = [];
84
+ for (const resource of ctx.resourcesToDeploy) {
85
+ const resourceTitle = `${(resource.resourceType ?? "").toLowerCase()} ${resource.location}`;
86
+ subtasks.push({
87
+ title: `Checking ${resourceTitle}...`,
88
+ task: async () => {
89
+ try {
90
+ await this.validateGitSetup(resource.resourceType, resource.resourceId, ComponentEvent.DEPLOY);
91
+ }
92
+ catch (error) {
93
+ if (isCI() && error instanceof ValidateGitSetupError) {
94
+ this.log(`WARN: Failed to validate git setup for ${resourceTitle}. Skipping deploy for this resource.\n\n${error.message}.`);
95
+ ctx.resourceIdsToSkip.add(resource.resourceId);
96
+ }
97
+ else {
98
+ throw error;
99
+ }
100
+ }
101
+ },
102
+ });
103
+ }
104
+ return task.newListr(subtasks, {
105
+ concurrent: true,
106
+ });
107
+ },
108
+ },
109
+ {
110
+ task: async (ctx, task) => {
111
+ const subtasks = [];
112
+ const resourcesToDeploy = ctx.resourcesToDeploy;
113
+ task.title = `Deploying ${resourcesToDeploy.length} resources:`;
114
+ const resourceDeploymentResolvers = [];
115
+ const resourceDeploymentErrors = [];
116
+ const resourcesStatuses = [];
117
+ // Create all deployment tasks upfront to have statuses
118
+ resourcesToDeploy.forEach((resource) => {
119
+ const resourceConfig = ctx.existingSuperblocksRootConfig?.resources[resource.resourceId];
120
+ const resourceTitle = `${(resourceConfig.resourceType ?? "").toLowerCase()} ${resourceConfig.location}`;
121
+ const promise = new Promise((resolve) => {
122
+ resourceDeploymentResolvers.push(resolve);
123
+ resourcesStatuses.push({
124
+ message: "",
125
+ });
126
+ });
127
+ if (ctx.resourceIdsToSkip.has(resource.resourceId)) {
128
+ subtasks.push({
129
+ title: resourceTitle,
130
+ options: {
131
+ showSubtasks: true,
132
+ skip: true,
133
+ },
134
+ task: async (_ctx, task) => {
135
+ task.title = `${resourceTitle} skipped`;
136
+ },
137
+ });
138
+ }
139
+ else {
140
+ subtasks.push({
141
+ title: resourceTitle,
142
+ options: { showSubtasks: true, spinner: true },
143
+ task: async (_ctx, task) => {
144
+ const result = await promise;
145
+ if (result.status === DeploymentStatus.SUCCESS) {
146
+ task.title = `${resourceTitle} deployed with commit ${result.commitId}`;
147
+ }
148
+ else if (result.status === DeploymentStatus.SKIPPED) {
149
+ const skipMessage = `${yellow("[!]")} ${resourceTitle} deployment skipped: ${result.error}`;
150
+ task.skip(skipMessage);
151
+ }
152
+ else if (result.status === DeploymentStatus.FAILED) {
153
+ task.title = `${resourceTitle} failed with error: ${result.error}`;
154
+ throw new Error(`FAILURE: ${resourceTitle}: Deploy failed with error: ${result.error}`);
155
+ }
156
+ },
157
+ });
158
+ }
159
+ });
160
+ // Create hidden tasks for batch requests to server
161
+ subtasks.push({
162
+ title: "",
163
+ task: async () => {
164
+ try {
165
+ for (let resourceIdx = 0; resourceIdx < resourcesToDeploy.length; resourceIdx += BATCH_DEPLOY_SIZE) {
166
+ const resourceBatch = resourcesToDeploy.slice(resourceIdx, resourceIdx + BATCH_DEPLOY_SIZE);
167
+ // Create a batch of promises for concurrent execution
168
+ const deployPromises = resourceBatch.map(async (resource, resultIdx) => {
169
+ const taskIndex = resourceIdx + resultIdx;
170
+ const resourceTitle = `${(resource.resourceType ?? "").toLowerCase()} ${resource.location}`;
171
+ if (ctx.resourceIdsToSkip.has(resource.resourceId)) {
172
+ return;
173
+ }
174
+ try {
175
+ let result;
176
+ if (resource.resourceType === "APPLICATION") {
177
+ result = await this.getSdk().deployApplication(resource.resourceId, commitId);
178
+ }
179
+ else if (resource.resourceType === "BACKEND") {
180
+ result = await this.getSdk().deployApi(resource.resourceId, commitId);
181
+ }
182
+ else {
183
+ throw new Error(`Unsupported resource type: ${resource.resourceType}`);
184
+ }
185
+ resourceDeploymentResolvers[taskIndex]({
186
+ status: DeploymentStatus.SUCCESS,
187
+ commitId: result.commitId,
188
+ });
189
+ resourcesStatuses[taskIndex].status =
190
+ DeploymentStatus.SUCCESS;
191
+ resourcesStatuses[taskIndex].message =
192
+ `${resourceTitle} deployed with commit ${result.commitId}`;
193
+ }
194
+ catch (error) {
195
+ if (error instanceof ForbiddenError ||
196
+ error instanceof BadRequestError ||
197
+ error instanceof NotFoundError) {
198
+ resourceDeploymentResolvers[taskIndex]({
199
+ status: DeploymentStatus.SKIPPED,
200
+ error: error.message,
201
+ });
202
+ resourcesStatuses[taskIndex].status =
203
+ DeploymentStatus.SKIPPED;
204
+ resourcesStatuses[taskIndex].message =
205
+ `${resourceTitle} deployment skipped: ${error.message}`;
206
+ }
207
+ else {
208
+ resourceDeploymentResolvers[taskIndex]({
209
+ status: DeploymentStatus.FAILED,
210
+ error: error.message,
211
+ });
212
+ resourceDeploymentErrors.push(new Error(`FAILURE: ${resourceTitle}: Deploy failed with error: ${error.message}`));
213
+ }
214
+ }
215
+ });
216
+ // Wait for all promises in this batch to complete
217
+ await Promise.all(deployPromises);
218
+ }
219
+ }
220
+ finally {
221
+ const header = "===================================";
222
+ if (resourceDeploymentErrors.length > 0) {
223
+ this.log(`${header} ERRORS ${header}`);
224
+ resourceDeploymentErrors.forEach((e) => this.log(e.message));
225
+ }
226
+ else {
227
+ this.log(`${header} STATUSES ${header}`);
228
+ resourcesStatuses.forEach((statuses) => {
229
+ if (statuses.status === DeploymentStatus.SUCCESS) {
230
+ this.log(`${green("✔")} ${statuses.message}`);
231
+ }
232
+ else if (statuses.status === DeploymentStatus.SKIPPED) {
233
+ this.log(`${yellow("[!]")} ${statuses.message}`);
234
+ }
235
+ });
236
+ }
237
+ }
238
+ },
239
+ });
240
+ return task.newListr(subtasks, {
241
+ concurrent: true,
242
+ exitOnError: false,
243
+ persistentOutput: true,
244
+ collapse: false,
245
+ showSubtasks: true,
246
+ });
247
+ },
248
+ },
249
+ ], {
250
+ concurrent: false,
251
+ });
252
+ return tasks;
253
+ }
254
+ async getResourceIdsToDeploy(ctx, task, resourcePath, supportedResourceTypes = ["APPLICATION", "BACKEND"]) {
255
+ const resources = Object.entries(ctx.existingSuperblocksRootConfig?.resources ?? {}).filter(([, resource]) => {
256
+ return supportedResourceTypes.includes(resource.resourceType);
257
+ });
258
+ if (isEmpty(resources)) {
259
+ throw new Error("No resources found in the current project");
260
+ }
261
+ else if (!isEmpty(resourcePath)) {
262
+ const resource = resources.find(([, resource]) => resource.location === resourcePath);
263
+ if (resource) {
264
+ return [resource[0]];
265
+ }
266
+ throw new Error(`No resource found with the given location: ${resourcePath}`);
267
+ }
268
+ const resourceConfig = await getSuperblocksResourceConfigIfExists();
269
+ if (resourceConfig) {
270
+ return [resourceConfig.id];
271
+ }
272
+ const choices = [];
273
+ const initialSelections = [];
274
+ let counter = 0;
275
+ for (const [resourceId, resource] of resources) {
276
+ choices.push({
277
+ name: resourceId,
278
+ message: resource.location,
279
+ });
280
+ if (ctx.existingSuperblocksResourceConfig?.id === resourceId) {
281
+ initialSelections.push(counter);
282
+ }
283
+ counter++;
284
+ }
285
+ const resourcesToDeploy = choices.length === 1
286
+ ? [choices[0].name]
287
+ : await task.prompt([
288
+ {
289
+ type: "AutoComplete",
290
+ name: "resourcesToDeploy",
291
+ message: `Select resources to deploy (${MULTI_SELECT_PROMPT_HELP})`,
292
+ choices: choices,
293
+ initial: initialSelections,
294
+ multiple: true,
295
+ validate: atLeastOneSelection,
296
+ prefix: "▸",
297
+ indicator: "◉",
298
+ },
299
+ ]);
300
+ return resourcesToDeploy;
301
+ }
302
+ }
@@ -235,6 +235,9 @@ export default class Initialize extends AuthenticatedCommand {
235
235
  }
236
236
  // create superblocks.json file
237
237
  await fs.writeFile(ctx.superblocksRootConfigPath ?? RESOURCE_CONFIG_PATH, JSON.stringify(sortByKey(superblocksConfig), null, 2));
238
+ if (ctx.existingSuperblocksRootConfig) {
239
+ this.log(`Superblocks resources added at ${ctx.superblocksRootConfigPath?.replace(RESOURCE_CONFIG_PATH, "")}`);
240
+ }
238
241
  },
239
242
  },
240
243
  ], {
@@ -614,18 +614,20 @@ function getExistingFilePathsForBackendApi(superblocksBackendConfig, location) {
614
614
  }
615
615
  export function addExistingFilePathsForApi(api, location, paths, useNestedFolder) {
616
616
  const apiNameSlug = slugifyName(api.name);
617
- if (api.sourceFiles?.length) {
617
+ if (api.sourceFiles) {
618
+ // File version 0.2.0 and later
618
619
  // API files are in an API-specific folder
619
620
  const apiDirPath = useNestedFolder
620
621
  ? `${location}/${apiNameSlug}`
621
622
  : location;
622
- paths.add(`${apiDirPath}/${apiNameSlug}.yaml`);
623
+ paths.add(`${apiDirPath}/api.yaml`);
623
624
  // And there are source files
624
625
  for (const sourceFile of api.sourceFiles ?? []) {
625
626
  paths.add(`${apiDirPath}/${sourceFile}`);
626
627
  }
627
628
  }
628
629
  else {
630
+ // Pre-file version 0.2.0
629
631
  // API file is in the 'apis' folder with no separate source files
630
632
  paths.add(`${location}/${apiNameSlug}.yaml`);
631
633
  }
@@ -59,6 +59,47 @@
59
59
  "commits.mjs"
60
60
  ]
61
61
  },
62
+ "deploy": {
63
+ "aliases": [],
64
+ "args": {
65
+ "resource_path": {
66
+ "description": "The path to the resource to deploy",
67
+ "name": "resource_path",
68
+ "required": false
69
+ }
70
+ },
71
+ "description": "Deploy resources to Superblocks",
72
+ "examples": [
73
+ "<%= config.bin %> <%= command.id %>",
74
+ "<%= config.bin %> <%= command.id %> apps/my-app",
75
+ "<%= config.bin %> <%= command.id %> apps/my-app --commit-id commit-id",
76
+ "<%= config.bin %> <%= command.id %> --commit-id commit-id"
77
+ ],
78
+ "flags": {
79
+ "commit-id": {
80
+ "char": "c",
81
+ "description": "The commit ID to deploy",
82
+ "name": "commit-id",
83
+ "required": false,
84
+ "hasDynamicHelp": false,
85
+ "multiple": false,
86
+ "type": "option"
87
+ }
88
+ },
89
+ "hasDynamicHelp": false,
90
+ "hiddenAliases": [],
91
+ "id": "deploy",
92
+ "pluginAlias": "@superblocksteam/cli",
93
+ "pluginName": "@superblocksteam/cli",
94
+ "pluginType": "core",
95
+ "strict": true,
96
+ "isESM": true,
97
+ "relativePath": [
98
+ "dist",
99
+ "commands",
100
+ "deploy.mjs"
101
+ ]
102
+ },
62
103
  "init": {
63
104
  "aliases": [],
64
105
  "args": {
@@ -303,45 +344,6 @@
303
344
  "rm.mjs"
304
345
  ]
305
346
  },
306
- "config:set": {
307
- "aliases": [],
308
- "args": {
309
- "property": {
310
- "description": "Superblocks config name, e.g. domain",
311
- "name": "property",
312
- "options": [
313
- "domain"
314
- ],
315
- "required": true
316
- },
317
- "value": {
318
- "description": "Superblocks config value",
319
- "name": "value",
320
- "required": true
321
- }
322
- },
323
- "description": "Sets the specified property in your Superblocks configuration. A property governs the behavior of the Superblocks CLI, such as Superblocks region to interact with",
324
- "examples": [
325
- "<%= config.bin %> <%= command.id %> domain eu.superblocks.com",
326
- "<%= config.bin %> <%= command.id %> domain app.superblocks.com"
327
- ],
328
- "flags": {},
329
- "hasDynamicHelp": false,
330
- "hiddenAliases": [],
331
- "id": "config:set",
332
- "pluginAlias": "@superblocksteam/cli",
333
- "pluginName": "@superblocksteam/cli",
334
- "pluginType": "core",
335
- "strict": true,
336
- "enableJsonFlag": false,
337
- "isESM": true,
338
- "relativePath": [
339
- "dist",
340
- "commands",
341
- "config",
342
- "set.mjs"
343
- ]
344
- },
345
347
  "components:create": {
346
348
  "aliases": [],
347
349
  "args": {},
@@ -444,7 +446,46 @@
444
446
  "components",
445
447
  "watch.mjs"
446
448
  ]
449
+ },
450
+ "config:set": {
451
+ "aliases": [],
452
+ "args": {
453
+ "property": {
454
+ "description": "Superblocks config name, e.g. domain",
455
+ "name": "property",
456
+ "options": [
457
+ "domain"
458
+ ],
459
+ "required": true
460
+ },
461
+ "value": {
462
+ "description": "Superblocks config value",
463
+ "name": "value",
464
+ "required": true
465
+ }
466
+ },
467
+ "description": "Sets the specified property in your Superblocks configuration. A property governs the behavior of the Superblocks CLI, such as Superblocks region to interact with",
468
+ "examples": [
469
+ "<%= config.bin %> <%= command.id %> domain eu.superblocks.com",
470
+ "<%= config.bin %> <%= command.id %> domain app.superblocks.com"
471
+ ],
472
+ "flags": {},
473
+ "hasDynamicHelp": false,
474
+ "hiddenAliases": [],
475
+ "id": "config:set",
476
+ "pluginAlias": "@superblocksteam/cli",
477
+ "pluginName": "@superblocksteam/cli",
478
+ "pluginType": "core",
479
+ "strict": true,
480
+ "enableJsonFlag": false,
481
+ "isESM": true,
482
+ "relativePath": [
483
+ "dist",
484
+ "commands",
485
+ "config",
486
+ "set.mjs"
487
+ ]
447
488
  }
448
489
  },
449
- "version": "1.13.0"
490
+ "version": "1.14.0"
450
491
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superblocksteam/cli",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "description": "Official Superblocks CLI",
5
5
  "bin": {
6
6
  "superblocks": "./bin/run"
@@ -29,8 +29,8 @@
29
29
  "slugify": "^1.6.6",
30
30
  "vite": "^6.2.0",
31
31
  "yaml": "^2.6.1",
32
- "@superblocksteam/sdk": "1.13.0",
33
- "@superblocksteam/util": "1.13.0"
32
+ "@superblocksteam/sdk": "1.14.0",
33
+ "@superblocksteam/util": "1.14.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@oclif/test": "^4.1.11",
@@ -55,6 +55,7 @@
55
55
  "oclif": "^4.17.32",
56
56
  "prettier": "^3.5.2",
57
57
  "shx": "^0.3.3",
58
+ "sinon": "^20.0.0",
58
59
  "ts-node": "^10.9.1",
59
60
  "tslib": "^2.8.0",
60
61
  "typescript": "^5.7.0",
@@ -97,7 +98,7 @@
97
98
  "lint:fix": "eslint . --fix",
98
99
  "check": "pnpm run lint && pnpm run typecheck",
99
100
  "posttest": "npm run lint",
100
- "test": "NODE_OPTIONS='--loader ts-node/esm' mocha --forbid-only \"test/**/*.test.*ts\"",
101
+ "test": "NODE_OPTIONS='--loader ts-node/esm/transpile-only --trace-warnings' mocha --forbid-only \"test/**/*.test.*ts\"",
101
102
  "typecheck": "tsc --noEmit",
102
103
  "update-readme": "oclif readme"
103
104
  }