@superblocksteam/cli 0.0.20 → 0.0.21

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/0.0.20 linux-x64 node-v18.17.0
15
+ @superblocksteam/cli/0.0.21 linux-x64 node-v18.17.0
16
16
  $ superblocks --help [COMMAND]
17
17
  USAGE
18
18
  $ superblocks COMMAND
@@ -29,6 +29,7 @@ USAGE
29
29
  * [`superblocks help [COMMANDS]`](#superblocks-help-commands)
30
30
  * [`superblocks init [RESOURCEURL]`](#superblocks-init-resourceurl)
31
31
  * [`superblocks login`](#superblocks-login)
32
+ * [`superblocks migrate`](#superblocks-migrate)
32
33
  * [`superblocks pull [ONLY]`](#superblocks-pull-only)
33
34
 
34
35
  ## `superblocks components create`
@@ -154,7 +155,7 @@ EXAMPLES
154
155
 
155
156
  ## `superblocks login`
156
157
 
157
- Authenticates with Superblocks cloud
158
+ Authenticate with Superblocks cloud
158
159
 
159
160
  ```
160
161
  USAGE
@@ -164,7 +165,22 @@ FLAGS
164
165
  -t, --token=<value> Superblocks user API key
165
166
 
166
167
  DESCRIPTION
167
- Authenticates with Superblocks cloud
168
+ Authenticate with Superblocks cloud
169
+ ```
170
+
171
+ ## `superblocks migrate`
172
+
173
+ Migrate files to use the current CLI version
174
+
175
+ ```
176
+ USAGE
177
+ $ superblocks migrate
178
+
179
+ DESCRIPTION
180
+ Migrate files to use the current CLI version
181
+
182
+ EXAMPLES
183
+ $ superblocks migrate
168
184
  ```
169
185
 
170
186
  ## `superblocks pull [ONLY]`
@@ -1,20 +1,23 @@
1
1
  import React, { useCallback, useState } from "react";
2
+ import { useSuperblocksContext } from "@superblocksteam/custom-components";
3
+ import { type Props, type EventTriggers } from "./types";
2
4
  import { Task, ErrorComponent, validateTasks } from "./validation";
3
- import { Props } from "./types";
4
5
  import "./main.scss";
5
6
 
6
- export default function Component({
7
- updateStatefulProperties,
8
- tasks,
9
- onTaskAdded,
10
- onTaskStatusChanged,
11
- }: Props) {
12
- const { validatedTasks, hasError } = validateTasks(tasks);
7
+ export default function Component({ tasks }: Props) {
8
+ const {
9
+ updateProperties,
10
+ events : {
11
+ onTaskAdded,
12
+ onTaskStatusChanged,
13
+ },
14
+ } = useSuperblocksContext<Props, EventTriggers>();
15
+ const { validatedTasks, hasError } = validateTasks(tasks ?? {});
13
16
  const [value, setValue] = useState("");
14
17
 
15
18
  const onTodoAdded = useCallback(() => {
16
19
  const id = Math.random().toString(36).substring(2, 8);
17
- updateStatefulProperties({
20
+ updateProperties({
18
21
  tasks: {
19
22
  ...validatedTasks,
20
23
  [id]: {
@@ -24,11 +27,11 @@ export default function Component({
24
27
  },
25
28
  });
26
29
  onTaskAdded();
27
- }, [updateStatefulProperties, validatedTasks, value, onTaskAdded]);
30
+ }, [updateProperties, validatedTasks, value, onTaskAdded]);
28
31
 
29
32
  const onTaskStatusChange = useCallback(
30
33
  (id: string, status: boolean) => {
31
- updateStatefulProperties({
34
+ updateProperties({
32
35
  tasks: {
33
36
  ...validatedTasks,
34
37
  [id]: {
@@ -39,7 +42,7 @@ export default function Component({
39
42
  });
40
43
  onTaskStatusChanged();
41
44
  },
42
- [updateStatefulProperties, validatedTasks, tasks, onTaskStatusChanged]
45
+ [updateProperties, validatedTasks, tasks, onTaskStatusChanged]
43
46
  );
44
47
 
45
48
  return hasError ? (
@@ -7,7 +7,7 @@
7
7
  "lint:fix": "npx eslint . --fix"
8
8
  },
9
9
  "dependencies": {
10
- "@superblocksteam/custom-components": "0.0.20",
10
+ "@superblocksteam/custom-components": "0.0.21",
11
11
  "react": "^18",
12
12
  "react-dom": "^18"
13
13
  },
@@ -1,6 +1,7 @@
1
1
  import { AuthenticatedApplicationCommand } from "../../common/authenticated-command";
2
2
  export default class CreateComponent extends AuthenticatedApplicationCommand {
3
3
  static description: string;
4
+ private dataTypeToDefaultControlType;
4
5
  private initializeComponentByWizard;
5
6
  private initializeComponentByExample;
6
7
  run(): Promise<void>;
@@ -27,11 +27,26 @@ const tsStringify = (obj) => {
27
27
  return `{\n${stringifiedEntries.join(",\n")}\n},\n`;
28
28
  };
29
29
  class CreateComponent extends authenticated_command_1.AuthenticatedApplicationCommand {
30
+ constructor() {
31
+ super(...arguments);
32
+ this.dataTypeToDefaultControlType = (propertyType) => {
33
+ switch (propertyType) {
34
+ case "any":
35
+ return "js-expr";
36
+ case "boolean":
37
+ return "switch";
38
+ default:
39
+ return "text";
40
+ }
41
+ };
42
+ }
30
43
  async initializeComponentByWizard(isFirstTimeCreate) {
44
+ this.log(`${(0, colorette_1.cyanBright)("ℹ")} Follow this wizard to configure your Custom Component. You can always edit the generated config.ts file directly to modify your properties or events.`);
45
+ this.log();
31
46
  const displayName = (await (0, enquirer_1.prompt)({
32
47
  type: "input",
33
48
  name: "displayName",
34
- message: "What is the display name of the component you want to create? (e.g. Date Picker)",
49
+ message: "What is the display name of the component you want to create? (e.g. To-Do List)",
35
50
  validate: (response) => !(0, lodash_1.isEmpty)(response.trim()),
36
51
  })).displayName;
37
52
  const name = (await (0, enquirer_1.prompt)({
@@ -39,112 +54,121 @@ class CreateComponent extends authenticated_command_1.AuthenticatedApplicationCo
39
54
  name: "name",
40
55
  message: "What is the machine readable name of the component you want to create?",
41
56
  validate: (response) => (0, identifiers_1.isValidIdentifier)(response) || "Invalid identifier",
42
- initial: (0, identifiers_1.suggestIdentifier)(displayName, true) || "DatePicker",
57
+ initial: (0, identifiers_1.suggestIdentifier)(displayName, true) || "ToDoList",
43
58
  })).name;
44
59
  this.log();
45
- this.log(`${(0, colorette_1.cyanBright)("ℹ")} A ${(0, colorette_1.bold)("stateful property")} determines what shows up in the properties panel of a custom component`);
60
+ this.log(`${(0, colorette_1.cyanBright)("ℹ")} Properties represent the state of the component. You will define how each property is made available to the rest of your App and whether each property is displayed in the Properties Panel. Read more about properties here: ${(0, colorette_1.magenta)("https://docs.superblocks.com/applications/custom-components/#properties--events")}`);
46
61
  this.log();
47
- const hasStatefulProps = (await (0, enquirer_1.prompt)({
62
+ const hasProperties = (await (0, enquirer_1.prompt)({
48
63
  type: "confirm",
49
- name: "hasStatefulProps",
50
- message: `Does this component have ${(0, colorette_1.bold)("stateful properties")}?`,
64
+ name: "hasProperties",
65
+ message: `Does this component have ${(0, colorette_1.bold)("properties")}?`,
51
66
  initial: false,
52
- })).hasStatefulProps;
53
- const statefulProps = [];
54
- if (hasStatefulProps) {
67
+ })).hasProperties;
68
+ const properties = [];
69
+ if (hasProperties) {
55
70
  for (;;) {
56
- const statefulPropName = (await (0, enquirer_1.prompt)({
57
- type: "input",
58
- name: "statefulPropName",
59
- message: "What is the label of the stateful prop? (e.g. Selected Date)",
60
- validate: (response) => !(0, lodash_1.isEmpty)(response.trim()),
61
- })).statefulPropName.trim();
62
- const statefulPropPath = (await (0, enquirer_1.prompt)({
71
+ const propertyPath = (await (0, enquirer_1.prompt)({
63
72
  type: "input",
64
73
  name: "path",
65
- initial: (0, identifiers_1.suggestIdentifier)(statefulPropName) || "selectedDate",
66
- message: "What is the machine readable name of the stateful prop?",
74
+ message: "What is the path of the property? This will be used to access the property in your code (e.g. currentTasks)",
67
75
  validate: (response) => {
68
76
  if (!(0, identifiers_1.isValidIdentifier)(response))
69
77
  return "Invalid identifier";
70
- if (statefulProps.some((v) => v.path === response))
78
+ if (properties.some((v) => v.path === response))
71
79
  return "Duplicate property name";
72
80
  return true;
73
81
  },
74
82
  })).path;
75
- const statefulPropType = (await (0, enquirer_1.prompt)({
83
+ const propertyType = (await (0, enquirer_1.prompt)({
76
84
  type: "select",
77
85
  name: "type",
78
- message: `What input type should ${statefulPropName} be?`,
79
- choices: Object.entries(util_1.inputTypeDefinions).map(([k, v]) => ({
86
+ message: `What is the type of ${propertyPath}?`,
87
+ choices: Object.entries(util_1.dataTypeDefinions).map(([k, v]) => ({
80
88
  name: k,
81
89
  message: v.prompt,
82
90
  value: k,
83
91
  })),
84
92
  initial: 0,
85
93
  })).type;
86
- const placeholderText = (await (0, enquirer_1.prompt)({
87
- type: "input",
94
+ const showInPropsPanel = (await (0, enquirer_1.prompt)({
88
95
  name: "value",
89
- message: `What placeholder text should ${statefulPropName} have, if any?`,
90
- required: false,
96
+ type: "confirm",
97
+ message: "Should this property be shown in the properties panel?",
98
+ initial: true,
91
99
  })).value;
92
- statefulProps.push({
93
- label: statefulPropName,
94
- path: statefulPropPath,
95
- inputType: statefulPropType,
96
- placeholder: placeholderText || undefined,
100
+ let propertiesPanelDisplay = undefined;
101
+ if (showInPropsPanel) {
102
+ this.log();
103
+ this.log((0, colorette_1.bold)("Properties Panel Display Configuration"));
104
+ const propertyLabel = (await (0, enquirer_1.prompt)({
105
+ type: "input",
106
+ name: "label",
107
+ message: "What is the label of the property? (e.g. Tasks)",
108
+ validate: (response) => !(0, lodash_1.isEmpty)(response.trim()),
109
+ })).label.trim();
110
+ propertiesPanelDisplay = {
111
+ label: propertyLabel,
112
+ controlType: this.dataTypeToDefaultControlType(propertyType),
113
+ };
114
+ }
115
+ properties.push({
116
+ path: propertyPath,
117
+ dataType: propertyType,
118
+ propertiesPanelDisplay,
97
119
  });
98
- const addAnotherStatefulProp = (await (0, enquirer_1.prompt)({
120
+ this.log(`You can configure the remaining display attributes (tooltip, placeholder, etc) in the config.ts file directly.`);
121
+ this.log();
122
+ const addAnotherProperty = (await (0, enquirer_1.prompt)({
99
123
  type: "confirm",
100
124
  name: "value",
101
- message: "Do you want to add another stateful prop?",
125
+ message: "Do you want to add another property?",
102
126
  initial: false,
103
127
  })).value;
104
- if (!addAnotherStatefulProp) {
128
+ if (!addAnotherProperty) {
105
129
  break;
106
130
  }
107
131
  }
108
132
  }
109
133
  this.log();
110
- this.log(`${(0, colorette_1.cyanBright)("ℹ")} ${(0, colorette_1.bold)("Event handlers")} represent the events your custom component can fire`);
134
+ this.log(`${(0, colorette_1.cyanBright)("ℹ")} ${(0, colorette_1.bold)("Events")} represent the types of events that the component can trigger in Superblocks. Learn more about events: ${(0, colorette_1.magenta)("https://docs.superblocks.com/applications/custom-components/#properties--events")}`);
111
135
  this.log();
112
136
  const hasEventHandlers = (await (0, enquirer_1.prompt)({
113
137
  type: "confirm",
114
138
  name: "value",
115
- message: `Does this component have ${(0, colorette_1.bold)("event handlers")}? (e.g. a date picker component that has a On Change event handler)`,
139
+ message: `Does this component have ${(0, colorette_1.bold)("events")}? (e.g. a date picker component that triggers an On Change event)`,
116
140
  initial: false,
117
141
  })).value;
118
- const eventHandlers = [];
142
+ const events = [];
119
143
  if (hasEventHandlers) {
120
144
  for (;;) {
121
145
  const eventHandlerName = (await (0, enquirer_1.prompt)({
122
146
  name: "value",
123
147
  type: "input",
124
- message: "What is the label of the event handler? (e.g. On Change)",
148
+ message: "What is the label of the event? (e.g. On Change)",
125
149
  validate: (response) => !(0, lodash_1.isEmpty)(response.trim()),
126
150
  })).value.trim();
127
151
  const eventHandlerPath = (await (0, enquirer_1.prompt)({
128
152
  name: "value",
129
- message: "What is the machine readable name of the event handler?",
153
+ message: "What is the path of the event? This will be used to trigger the event in your code (e.g. onChange)",
130
154
  type: "input",
131
155
  initial: (0, identifiers_1.suggestIdentifier)(eventHandlerName) || "onChange",
132
156
  validate: (response) => {
133
157
  if (!(0, identifiers_1.isValidIdentifier)(response))
134
158
  return "Invalid identifier";
135
- if (eventHandlers.some((v) => v.path === response))
159
+ if (events.some((v) => v.path === response))
136
160
  return "Duplicate property name";
137
161
  return true;
138
162
  },
139
163
  })).value;
140
- eventHandlers.push({
164
+ events.push({
141
165
  label: eventHandlerName,
142
166
  path: eventHandlerPath,
143
167
  });
144
168
  const addAnotherEventHandler = (await (0, enquirer_1.prompt)({
145
169
  name: "value",
146
170
  type: "confirm",
147
- message: "Do you want to add another event handler?",
171
+ message: "Do you want to add another event?",
148
172
  initial: false,
149
173
  })).value;
150
174
  if (!addAnotherEventHandler) {
@@ -156,14 +180,14 @@ class CreateComponent extends authenticated_command_1.AuthenticatedApplicationCo
156
180
  id: (0, node_crypto_1.randomUUID)(),
157
181
  name,
158
182
  displayName,
159
- statefulPropsRendered: statefulProps
160
- .map((statefulProp) => tsStringify(statefulProp))
183
+ propertiesRendered: properties
184
+ .map((property) => tsStringify(property))
161
185
  .join(""),
162
- eventHandlersRendered: eventHandlers
186
+ eventHandlersRendered: events
163
187
  .map((eventHandler) => tsStringify(eventHandler))
164
188
  .join(""),
165
189
  });
166
- const componentTsx = (0, create_component_defaults_1.getDefaultComponentTsx)(statefulProps, eventHandlers.map((prop) => prop.path));
190
+ const componentTsx = (0, create_component_defaults_1.getDefaultComponentTsx)(properties, events.map((prop) => prop.path));
167
191
  if (isFirstTimeCreate) {
168
192
  await fs.copy(DEFAULT_PACKAGE_JSON_TEMPLATE_PATH, ".");
169
193
  }
@@ -173,23 +197,26 @@ class CreateComponent extends authenticated_command_1.AuthenticatedApplicationCo
173
197
  return {
174
198
  name,
175
199
  displayName,
176
- statefulProps,
177
- eventHandlers,
200
+ properties,
201
+ events,
178
202
  };
179
203
  }
180
204
  async initializeComponentByExample() {
181
205
  const response = {
182
- displayName: "Example Component",
183
- name: "ExampleComponent",
184
- statefulProps: [
206
+ displayName: "To-Do List (Example)",
207
+ name: "ToDoList",
208
+ properties: [
185
209
  {
186
- label: "Default Tasks",
187
210
  path: "tasks",
188
- inputType: "js",
189
- placeholder: "{ taskId: { taskName: 'Task Name', taskStatus: 'complete' | 'todo' } }",
211
+ dataType: "any",
212
+ propertiesPanelDisplay: {
213
+ label: "Default Tasks",
214
+ placeholder: "{ taskId: { taskName: 'Task Name', taskStatus: 'complete' | 'todo' } }",
215
+ controlType: "js-expr",
216
+ },
190
217
  },
191
218
  ],
192
- eventHandlers: [
219
+ events: [
193
220
  {
194
221
  label: "On Task Added",
195
222
  path: "onTaskAdded",
@@ -204,10 +231,10 @@ class CreateComponent extends authenticated_command_1.AuthenticatedApplicationCo
204
231
  id: (0, node_crypto_1.randomUUID)(),
205
232
  name: response.name,
206
233
  displayName: response.displayName,
207
- statefulPropsRendered: response.statefulProps
208
- .map((statefulProp) => tsStringify(statefulProp))
234
+ propertiesRendered: response.properties
235
+ .map((property) => tsStringify(property))
209
236
  .join(""),
210
- eventHandlersRendered: response.eventHandlers
237
+ eventHandlersRendered: response.events
211
238
  .map((eventHandler) => tsStringify(eventHandler))
212
239
  .join(""),
213
240
  });
@@ -218,31 +245,38 @@ class CreateComponent extends authenticated_command_1.AuthenticatedApplicationCo
218
245
  return response;
219
246
  }
220
247
  async run() {
248
+ this.log();
221
249
  const isFirstTimeCreate = !(await fs.exists("package.json"));
222
- const initExampleComponent = isFirstTimeCreate &&
223
- (await (0, enquirer_1.prompt)({
250
+ let response;
251
+ if (isFirstTimeCreate) {
252
+ this.log(`${(0, colorette_1.cyanBright)("ℹ")} Welcome to Custom Components! In order to help you get started, we’ve built an example To-Do List Component. You can use this component as a template as you build your own Custom Component. To work with this component, we recommend following along with our quickstart guide: ${(0, colorette_1.magenta)("https://docs.superblocks.com/applications/custom-components/quickstart")}`);
253
+ this.log();
254
+ this.log(`If you choose to generate the example component, you will not have to provide any additional inputs. You can re-run superblocks components create once you are ready to create your own component from scratch. `);
255
+ this.log();
256
+ const initExampleComponent = (await (0, enquirer_1.prompt)({
224
257
  name: "value",
225
258
  type: "confirm",
226
- message: "Would you like to use a pre-generated example custom component?",
259
+ message: "Would you like to generate an example custom component?",
227
260
  initial: false,
228
261
  })).value;
229
- let response;
230
- if (!initExampleComponent) {
231
- response = await this.initializeComponentByWizard(isFirstTimeCreate);
262
+ response = initExampleComponent
263
+ ? await this.initializeComponentByExample()
264
+ : await this.initializeComponentByWizard(isFirstTimeCreate);
232
265
  }
233
266
  else {
234
- response = await this.initializeComponentByExample();
267
+ response = await this.initializeComponentByWizard(isFirstTimeCreate);
235
268
  }
236
269
  this.log((0, colorette_1.green)("Successfully created component! Added the following files:"));
237
270
  this.log();
238
271
  const tree = core_1.ux.tree();
239
272
  tree.insert("components");
240
273
  tree.nodes.components.insert(response.name);
241
- tree.nodes.components.nodes[response.name].insert("config.ts # update this file to configure your component's properties panel");
274
+ tree.nodes.components.nodes[response.name].insert("config.ts # update this file to manage your component's properties and events");
275
+ tree.nodes.components.nodes[response.name].insert("types.ts # this file contains your react component type definitions and is automatically updated while you run 'superblocks components watch'");
242
276
  tree.nodes.components.nodes[response.name].insert("component.tsx # your component's react code");
243
277
  tree.display();
244
278
  this.log();
245
- this.log(`${(0, colorette_1.green)("Remember to run $ ")}${(0, colorette_1.cyan)("superblocks components watch")}${(0, colorette_1.green)(" to watch for changes to your component files")}`);
279
+ this.log(`${(0, colorette_1.green)("Remember to run $ ")}${(0, colorette_1.magenta)("superblocks components watch")}${(0, colorette_1.green)(" to watch for changes to your component files")}`);
246
280
  this.log();
247
281
  this.log(`Edit your component's react code here:
248
282
  ${(0, colorette_1.green)(`components/${response.name}/component.tsx`)}`);
@@ -32,7 +32,6 @@ class Watch extends authenticated_command_1.AuthenticatedApplicationCommand {
32
32
  await this.registerComponents();
33
33
  this.log((0, colorette_1.yellow)("Remember to refresh your application to see any newly registered components."));
34
34
  this.log();
35
- this.log((0, colorette_1.green)(`Visit ${await this.getBaseUrl()} and access your application in edit mode to see your components! 🐨`));
36
35
  const port = 3002;
37
36
  const editModeUrl = await this.getEditModeUrl();
38
37
  const viteLogger = (0, vite_1.createLogger)();
@@ -71,7 +70,7 @@ class Watch extends authenticated_command_1.AuthenticatedApplicationCommand {
71
70
  this.log();
72
71
  this.log((0, colorette_1.bold)((0, colorette_1.magenta)(`${editModeUrl}?devMode=true`)));
73
72
  this.log();
74
- this.log((0, colorette_1.yellow)("Remember to refresh your already open Application page and turn on local development in Superblocks to connect to your local server!"));
73
+ this.log((0, colorette_1.yellow)(`Please ensure that Local Dev Mode is enabled in your Application so that the component is fetched from your local dev server. Learn more about Local Dev Mode here: ${(0, colorette_1.magenta)("https://docs.superblocks.com/applications/custom-components/development-lifecycle#local-development-mode")}`));
75
74
  })();
76
75
  }
77
76
  }
@@ -265,7 +265,7 @@ function getResourceIdFromUrl(resourceUrl) {
265
265
  return [url.pathname.split("/")[2], "APPLICATION"];
266
266
  }
267
267
  else if (url.pathname.startsWith("/workflows") ||
268
- url.pathname.startsWith("/scheduled-jobs")) {
268
+ url.pathname.startsWith("/scheduled_jobs")) {
269
269
  return [url.pathname.split("/")[2], "BACKEND"];
270
270
  }
271
271
  throw new Error(`Failed to parse resource URL: ${resourceUrl}`);
@@ -49,7 +49,7 @@ class Login extends core_1.Command {
49
49
  }
50
50
  }
51
51
  }
52
- Login.description = "Authenticates with Superblocks cloud";
52
+ Login.description = "Authenticate with Superblocks cloud";
53
53
  Login.flags = {
54
54
  // flag with a value (-t, --token=VALUE)
55
55
  token: core_1.Flags.string({
@@ -0,0 +1,9 @@
1
+ import { AuthenticatedCommand } from "../common/authenticated-command";
2
+ export default class Migrate extends AuthenticatedCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ run(): Promise<void>;
6
+ private createTasks;
7
+ private getResourceIdsToMigrate;
8
+ private migrateApplication;
9
+ }
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const node_child_process_1 = require("node:child_process");
5
+ const node_path_1 = tslib_1.__importDefault(require("node:path"));
6
+ const node_util_1 = tslib_1.__importDefault(require("node:util"));
7
+ const util_1 = require("@superblocksteam/util");
8
+ const colorette_1 = require("colorette");
9
+ const fs = tslib_1.__importStar(require("fs-extra"));
10
+ const listr2_1 = require("listr2");
11
+ const semver_1 = tslib_1.__importDefault(require("semver"));
12
+ const authenticated_command_1 = require("../common/authenticated-command");
13
+ const version_control_1 = require("../common/version-control");
14
+ class Migrate extends authenticated_command_1.AuthenticatedCommand {
15
+ async run() {
16
+ const tasks = this.createTasks();
17
+ await tasks.run();
18
+ }
19
+ createTasks() {
20
+ const tasks = new listr2_1.Listr([
21
+ {
22
+ title: "Checking for existing Superblocks project...",
23
+ task: async (ctx) => {
24
+ try {
25
+ [
26
+ ctx.existingSuperblocksRootConfig,
27
+ ctx.superblocksRootConfigPath,
28
+ ] = await (0, util_1.getSuperblocksMonorepoConfigJson)(true);
29
+ ctx.existingSuperblocksResourceConfig =
30
+ await (0, util_1.getSuperblocksResourceConfigIfExists)();
31
+ }
32
+ catch {
33
+ // no existing superblocks config
34
+ this.error(`No Superblocks project found in the current folder hierarchy. Run ${(0, colorette_1.cyan)("superblocks init")} to initialize a new project.`);
35
+ }
36
+ },
37
+ },
38
+ {
39
+ title: "Migrating resources...",
40
+ task: async (ctx, task) => {
41
+ var _a;
42
+ const resourceIdsToMigrate = await this.getResourceIdsToMigrate(ctx, task);
43
+ const subtasks = [];
44
+ const superblocksRootPath = node_path_1.default.resolve(node_path_1.default.dirname(ctx.superblocksRootConfigPath), "..");
45
+ for (const resourceId of resourceIdsToMigrate) {
46
+ const resource = (_a = ctx.existingSuperblocksRootConfig) === null || _a === void 0 ? void 0 : _a.resources[resourceId];
47
+ switch (resource === null || resource === void 0 ? void 0 : resource.resourceType) {
48
+ case "APPLICATION": {
49
+ subtasks.push({
50
+ title: `Migrating application ${resource.location}...`,
51
+ task: async (_ctx, task) => {
52
+ await this.migrateApplication(resource, superblocksRootPath);
53
+ task.title += `: done`;
54
+ },
55
+ });
56
+ break;
57
+ }
58
+ case "BACKEND": {
59
+ subtasks.push({
60
+ title: `Migrating backend ${resource.location}...`,
61
+ task: async (_ctx, task) => {
62
+ task.title += `: done`;
63
+ },
64
+ });
65
+ break;
66
+ }
67
+ default: {
68
+ this.error(`Unsupported resource type, resource: ${JSON.stringify(resource)}
69
+ `);
70
+ }
71
+ }
72
+ }
73
+ return task.newListr(subtasks, {
74
+ concurrent: true,
75
+ });
76
+ },
77
+ },
78
+ ], {
79
+ concurrent: false,
80
+ });
81
+ return tasks;
82
+ }
83
+ async getResourceIdsToMigrate(ctx, task) {
84
+ var _a, _b, _c, _d, _e;
85
+ const choices = [];
86
+ const initialSelections = [];
87
+ choices.push({
88
+ name: "All Resources",
89
+ message: "",
90
+ });
91
+ let counter = 1;
92
+ for (const [resourceId, resource] of Object.entries((_b = (_a = ctx.existingSuperblocksRootConfig) === null || _a === void 0 ? void 0 : _a.resources) !== null && _b !== void 0 ? _b : {})) {
93
+ choices.push({
94
+ name: resourceId,
95
+ message: resource.location,
96
+ });
97
+ if (((_c = ctx.existingSuperblocksResourceConfig) === null || _c === void 0 ? void 0 : _c.id) === resourceId) {
98
+ initialSelections.push(counter);
99
+ }
100
+ counter++;
101
+ }
102
+ const resourceIdsToMigrate = choices.length === 1
103
+ ? [choices[0].name]
104
+ : await task.prompt([
105
+ {
106
+ type: "MultiSelect",
107
+ name: "resourceIdsToMigrate",
108
+ message: `Select resources to migrate (${version_control_1.MULTI_SELECT_PROMPT_HELP})`,
109
+ choices: choices,
110
+ initial: initialSelections,
111
+ validate: version_control_1.atLeastOneSelection,
112
+ // @ts-expect-error listr2 types are wrong for prefix
113
+ prefix: "▸",
114
+ indicator: "◉",
115
+ },
116
+ ]);
117
+ if (resourceIdsToMigrate[0] === "All Resources") {
118
+ return Object.entries((_e = (_d = ctx.existingSuperblocksRootConfig) === null || _d === void 0 ? void 0 : _d.resources) !== null && _e !== void 0 ? _e : {}).map(([id]) => id);
119
+ }
120
+ return resourceIdsToMigrate;
121
+ }
122
+ async migrateApplication(applicationResource, superblocksRootPath) {
123
+ var _a;
124
+ this.log("Checking CLI version compatibility...");
125
+ try {
126
+ const packageJson = await fs.readJson(node_path_1.default.join(superblocksRootPath, applicationResource.location, "package.json"));
127
+ const versionStr = (_a = packageJson.dependencies) === null || _a === void 0 ? void 0 : _a["@superblocksteam/custom-components"];
128
+ if (!semver_1.default.satisfies(this.config.version, versionStr)) {
129
+ this.log("Migrating application dependencies...");
130
+ await node_util_1.default.promisify(node_child_process_1.exec)(`npm install @superblocksteam/custom-components@${this.config.version}`, {
131
+ cwd: node_path_1.default.join(superblocksRootPath, applicationResource.location),
132
+ });
133
+ }
134
+ else {
135
+ this.log("CLI version matches package.json version");
136
+ }
137
+ }
138
+ catch (e) {
139
+ this.error(e.message, {
140
+ exit: 1,
141
+ });
142
+ }
143
+ }
144
+ }
145
+ Migrate.description = "Migrate files to use the current CLI version";
146
+ Migrate.examples = ["<%= config.bin %> <%= command.id %>"];
147
+ exports.default = Migrate;
@@ -6,6 +6,7 @@ export declare abstract class AuthenticatedCommand extends Command {
6
6
  protected init(): Promise<void>;
7
7
  protected getBaseUrl(): Promise<string>;
8
8
  protected getSdk(): SuperblocksSdk;
9
+ protected runAutomatedDotfileUpdates(): Promise<void>;
9
10
  }
10
11
  export declare abstract class AuthenticatedApplicationCommand extends AuthenticatedCommand {
11
12
  applicationConfig: SuperblocksApplicationConfig;
@@ -2,10 +2,17 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AuthenticatedApplicationCommand = exports.AuthenticatedCommand = void 0;
4
4
  const tslib_1 = require("tslib");
5
+ const node_path_1 = tslib_1.__importDefault(require("node:path"));
5
6
  const core_1 = require("@oclif/core");
6
7
  const sdk_1 = require("@superblocksteam/sdk");
7
8
  const util_1 = require("@superblocksteam/util");
9
+ const colorette_1 = require("colorette");
8
10
  const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
11
+ const semver_1 = tslib_1.__importDefault(require("semver"));
12
+ const yaml_1 = require("yaml");
13
+ const migrationWarningsForApplications_1 = require("../util/migrationWarningsForApplications");
14
+ const migrationsForDotfiles_1 = require("../util/migrationsForDotfiles");
15
+ const version_control_1 = require("./version-control");
9
16
  class AuthenticatedCommand extends core_1.Command {
10
17
  async init() {
11
18
  await super.init();
@@ -16,7 +23,7 @@ class AuthenticatedCommand extends core_1.Command {
16
23
  }
17
24
  const { token, superblocksBaseUrl } = result;
18
25
  this.sdk = new sdk_1.SuperblocksSdk(token, superblocksBaseUrl);
19
- await (0, util_1.updatedMonorepoConfig)(this.config.version);
26
+ await this.runAutomatedDotfileUpdates();
20
27
  }
21
28
  catch (e) {
22
29
  console.log(e.message);
@@ -34,6 +41,62 @@ class AuthenticatedCommand extends core_1.Command {
34
41
  }
35
42
  return this.sdk;
36
43
  }
44
+ async runAutomatedDotfileUpdates() {
45
+ let previousConfig;
46
+ let rootConfigPath;
47
+ try {
48
+ [previousConfig, rootConfigPath] = await (0, util_1.getSuperblocksMonorepoConfigJson)(true);
49
+ }
50
+ catch {
51
+ // This might fail for a brand-new project folder, which is deliberately ignored
52
+ return;
53
+ }
54
+ if (previousConfig &&
55
+ rootConfigPath &&
56
+ (0, migrationsForDotfiles_1.isDotfileMigrationNeeded)(previousConfig.metadata.cliVersion, this.config.version)) {
57
+ this.log(`Migrating Superblocks settings from ${previousConfig.metadata.cliVersion} to ${this.config.version}...`);
58
+ try {
59
+ const newConfig = await (0, migrationsForDotfiles_1.migrateSuperblocksMonorepoConfig)(previousConfig, {
60
+ previousVersion: previousConfig.metadata.cliVersion,
61
+ currentVersion: this.config.version,
62
+ });
63
+ await fs_extra_1.default.writeFile(rootConfigPath, JSON.stringify((0, version_control_1.sortByKey)(newConfig), null, 2));
64
+ await Promise.all(Object.entries(newConfig.resources).map(async ([, resource]) => {
65
+ this.log(`Migrating ${resource.location}`);
66
+ try {
67
+ const resourcePath = node_path_1.default.join(rootConfigPath, "../..", resource.location, ".superblocks/superblocks.json");
68
+ const resourceConfig = await fs_extra_1.default.readJSON(resourcePath);
69
+ const newConfig = await (0, migrationsForDotfiles_1.migrateSuperblocksEntityConfig)(resourceConfig, {
70
+ previousVersion: previousConfig
71
+ .metadata.cliVersion,
72
+ currentVersion: this.config.version,
73
+ });
74
+ await fs_extra_1.default.writeFile(resourcePath, JSON.stringify((0, version_control_1.sortByKey)(newConfig), null, 2));
75
+ }
76
+ catch (e) {
77
+ this.error(e);
78
+ }
79
+ }));
80
+ this.log(`Finalizing migration`);
81
+ this.log(`After upgrading the CLI version, you may need to perform additional migrations using
82
+ ${(0, colorette_1.cyan)("superblocks migrate")}`);
83
+ }
84
+ catch (e) {
85
+ this.error(e);
86
+ }
87
+ }
88
+ if (previousConfig.metadata.cliVersion) {
89
+ const warningMessage = (0, migrationWarningsForApplications_1.getWarningsForApplicationMigration)(previousConfig.metadata.cliVersion, this.config.version);
90
+ if (warningMessage) {
91
+ this.log(warningMessage);
92
+ }
93
+ }
94
+ // Always upgrade the root config even if there's no migration
95
+ if (rootConfigPath) {
96
+ previousConfig.metadata.cliVersion = this.config.version;
97
+ await fs_extra_1.default.writeFile(rootConfigPath, JSON.stringify((0, version_control_1.sortByKey)(previousConfig), null, 2));
98
+ }
99
+ }
37
100
  }
38
101
  exports.AuthenticatedCommand = AuthenticatedCommand;
39
102
  class AuthenticatedApplicationCommand extends AuthenticatedCommand {
@@ -52,6 +115,7 @@ class AuthenticatedApplicationCommand extends AuthenticatedCommand {
52
115
  return new URL(`/applications/${this.applicationConfig.id}/pages/${this.applicationConfig.defaultPageId}/edit`, baseUrl).toString();
53
116
  }
54
117
  async init() {
118
+ var _a;
55
119
  await super.init();
56
120
  try {
57
121
  this.applicationConfig = await (0, util_1.getSuperblocksApplicationConfigJson)();
@@ -61,8 +125,44 @@ class AuthenticatedApplicationCommand extends AuthenticatedCommand {
61
125
  exit: 1,
62
126
  });
63
127
  }
128
+ try {
129
+ const appYaml = await fs_extra_1.default.readFile("application.yaml", "utf-8");
130
+ const appJson = await (0, yaml_1.parse)(appYaml);
131
+ if ((_a = appJson === null || appJson === void 0 ? void 0 : appJson.settings) === null || _a === void 0 ? void 0 : _a.cliVersion) {
132
+ const warningMessage = (0, migrationWarningsForApplications_1.getWarningsForApplicationMigration)(appJson.settings.cliVersion, this.config.version);
133
+ if (warningMessage) {
134
+ this.log(warningMessage);
135
+ }
136
+ }
137
+ }
138
+ catch (e) {
139
+ this.warn(`Could not read page.yaml to determine version compatibility: ${e.message}`);
140
+ }
64
141
  }
65
142
  async registerComponents() {
143
+ var _a;
144
+ core_1.ux.action.start("Checking CLI version compatibility...");
145
+ try {
146
+ const packageJson = await fs_extra_1.default.readJson("package.json");
147
+ const versionStr = (_a = packageJson.dependencies) === null || _a === void 0 ? void 0 : _a["@superblocksteam/custom-components"];
148
+ if (!versionStr) {
149
+ throw new Error(`You must install the @superblocksteam/custom-components library as a dependency in your package.json file. Run:
150
+
151
+ ${(0, colorette_1.cyan)("superblocks migrate")}`);
152
+ }
153
+ if (!semver_1.default.satisfies(this.config.version, versionStr)) {
154
+ throw new Error(`You must upgrade the @superblocksteam/custom-components library. You are using ${versionStr} but the CLI is ${this.config.version}. Run:
155
+
156
+ ${(0, colorette_1.cyan)("superblocks migrate")}`);
157
+ }
158
+ }
159
+ catch (e) {
160
+ core_1.ux.action.stop();
161
+ this.error(e.message, {
162
+ exit: 1,
163
+ });
164
+ }
165
+ core_1.ux.action.stop();
66
166
  core_1.ux.action.start("Scanning for Superblocks components...");
67
167
  const exists = await fs_extra_1.default.pathExists(util_1.CUSTOM_COMPONENTS_PATH);
68
168
  if (!exists) {
@@ -1,9 +1,9 @@
1
- import { type StatefulProperty } from "@superblocksteam/util";
2
- export declare const getDefaultConfigTs: ({ id, name, displayName, statefulPropsRendered, eventHandlersRendered, }: {
1
+ import { type Property } from "@superblocksteam/util";
2
+ export declare const getDefaultConfigTs: ({ id, name, displayName, propertiesRendered, eventHandlersRendered, }: {
3
3
  id: string;
4
4
  name: string;
5
5
  displayName: string;
6
- statefulPropsRendered: string;
6
+ propertiesRendered: string;
7
7
  eventHandlersRendered: string;
8
8
  }) => string;
9
- export declare const getDefaultComponentTsx: (statefulProps: StatefulProperty[], eventHandlers: string[]) => string;
9
+ export declare const getDefaultComponentTsx: (properties: Property[], eventHandlers: string[]) => string;
@@ -8,7 +8,7 @@ const indent = (str, spaces) => {
8
8
  .map((line) => `${spacesString}${line}`)
9
9
  .join("\n");
10
10
  };
11
- const getDefaultConfigTs = ({ id, name, displayName, statefulPropsRendered, eventHandlersRendered, }) => `import { type ComponentConfig } from "@superblocksteam/custom-components";
11
+ const getDefaultConfigTs = ({ id, name, displayName, propertiesRendered, eventHandlersRendered, }) => `import { type ComponentConfig } from "@superblocksteam/custom-components";
12
12
 
13
13
  export default {
14
14
  // DO NOT CHANGE THE ID ONCE THE COMPONENT HAS BEEN REGISTERED!
@@ -16,20 +16,20 @@ export default {
16
16
  name: "${name}",
17
17
  displayName: "${displayName}",
18
18
  componentPath: "components/${name}/component.tsx",
19
- statefulProperties: [${indent(statefulPropsRendered, 4).trim()}],
20
- eventHandlers: [${indent(eventHandlersRendered, 4).trim()}],
19
+ properties: [${indent(propertiesRendered, 4).trim()}],
20
+ events: [${indent(eventHandlersRendered, 4).trim()}],
21
21
  } satisfies ComponentConfig;
22
22
  `;
23
23
  exports.getDefaultConfigTs = getDefaultConfigTs;
24
- const getDefaultComponentTsx = (statefulProps, eventHandlers) => `import React from "react";
24
+ const getDefaultComponentTsx = (properties, eventHandlers) => `import React from "react";
25
25
  import { useSuperblocksContext } from "@superblocksteam/custom-components";
26
26
  import { type Props, type EventTriggers } from "./types";
27
27
 
28
- export default function Component({${statefulProps.length === 0
28
+ export default function Component({${properties.length === 0
29
29
  ? ""
30
- : "\n" + statefulProps.map((v) => indent(v.path, 2) + ",\n").join("")}}: Props) {
30
+ : "\n" + properties.map((v) => indent(v.path, 2) + ",\n").join("")}}: Props) {
31
31
  const {
32
- updateStatefulProperties,
32
+ updateProperties,
33
33
  events: {${"\n" + eventHandlers.map((v) => indent(v, 6) + ",\n").join("")} },
34
34
  } = useSuperblocksContext<Props, EventTriggers>();
35
35
 
@@ -88,7 +88,7 @@ async function downloadFile(rootDirectory, filepath, url) {
88
88
  return Promise.resolve("");
89
89
  }
90
90
  async function writeResourceToDisk(resourceType, resourceId, resource, now, rootPath, existingRelativeLocation) {
91
- var _a, _b, _c, _d, _e, _f, _g;
91
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
92
92
  switch (resourceType) {
93
93
  case "APPLICATION": {
94
94
  const parentDirName = "apps";
@@ -135,7 +135,9 @@ async function writeResourceToDisk(resourceType, resourceId, resource, now, root
135
135
  }
136
136
  await fs.ensureDir(`${appDirName}/${util_1.SUPERBLOCKS_HOME_FOLDER_NAME}`);
137
137
  await fs.writeFile(`${appDirName}/${util_1.RESOURCE_CONFIG_PATH}`, JSON.stringify(sortByKey(applicationConfig), null, 2));
138
- const createdFiles = await Promise.resolve(resource.componentFiles.map((file) => downloadFile(appDirName, file.filename, file.url)));
138
+ const createdFiles = await Promise.resolve(
139
+ // Defensive check for when application settings are missing componentFiles
140
+ (_f = (_e = resource.componentFiles) === null || _e === void 0 ? void 0 : _e.map((file) => downloadFile(appDirName, file.filename, file.url))) !== null && _f !== void 0 ? _f : []);
139
141
  // print out failed downloads synchronously here
140
142
  createdFiles
141
143
  .filter((createdFiles) => createdFiles.length)
@@ -149,7 +151,7 @@ async function writeResourceToDisk(resourceType, resourceId, resource, now, root
149
151
  }
150
152
  case "BACKEND": {
151
153
  const parentDirName = "backends";
152
- const apiName = slugifyName((_g = (_f = (_e = resource.apiPb) === null || _e === void 0 ? void 0 : _e.metadata) === null || _f === void 0 ? void 0 : _f.name) !== null && _g !== void 0 ? _g : resource.actions.name);
154
+ const apiName = slugifyName((_j = (_h = (_g = resource.apiPb) === null || _g === void 0 ? void 0 : _g.metadata) === null || _h === void 0 ? void 0 : _h.name) !== null && _j !== void 0 ? _j : resource.actions.name);
153
155
  // server is still sending actions for a backwards compatibility
154
156
  delete resource.actions;
155
157
  const newRelativeLocation = `${parentDirName}/${apiName}`;
@@ -0,0 +1 @@
1
+ export declare function getWarningsForApplicationMigration(previousVersion: string, newVersion: string): string | undefined;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getWarningsForApplicationMigration = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const colorette_1 = require("colorette");
6
+ const semver_1 = tslib_1.__importDefault(require("semver"));
7
+ // If you are changing how application files work on disk, for example changing the directory layout,
8
+ // you need to tell the user how to upgrade manually.
9
+ const BREAKING_APPLICATION_VERSIONS = [
10
+ {
11
+ version: "0.0.20",
12
+ // This is an example message because 0.0.20 does not have a docs page for any migrations. This will be added
13
+ // in a future release
14
+ message: `${(0, colorette_1.red)("Warning")}: Your code must be updated due to a breaking change in custom component definitions. See docs.`,
15
+ },
16
+ {
17
+ version: "0.0.21",
18
+ // This is an example message because 0.0.21 does not have a docs page for any migrations. This will be added
19
+ // in a future release
20
+ message: `${(0, colorette_1.red)("Warning")}: You will not be able to use CLI commands on custom components until you manually migrate your components,
21
+ due to breaking changes in the config.ts format and custom component API.
22
+
23
+ ${(0, colorette_1.bold)("Your existing components are safe.")}
24
+
25
+ To manually migrate:
26
+
27
+ 1. Rename "eventHandlers" to "events".
28
+ 2. Update your properties to use the new format: ${(0, colorette_1.magenta)("https://docs.superblocks.com/applications/custom-components/development-lifecycle#configts")}
29
+ `,
30
+ },
31
+ ];
32
+ function getWarningsForApplicationMigration(previousVersion, newVersion) {
33
+ const firstBreakingChange = BREAKING_APPLICATION_VERSIONS.find(({ version }) => {
34
+ return (semver_1.default.lt(previousVersion, version) && semver_1.default.lte(version, newVersion));
35
+ });
36
+ return firstBreakingChange === null || firstBreakingChange === void 0 ? void 0 : firstBreakingChange.message;
37
+ }
38
+ exports.getWarningsForApplicationMigration = getWarningsForApplicationMigration;
@@ -0,0 +1,10 @@
1
+ import { SuperblocksApplicationConfig, SuperblocksBackendConfig, SuperblocksMonorepoConfig } from "@superblocksteam/util";
2
+ export declare function migrateSuperblocksMonorepoConfig(config: SuperblocksMonorepoConfig, options: {
3
+ previousVersion: string;
4
+ currentVersion: string;
5
+ }): Promise<SuperblocksMonorepoConfig>;
6
+ export declare function migrateSuperblocksEntityConfig(config: SuperblocksApplicationConfig | SuperblocksBackendConfig, options: {
7
+ previousVersion: string;
8
+ currentVersion: string;
9
+ }): Promise<SuperblocksApplicationConfig | SuperblocksBackendConfig>;
10
+ export declare function isDotfileMigrationNeeded(previousVersion: string, currentVersion: string): boolean;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isDotfileMigrationNeeded = exports.migrateSuperblocksEntityConfig = exports.migrateSuperblocksMonorepoConfig = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const semver_1 = tslib_1.__importDefault(require("semver"));
6
+ // These migrations are used to upgrade the Superblocks config files as needed.
7
+ // For example if we add a new required field
8
+ const MonorepoMigratorsFromOldVersion = [
9
+ {
10
+ migrateWhenUpgradingTo: "0.0.19",
11
+ migrator: (config) => config,
12
+ },
13
+ ];
14
+ // Recursively migrate configs to the latest version
15
+ async function migrateSuperblocksMonorepoConfig(config, options) {
16
+ return await sharedMigrator(MonorepoMigratorsFromOldVersion, config, options);
17
+ }
18
+ exports.migrateSuperblocksMonorepoConfig = migrateSuperblocksMonorepoConfig;
19
+ // These migrations are used to upgrade the Superblocks config files as needed.
20
+ // For example if we add a new required field
21
+ const EntityMigratorsFromOldVersion = [
22
+ {
23
+ migrateWhenUpgradingTo: "0.0.19",
24
+ migrator: (config) => config,
25
+ },
26
+ ];
27
+ async function migrateSuperblocksEntityConfig(config, options) {
28
+ return await sharedMigrator(EntityMigratorsFromOldVersion, config, options);
29
+ }
30
+ exports.migrateSuperblocksEntityConfig = migrateSuperblocksEntityConfig;
31
+ function isDotfileMigrationNeeded(previousVersion, currentVersion) {
32
+ return (previousVersion !== currentVersion &&
33
+ (MonorepoMigratorsFromOldVersion.some(({ migrateWhenUpgradingTo }) => semver_1.default.gt(migrateWhenUpgradingTo, previousVersion) &&
34
+ semver_1.default.lte(migrateWhenUpgradingTo, currentVersion)) ||
35
+ EntityMigratorsFromOldVersion.some(({ migrateWhenUpgradingTo }) => semver_1.default.gt(migrateWhenUpgradingTo, previousVersion) &&
36
+ semver_1.default.lte(migrateWhenUpgradingTo, currentVersion))));
37
+ }
38
+ exports.isDotfileMigrationNeeded = isDotfileMigrationNeeded;
39
+ async function sharedMigrator(migratorObject, config, { previousVersion, currentVersion, }) {
40
+ let newConfig = JSON.parse(JSON.stringify(config));
41
+ // Find all migrators in the range between previousVersion and currentVersion
42
+ const migratorFns = migratorObject.filter(({ migrateWhenUpgradingTo }) => semver_1.default.gt(migrateWhenUpgradingTo, previousVersion) &&
43
+ semver_1.default.lte(migrateWhenUpgradingTo, currentVersion));
44
+ migratorFns.forEach(({ migrator, migrateWhenUpgradingTo }) => {
45
+ try {
46
+ newConfig = migrator(newConfig);
47
+ }
48
+ catch (e) {
49
+ // Migrations fail if our preconditions aren't met
50
+ console.warn(`Error while migrating config to version ${migrateWhenUpgradingTo}: ${e.message}`);
51
+ throw e;
52
+ }
53
+ });
54
+ return newConfig;
55
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.0.20",
2
+ "version": "0.0.21",
3
3
  "commands": {
4
4
  "init": {
5
5
  "id": "init",
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "login": {
39
39
  "id": "login",
40
- "description": "Authenticates with Superblocks cloud",
40
+ "description": "Authenticate with Superblocks cloud",
41
41
  "strict": true,
42
42
  "pluginName": "@superblocksteam/cli",
43
43
  "pluginAlias": "@superblocksteam/cli",
@@ -54,6 +54,20 @@
54
54
  },
55
55
  "args": {}
56
56
  },
57
+ "migrate": {
58
+ "id": "migrate",
59
+ "description": "Migrate files to use the current CLI version",
60
+ "strict": true,
61
+ "pluginName": "@superblocksteam/cli",
62
+ "pluginAlias": "@superblocksteam/cli",
63
+ "pluginType": "core",
64
+ "aliases": [],
65
+ "examples": [
66
+ "<%= config.bin %> <%= command.id %>"
67
+ ],
68
+ "flags": {},
69
+ "args": {}
70
+ },
57
71
  "pull": {
58
72
  "id": "pull",
59
73
  "description": "Download objects from Superblocks and save them locally",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superblocksteam/cli",
3
- "version": "0.0.20",
3
+ "version": "0.0.21",
4
4
  "description": "Official Superblocks CLI",
5
5
  "bin": {
6
6
  "superblocks": "bin/run"
@@ -29,6 +29,7 @@
29
29
  "fs-extra": "^11.1.1",
30
30
  "listr2": "6.6.0",
31
31
  "lodash": "^4.17.21",
32
+ "semver": "^7.5.4",
32
33
  "slugify": "^1.6.6",
33
34
  "vite": "^4.4.8",
34
35
  "vite-plugin-inspect": "^0.7.28",