@superblocksteam/cli 0.0.20 → 0.0.22

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.22 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 ? (
@@ -0,0 +1,2 @@
1
+ node_modules/
2
+ dist/
@@ -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.22",
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,124 @@ 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,
119
+ // Optional but include them by default
120
+ isExternallyReadable: true,
121
+ isExternallySettable: true,
97
122
  });
98
- const addAnotherStatefulProp = (await (0, enquirer_1.prompt)({
123
+ this.log(`You can configure the remaining display attributes (placeholder, etc) in the config.ts file directly.`);
124
+ this.log();
125
+ const addAnotherProperty = (await (0, enquirer_1.prompt)({
99
126
  type: "confirm",
100
127
  name: "value",
101
- message: "Do you want to add another stateful prop?",
128
+ message: "Do you want to add another property?",
102
129
  initial: false,
103
130
  })).value;
104
- if (!addAnotherStatefulProp) {
131
+ if (!addAnotherProperty) {
105
132
  break;
106
133
  }
107
134
  }
108
135
  }
109
136
  this.log();
110
- this.log(`${(0, colorette_1.cyanBright)("ℹ")} ${(0, colorette_1.bold)("Event handlers")} represent the events your custom component can fire`);
137
+ 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
138
  this.log();
112
139
  const hasEventHandlers = (await (0, enquirer_1.prompt)({
113
140
  type: "confirm",
114
141
  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)`,
142
+ message: `Does this component have ${(0, colorette_1.bold)("events")}? (e.g. a date picker component that triggers an On Change event)`,
116
143
  initial: false,
117
144
  })).value;
118
- const eventHandlers = [];
145
+ const events = [];
119
146
  if (hasEventHandlers) {
120
147
  for (;;) {
121
148
  const eventHandlerName = (await (0, enquirer_1.prompt)({
122
149
  name: "value",
123
150
  type: "input",
124
- message: "What is the label of the event handler? (e.g. On Change)",
151
+ message: "What is the label of the event? (e.g. On Change)",
125
152
  validate: (response) => !(0, lodash_1.isEmpty)(response.trim()),
126
153
  })).value.trim();
127
154
  const eventHandlerPath = (await (0, enquirer_1.prompt)({
128
155
  name: "value",
129
- message: "What is the machine readable name of the event handler?",
156
+ message: "What is the path of the event? This will be used to trigger the event in your code (e.g. onChange)",
130
157
  type: "input",
131
158
  initial: (0, identifiers_1.suggestIdentifier)(eventHandlerName) || "onChange",
132
159
  validate: (response) => {
133
160
  if (!(0, identifiers_1.isValidIdentifier)(response))
134
161
  return "Invalid identifier";
135
- if (eventHandlers.some((v) => v.path === response))
162
+ if (events.some((v) => v.path === response))
136
163
  return "Duplicate property name";
137
164
  return true;
138
165
  },
139
166
  })).value;
140
- eventHandlers.push({
167
+ events.push({
141
168
  label: eventHandlerName,
142
169
  path: eventHandlerPath,
143
170
  });
144
171
  const addAnotherEventHandler = (await (0, enquirer_1.prompt)({
145
172
  name: "value",
146
173
  type: "confirm",
147
- message: "Do you want to add another event handler?",
174
+ message: "Do you want to add another event?",
148
175
  initial: false,
149
176
  })).value;
150
177
  if (!addAnotherEventHandler) {
@@ -156,16 +183,19 @@ class CreateComponent extends authenticated_command_1.AuthenticatedApplicationCo
156
183
  id: (0, node_crypto_1.randomUUID)(),
157
184
  name,
158
185
  displayName,
159
- statefulPropsRendered: statefulProps
160
- .map((statefulProp) => tsStringify(statefulProp))
186
+ propertiesRendered: properties
187
+ .map((property) => tsStringify(property))
161
188
  .join(""),
162
- eventHandlersRendered: eventHandlers
189
+ eventHandlersRendered: events
163
190
  .map((eventHandler) => tsStringify(eventHandler))
164
191
  .join(""),
165
192
  });
166
- const componentTsx = (0, create_component_defaults_1.getDefaultComponentTsx)(statefulProps, eventHandlers.map((prop) => prop.path));
193
+ const componentTsx = (0, create_component_defaults_1.getDefaultComponentTsx)(properties, events.map((prop) => prop.path));
167
194
  if (isFirstTimeCreate) {
168
195
  await fs.copy(DEFAULT_PACKAGE_JSON_TEMPLATE_PATH, ".");
196
+ // There is a very old npm bug where it won't publish .gitignore files
197
+ // https://github.com/npm/npm/issues/3763
198
+ await fs.move("./gitignore_will_rename", "./.gitignore");
169
199
  }
170
200
  await fs.ensureDir("components/" + name);
171
201
  await fs.writeFile(`components/${name}/config.ts`, configTs);
@@ -173,23 +203,28 @@ class CreateComponent extends authenticated_command_1.AuthenticatedApplicationCo
173
203
  return {
174
204
  name,
175
205
  displayName,
176
- statefulProps,
177
- eventHandlers,
206
+ properties,
207
+ events,
178
208
  };
179
209
  }
180
210
  async initializeComponentByExample() {
181
211
  const response = {
182
- displayName: "Example Component",
183
- name: "ExampleComponent",
184
- statefulProps: [
212
+ displayName: "To-Do List (Example)",
213
+ name: "ToDoList",
214
+ properties: [
185
215
  {
186
- label: "Default Tasks",
187
216
  path: "tasks",
188
- inputType: "js",
189
- placeholder: "{ taskId: { taskName: 'Task Name', taskStatus: 'complete' | 'todo' } }",
217
+ dataType: "any",
218
+ propertiesPanelDisplay: {
219
+ label: "Default Tasks",
220
+ placeholder: "{ taskId: { taskName: 'Task Name', taskStatus: 'complete' | 'todo' } }",
221
+ controlType: "js-expr",
222
+ },
223
+ isExternallyReadable: true,
224
+ isExternallySettable: true,
190
225
  },
191
226
  ],
192
- eventHandlers: [
227
+ events: [
193
228
  {
194
229
  label: "On Task Added",
195
230
  path: "onTaskAdded",
@@ -204,10 +239,10 @@ class CreateComponent extends authenticated_command_1.AuthenticatedApplicationCo
204
239
  id: (0, node_crypto_1.randomUUID)(),
205
240
  name: response.name,
206
241
  displayName: response.displayName,
207
- statefulPropsRendered: response.statefulProps
208
- .map((statefulProp) => tsStringify(statefulProp))
242
+ propertiesRendered: response.properties
243
+ .map((property) => tsStringify(property))
209
244
  .join(""),
210
- eventHandlersRendered: response.eventHandlers
245
+ eventHandlersRendered: response.events
211
246
  .map((eventHandler) => tsStringify(eventHandler))
212
247
  .join(""),
213
248
  });
@@ -218,31 +253,38 @@ class CreateComponent extends authenticated_command_1.AuthenticatedApplicationCo
218
253
  return response;
219
254
  }
220
255
  async run() {
256
+ this.log();
221
257
  const isFirstTimeCreate = !(await fs.exists("package.json"));
222
- const initExampleComponent = isFirstTimeCreate &&
223
- (await (0, enquirer_1.prompt)({
258
+ let response;
259
+ if (isFirstTimeCreate) {
260
+ 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")}`);
261
+ this.log();
262
+ 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. `);
263
+ this.log();
264
+ const initExampleComponent = (await (0, enquirer_1.prompt)({
224
265
  name: "value",
225
266
  type: "confirm",
226
- message: "Would you like to use a pre-generated example custom component?",
267
+ message: "Would you like to generate an example custom component?",
227
268
  initial: false,
228
269
  })).value;
229
- let response;
230
- if (!initExampleComponent) {
231
- response = await this.initializeComponentByWizard(isFirstTimeCreate);
270
+ response = initExampleComponent
271
+ ? await this.initializeComponentByExample()
272
+ : await this.initializeComponentByWizard(isFirstTimeCreate);
232
273
  }
233
274
  else {
234
- response = await this.initializeComponentByExample();
275
+ response = await this.initializeComponentByWizard(isFirstTimeCreate);
235
276
  }
236
277
  this.log((0, colorette_1.green)("Successfully created component! Added the following files:"));
237
278
  this.log();
238
279
  const tree = core_1.ux.tree();
239
280
  tree.insert("components");
240
281
  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");
282
+ tree.nodes.components.nodes[response.name].insert("config.ts # update this file to manage your component's properties and events");
283
+ 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
284
  tree.nodes.components.nodes[response.name].insert("component.tsx # your component's react code");
243
285
  tree.display();
244
286
  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")}`);
287
+ 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
288
  this.log();
247
289
  this.log(`Edit your component's react code here:
248
290
  ${(0, colorette_1.green)(`components/${response.name}/component.tsx`)}`);
@@ -258,7 +300,10 @@ class CreateComponent extends authenticated_command_1.AuthenticatedApplicationCo
258
300
  }
259
301
  core_1.ux.action.stop();
260
302
  }
261
- this.registerComponents();
303
+ const headers = {
304
+ [util_1.COMPONENT_EVENT_HEADER]: util_1.ComponentEvent.CREATE,
305
+ };
306
+ this.registerComponents(headers);
262
307
  }
263
308
  }
264
309
  CreateComponent.description = "Creates a new Superblocks component in the current application folder";
@@ -1,9 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const util_1 = require("@superblocksteam/util");
3
4
  const authenticated_command_1 = require("../../common/authenticated-command");
4
5
  class Register extends authenticated_command_1.AuthenticatedApplicationCommand {
5
6
  async run() {
6
- await this.registerComponents();
7
+ const headers = {
8
+ [util_1.COMPONENT_EVENT_HEADER]: util_1.ComponentEvent.REGISTER,
9
+ };
10
+ await this.registerComponents(headers);
7
11
  }
8
12
  }
9
13
  Register.description = "Registers all local component config files";
@@ -20,7 +20,10 @@ class Upload extends authenticated_command_1.AuthenticatedApplicationCommand {
20
20
  exit: 1,
21
21
  });
22
22
  }
23
- const configs = await this.registerComponents();
23
+ const headers = {
24
+ [util_1.COMPONENT_EVENT_HEADER]: util_1.ComponentEvent.REGISTER,
25
+ };
26
+ const configs = await this.registerComponents(headers);
24
27
  if (!configs)
25
28
  return;
26
29
  // Map component name to component path- each name is a separate entry point
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.healthEndpointMiddleware = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const react_shim_1 = require("@superblocksteam/react-shim");
6
+ const util_1 = require("@superblocksteam/util");
6
7
  const vite_custom_component_reload_plugin_1 = require("@superblocksteam/vite-custom-component-reload-plugin");
7
8
  const plugin_react_1 = tslib_1.__importDefault(require("@vitejs/plugin-react"));
8
9
  const colorette_1 = require("colorette");
@@ -29,10 +30,12 @@ function healthEndpointMiddleware() {
29
30
  exports.healthEndpointMiddleware = healthEndpointMiddleware;
30
31
  class Watch extends authenticated_command_1.AuthenticatedApplicationCommand {
31
32
  async run() {
32
- await this.registerComponents();
33
+ const headers = {
34
+ [util_1.COMPONENT_EVENT_HEADER]: util_1.ComponentEvent.REGISTER,
35
+ };
36
+ await this.registerComponents(headers);
33
37
  this.log((0, colorette_1.yellow)("Remember to refresh your application to see any newly registered components."));
34
38
  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
39
  const port = 3002;
37
40
  const editModeUrl = await this.getEditModeUrl();
38
41
  const viteLogger = (0, vite_1.createLogger)();
@@ -71,7 +74,7 @@ class Watch extends authenticated_command_1.AuthenticatedApplicationCommand {
71
74
  this.log();
72
75
  this.log((0, colorette_1.bold)((0, colorette_1.magenta)(`${editModeUrl}?devMode=true`)));
73
76
  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!"));
77
+ 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
78
  })();
76
79
  }
77
80
  }
@@ -62,7 +62,13 @@ class Initialize extends authenticated_command_1.AuthenticatedCommand {
62
62
  task: async (ctx, task) => {
63
63
  const [resourceId, resourceType] = getResourceIdFromUrl(args.resourceUrl);
64
64
  if (resourceType === "APPLICATION") {
65
- const application = await this.getSdk().fetchApplication(resourceId);
65
+ const headers = {
66
+ [util_1.COMPONENT_EVENT_HEADER]: util_1.ComponentEvent.INIT,
67
+ };
68
+ const application = await this.getSdk().fetchApplication({
69
+ applicationId: resourceId,
70
+ headers,
71
+ });
66
72
  ctx.fetchedResources[application.application.id] = {
67
73
  resourceType,
68
74
  name: application.application.name,
@@ -104,7 +110,14 @@ class Initialize extends authenticated_command_1.AuthenticatedCommand {
104
110
  subtasks.push({
105
111
  title: `Fetching application ${resource.name}...`,
106
112
  task: async (_ctx, task) => {
107
- const application = await this.getSdk().fetchApplication(resourceId, ctx.viewMode);
113
+ const headers = {
114
+ [util_1.COMPONENT_EVENT_HEADER]: util_1.ComponentEvent.INIT,
115
+ };
116
+ const application = await this.getSdk().fetchApplication({
117
+ applicationId: resourceId,
118
+ viewMode: ctx.viewMode,
119
+ headers,
120
+ });
108
121
  task.title += `: fetched`;
109
122
  ctx.writtenResources[resourceId] =
110
123
  await (0, version_control_1.writeResourceToDisk)("APPLICATION", resourceId, application, now, process.cwd());
@@ -265,7 +278,7 @@ function getResourceIdFromUrl(resourceUrl) {
265
278
  return [url.pathname.split("/")[2], "APPLICATION"];
266
279
  }
267
280
  else if (url.pathname.startsWith("/workflows") ||
268
- url.pathname.startsWith("/scheduled-jobs")) {
281
+ url.pathname.startsWith("/scheduled_jobs")) {
269
282
  return [url.pathname.split("/")[2], "BACKEND"];
270
283
  }
271
284
  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;
@@ -51,7 +51,14 @@ class Pull extends authenticated_command_1.AuthenticatedCommand {
51
51
  subtasks.push({
52
52
  title: `Pulling application ${resource.location}...`,
53
53
  task: async (_ctx, task) => {
54
- const application = await this.getSdk().fetchApplication(resourceId, viewMode);
54
+ const headers = {
55
+ [util_1.COMPONENT_EVENT_HEADER]: util_1.ComponentEvent.PULL,
56
+ };
57
+ const application = await this.getSdk().fetchApplication({
58
+ applicationId: resourceId,
59
+ viewMode,
60
+ headers,
61
+ });
55
62
  task.title += `: fetched`;
56
63
  ctx.writtenResources[resourceId] =
57
64
  await (0, version_control_1.writeResourceToDisk)("APPLICATION", resourceId, application, now, superblocksRootPath, resource.location);
@@ -6,10 +6,11 @@ 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;
12
13
  protected getEditModeUrl(): Promise<string>;
13
14
  protected init(): Promise<void>;
14
- protected registerComponents(): Promise<Record<string, any> | undefined>;
15
+ protected registerComponents(injectedHeaders?: Record<string, string>): Promise<Record<string, any> | undefined>;
15
16
  }
@@ -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
- async registerComponents() {
142
+ async registerComponents(injectedHeaders) {
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) {
@@ -85,7 +185,7 @@ class AuthenticatedApplicationCommand extends AuthenticatedCommand {
85
185
  core_1.ux.action.stop();
86
186
  try {
87
187
  core_1.ux.action.start("Registering components...");
88
- await this.getSdk().registerComponents(this.applicationConfig.id, configs);
188
+ await this.getSdk().registerComponents(this.applicationConfig.id, configs, injectedHeaders);
89
189
  core_1.ux.action.stop();
90
190
  }
91
191
  catch (e) {
@@ -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)("Error")}: This version of the CLI is incompatible with any Custom Components you've previously used,
21
+ due to breaking changes introduced in the config.ts format and Custom Components React 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.22",
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.22",
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",