@superblocksteam/cli 1.5.2 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,7 +12,7 @@ $ npm install -g @superblocksteam/cli
12
12
  $ superblocks COMMAND
13
13
  running command...
14
14
  $ superblocks (--version)
15
- @superblocksteam/cli/1.5.2 linux-x64 node-v18.20.2
15
+ @superblocksteam/cli/1.7.0 linux-x64 node-v18.20.2
16
16
  $ superblocks --help [COMMAND]
17
17
  USAGE
18
18
  $ superblocks COMMAND
@@ -147,8 +147,8 @@ USAGE
147
147
  $ superblocks init [RESOURCE_URL] [-m latest-edits|most-recent-commit|deployed]
148
148
 
149
149
  ARGUMENTS
150
- RESOURCE_URL Superblocks resource URL (i.e.
151
- https://app.superblocks.com/applications/<application_id>/pages/<page_id>)
150
+ RESOURCE_URL Superblocks resource URL (i.e. https://app.superblocks.com/applications/<application_id> or
151
+ https://app.superblocks.com/applications/edit/<application_id>)
152
152
 
153
153
  FLAGS
154
154
  -m, --mode=<option> Pull mode
@@ -7,7 +7,7 @@
7
7
  "lint:fix": "npx eslint . --fix"
8
8
  },
9
9
  "dependencies": {
10
- "@superblocksteam/custom-components": "1.5.2",
10
+ "@superblocksteam/custom-components": "1.7.0",
11
11
  "react": "^18",
12
12
  "react-dom": "^18"
13
13
  },
@@ -138,15 +138,17 @@ class Initialize extends authenticated_command_1.AuthenticatedCommand {
138
138
  const headers = {
139
139
  [util_1.COMPONENT_EVENT_HEADER]: util_1.ComponentEvent.INIT,
140
140
  };
141
- const application = await this.getSdk().fetchApplicationWithComponents({
141
+ const application = (await this.getSdk().fetchApplicationWithComponents({
142
142
  applicationId: resourceId,
143
143
  branch: version_control_1.DEFAULT_BRANCH,
144
144
  viewMode: ctx.viewMode,
145
145
  headers,
146
- });
146
+ }));
147
147
  task.title += `: fetched`;
148
- ctx.writtenResources[resourceId] =
149
- await (0, version_control_1.writeResourceToDisk)("APPLICATION", resourceId, application, superblocksRootPath !== null && superblocksRootPath !== void 0 ? superblocksRootPath : process.cwd());
148
+ if (application) {
149
+ ctx.writtenResources[resourceId] =
150
+ await (0, version_control_1.writeMultiPageApplicationToDisk)(application, superblocksRootPath !== null && superblocksRootPath !== void 0 ? superblocksRootPath : process.cwd());
151
+ }
150
152
  task.title += `: done`;
151
153
  },
152
154
  });
@@ -293,7 +295,7 @@ Initialize.flags = {
293
295
  };
294
296
  Initialize.args = {
295
297
  resource_url: core_1.Args.string({
296
- description: "Superblocks resource URL (i.e. https://app.superblocks.com/applications/<application_id>/pages/<page_id>)",
298
+ description: "Superblocks resource URL (i.e. https://app.superblocks.com/applications/<application_id> or https://app.superblocks.com/applications/edit/<application_id>)",
297
299
  required: false,
298
300
  }),
299
301
  };
@@ -301,7 +303,16 @@ exports.default = Initialize;
301
303
  function getResourceIdFromUrl(resourceUrl) {
302
304
  const url = new URL(resourceUrl);
303
305
  if (url.pathname.startsWith("/applications")) {
304
- return [url.pathname.split("/")[2], "APPLICATION"];
306
+ const tokens = url.pathname.split("/");
307
+ if (tokens.length < 2) {
308
+ throw new Error(`Failed to parse resource URL: ${resourceUrl}`);
309
+ }
310
+ if (tokens[2] === "edit") {
311
+ return [tokens[3], "APPLICATION"];
312
+ }
313
+ else {
314
+ return [tokens[2], "APPLICATION"];
315
+ }
305
316
  }
306
317
  else if (url.pathname.startsWith("/workflows") ||
307
318
  url.pathname.startsWith("/scheduled_jobs")) {
@@ -39,7 +39,7 @@ class Login extends core_1.Command {
39
39
  this.log((0, colorette_1.green)(`Welcome to the Superblocks 🐨 CLI ${user.user.name}!`));
40
40
  }
41
41
  catch (error) {
42
- if (error.name === util_1.ERROR_FILE_ACCESS) {
42
+ if (error instanceof util_1.FileAccessError) {
43
43
  this.log((0, colorette_1.red)("Could not save token, ensure the Superblocks CLI has access to create folders in your home directory."));
44
44
  return;
45
45
  }
@@ -5,5 +5,7 @@ export default class Migrate extends AuthenticatedCommand {
5
5
  run(): Promise<void>;
6
6
  private createTasks;
7
7
  private getResourceIdsToMigrate;
8
+ private migrateSinglePageApplicationToMultiPage;
9
+ private migrateCustomComponentVersion;
8
10
  private migrateApplication;
9
11
  }
@@ -119,11 +119,39 @@ class Migrate extends authenticated_command_1.AuthenticatedCommand {
119
119
  }
120
120
  return resourceIdsToMigrate;
121
121
  }
122
- async migrateApplication(applicationResource, superblocksRootPath) {
122
+ async migrateSinglePageApplicationToMultiPage(applicationResource, superblocksRootPath) {
123
+ const fileStructure = await (0, version_control_1.getFileStructureType)(superblocksRootPath, applicationResource.location);
124
+ if (fileStructure == version_control_1.FileStructureType.SINGLE_PAGE &&
125
+ (await this.getSdk().fetchCurrentUser()).flagBootstrap["server.multipage.enabled"]) {
126
+ this.log(`Migrating single page application at ${applicationResource.location} to multi-page application...`);
127
+ const singlePageApplication = await (0, version_control_1.readApplicationFromDisk)(superblocksRootPath, applicationResource.location);
128
+ const multiPageApplication = {
129
+ application: {
130
+ id: singlePageApplication.application.id,
131
+ name: singlePageApplication.application.name,
132
+ organizationId: singlePageApplication.application.organizationId,
133
+ settings: singlePageApplication.application.settings,
134
+ configuration: {},
135
+ },
136
+ apis: [],
137
+ pages: [
138
+ {
139
+ ...singlePageApplication.page,
140
+ apis: singlePageApplication.apis,
141
+ },
142
+ ],
143
+ componentFiles: [],
144
+ };
145
+ await (0, version_control_1.writeMultiPageApplicationToDisk)(multiPageApplication, superblocksRootPath, applicationResource.location, true);
146
+ this.log(`Successfully migrated single page application at ${applicationResource.location} to multi-page application.`);
147
+ }
148
+ }
149
+ async migrateCustomComponentVersion(applicationResource, superblocksRootPath) {
123
150
  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"));
151
+ const packageJsonPath = node_path_1.default.join(superblocksRootPath, applicationResource.location, "package.json");
152
+ if (await fs.pathExists(packageJsonPath)) {
153
+ this.log("Checking CLI version compatibility...");
154
+ const packageJson = await fs.readJson(packageJsonPath);
127
155
  const versionStr = (_a = packageJson.dependencies) === null || _a === void 0 ? void 0 : _a["@superblocksteam/custom-components"];
128
156
  if (!semver_1.default.satisfies(this.config.version, versionStr)) {
129
157
  this.log("Migrating application dependencies...");
@@ -135,6 +163,15 @@ class Migrate extends authenticated_command_1.AuthenticatedCommand {
135
163
  this.log("CLI version matches package.json version");
136
164
  }
137
165
  }
166
+ else {
167
+ this.log(`No package.json found in ${applicationResource.location}. Skipping migration of dependencies.`);
168
+ }
169
+ }
170
+ async migrateApplication(applicationResource, superblocksRootPath) {
171
+ try {
172
+ await this.migrateCustomComponentVersion(applicationResource, superblocksRootPath);
173
+ await this.migrateSinglePageApplicationToMultiPage(applicationResource, superblocksRootPath);
174
+ }
138
175
  catch (e) {
139
176
  this.error(e.message, {
140
177
  exit: 1,
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  const node_path_1 = tslib_1.__importDefault(require("node:path"));
5
5
  const core_1 = require("@oclif/core");
6
+ const sdk_1 = require("@superblocksteam/sdk");
6
7
  const util_1 = require("@superblocksteam/util");
7
- const fs = tslib_1.__importStar(require("fs-extra"));
8
8
  const listr2_1 = require("listr2");
9
9
  const authenticated_command_1 = require("../common/authenticated-command");
10
10
  const version_control_1 = require("../common/version-control");
@@ -28,6 +28,7 @@ class Pull extends authenticated_command_1.AuthenticatedCommand {
28
28
  ] = await (0, util_1.getSuperblocksMonorepoConfigJson)(true);
29
29
  ctx.existingSuperblocksResourceConfig =
30
30
  await (0, util_1.getSuperblocksResourceConfigIfExists)();
31
+ ctx.superblocksRootPath = node_path_1.default.resolve(node_path_1.default.dirname(ctx.superblocksRootConfigPath), "..");
31
32
  }
32
33
  catch {
33
34
  // no existing superblocks config
@@ -51,7 +52,7 @@ class Pull extends authenticated_command_1.AuthenticatedCommand {
51
52
  {
52
53
  title: "Checking for deleted Superblocks resources...",
53
54
  task: async (ctx, task) => {
54
- var _a, _b, _c;
55
+ var _a, _b;
55
56
  try {
56
57
  for (const [resourceId, resource] of Object.entries((_b = (_a = ctx.existingSuperblocksRootConfig) === null || _a === void 0 ? void 0 : _a.resources) !== null && _b !== void 0 ? _b : {})) {
57
58
  switch (resource === null || resource === void 0 ? void 0 : resource.resourceType) {
@@ -109,10 +110,7 @@ Would you like to also delete these resources from your filesystem?`,
109
110
  },
110
111
  ]);
111
112
  if (removeResourcesFromDisk) {
112
- for (const resourceId of ctx.removedResourceIds) {
113
- const resource = (_c = ctx.existingSuperblocksRootConfig) === null || _c === void 0 ? void 0 : _c.resources[resourceId];
114
- await (0, version_control_1.removeResourceFromDisk)(resource.location);
115
- }
113
+ await (0, version_control_1.deleteResourcesAndUpdateRootConfig)(ctx.removedResourceIds, ctx.existingSuperblocksRootConfig, ctx.superblocksRootPath, ctx.superblocksRootConfigPath);
116
114
  }
117
115
  }
118
116
  catch (e) {
@@ -131,14 +129,26 @@ Would you like to also delete these resources from your filesystem?`,
131
129
  var _a, _b;
132
130
  task.title = `Validating git configuration...`;
133
131
  const subtasks = [];
132
+ ctx.resourceIdsToSkip = new Set();
134
133
  for (const resourceId of ctx.resourceIdsToPull) {
135
134
  const resource = (_a = ctx.existingSuperblocksRootConfig) === null || _a === void 0 ? void 0 : _a.resources[resourceId];
136
135
  const resourceTitle = `${((_b = resource.resourceType) !== null && _b !== void 0 ? _b : "").toLowerCase()} ${resourceId}`;
137
136
  subtasks.push({
138
137
  title: `Checking ${resourceTitle}...`,
139
138
  task: async () => {
140
- const { branchName } = await this.validateGitSetup(resource === null || resource === void 0 ? void 0 : resource.resourceType, resourceId, util_1.ComponentEvent.PULL, ctx.localBranchName);
141
- ctx.branchToPullFrom.set(resourceId, branchName);
139
+ try {
140
+ const { branchName } = await this.validateGitSetup(resource === null || resource === void 0 ? void 0 : resource.resourceType, resourceId, util_1.ComponentEvent.PULL, ctx.localBranchName);
141
+ ctx.branchToPullFrom.set(resourceId, branchName);
142
+ }
143
+ catch (error) {
144
+ if ((0, version_control_1.isCI)() && error instanceof sdk_1.ValidateGitSetupError) {
145
+ this.log(`WARN: Failed to validate git setup for ${resourceTitle}. Skipping pull.\n\n${error.message}.`);
146
+ }
147
+ else {
148
+ throw error;
149
+ }
150
+ ctx.resourceIdsToSkip.add(resourceId);
151
+ }
142
152
  },
143
153
  });
144
154
  }
@@ -152,8 +162,9 @@ Would you like to also delete these resources from your filesystem?`,
152
162
  var _a, _b;
153
163
  task.title = `Pulling resources from branch ${ctx.localBranchName}...`;
154
164
  const viewMode = await (0, version_control_1.getMode)(task, mode);
165
+ // Remove resources to skip from list of resources to push
166
+ ctx.resourceIdsToPull = ctx.resourceIdsToPull.filter((id) => !ctx.resourceIdsToSkip.has(id));
155
167
  const subtasks = [];
156
- const superblocksRootPath = node_path_1.default.resolve(node_path_1.default.dirname(ctx.superblocksRootConfigPath), "..");
157
168
  for (const resourceId of ctx.resourceIdsToPull) {
158
169
  const resource = (_a = ctx.existingSuperblocksRootConfig) === null || _a === void 0 ? void 0 : _a.resources[resourceId];
159
170
  const branchName = (_b = ctx.branchToPullFrom.get(resourceId)) !== null && _b !== void 0 ? _b : ctx.localBranchName;
@@ -166,15 +177,39 @@ Would you like to also delete these resources from your filesystem?`,
166
177
  [util_1.COMPONENT_EVENT_HEADER]: util_1.ComponentEvent.PULL,
167
178
  };
168
179
  try {
169
- const application = await this.getSdk().fetchApplicationWithComponents({
170
- applicationId: resourceId,
171
- branch: branchName,
172
- viewMode,
173
- headers,
174
- });
175
180
  task.title += `: fetched`;
176
- ctx.writtenResources[resourceId] =
177
- await (0, version_control_1.writeResourceToDisk)("APPLICATION", resourceId, application, superblocksRootPath, resource.location);
181
+ const fileStructureType = await (0, version_control_1.getFileStructureType)(ctx.superblocksRootPath, resource.location);
182
+ if (fileStructureType === version_control_1.FileStructureType.SINGLE_PAGE) {
183
+ const multiPageEnabled = (await this.getSdk().fetchCurrentUser()).flagBootstrap["server.multipage.enabled"];
184
+ if (multiPageEnabled) {
185
+ this.error(`Application files at ${resource.location} are in single page format, but the multi-page feature is enabled for your account. Please run superblocks migrate to convert your application to multi-page format and commit the changes before pulling.`);
186
+ }
187
+ const application = await this.getSdk().fetchApplicationWithComponents({
188
+ applicationId: resourceId,
189
+ branch: branchName,
190
+ viewMode,
191
+ headers,
192
+ fetchSinglePageApplication: true,
193
+ });
194
+ if (!application) {
195
+ return;
196
+ }
197
+ ctx.writtenResources[resourceId] =
198
+ await (0, version_control_1.writeResourceToDisk)("APPLICATION", resourceId, application, ctx.superblocksRootPath, resource.location);
199
+ }
200
+ else {
201
+ const application = (await this.getSdk().fetchApplicationWithComponents({
202
+ applicationId: resourceId,
203
+ branch: branchName,
204
+ viewMode,
205
+ headers,
206
+ }));
207
+ if (!application) {
208
+ return;
209
+ }
210
+ ctx.writtenResources[resourceId] =
211
+ await (0, version_control_1.writeMultiPageApplicationToDisk)(application, ctx.superblocksRootPath, resource.location, false);
212
+ }
178
213
  task.title += `: done`;
179
214
  }
180
215
  catch (e) {
@@ -198,7 +233,7 @@ Would you like to also delete these resources from your filesystem?`,
198
233
  const backend = await this.getSdk().fetchApi(resourceId, viewMode, branchName);
199
234
  task.title += `: fetched`;
200
235
  ctx.writtenResources[resourceId] =
201
- await (0, version_control_1.writeResourceToDisk)("BACKEND", resourceId, backend, superblocksRootPath, resource.location);
236
+ await (0, version_control_1.writeResourceToDisk)("BACKEND", resourceId, backend, ctx.superblocksRootPath, resource.location);
202
237
  task.title += `: done`;
203
238
  },
204
239
  });
@@ -215,17 +250,6 @@ Would you like to also delete these resources from your filesystem?`,
215
250
  });
216
251
  },
217
252
  },
218
- {
219
- title: "Updating Superblocks project file...",
220
- task: async (ctx) => {
221
- const [superblocksRootConfig, rootConfigPath] = await (0, util_1.getSuperblocksMonorepoConfigJson)(true);
222
- for (const removedResourceId of ctx.removedResourceIds) {
223
- delete superblocksRootConfig.resources[removedResourceId];
224
- }
225
- // update superblocks.json file
226
- await fs.writeFile(rootConfigPath, JSON.stringify((0, version_control_1.sortByKey)(superblocksRootConfig), null, 2));
227
- },
228
- },
229
253
  ], {
230
254
  concurrent: false,
231
255
  });
@@ -29,6 +29,7 @@ class Push extends authenticated_command_1.AuthenticatedCommand {
29
29
  ] = await (0, util_1.getSuperblocksMonorepoConfigJson)(true);
30
30
  ctx.existingSuperblocksResourceConfig =
31
31
  await (0, util_1.getSuperblocksResourceConfigIfExists)();
32
+ ctx.superblocksRootPath = node_path_1.default.resolve(node_path_1.default.dirname(ctx.superblocksRootConfigPath), "..");
32
33
  }
33
34
  catch {
34
35
  // no existing superblocks config
@@ -57,7 +58,7 @@ class Push extends authenticated_command_1.AuthenticatedCommand {
57
58
  {
58
59
  title: "Checking for deleted Superblocks resources...",
59
60
  task: async (ctx, task) => {
60
- var _a, _b, _c;
61
+ var _a, _b;
61
62
  try {
62
63
  for (const [resourceId, resource] of Object.entries((_b = (_a = ctx.existingSuperblocksRootConfig) === null || _a === void 0 ? void 0 : _a.resources) !== null && _b !== void 0 ? _b : {})) {
63
64
  switch (resource === null || resource === void 0 ? void 0 : resource.resourceType) {
@@ -115,10 +116,7 @@ Would you like to also delete these resources from your filesystem?`,
115
116
  },
116
117
  ]);
117
118
  if (removeResourcesFromDisk) {
118
- for (const resourceId of ctx.removedResourceIds) {
119
- const resource = (_c = ctx.existingSuperblocksRootConfig) === null || _c === void 0 ? void 0 : _c.resources[resourceId];
120
- await (0, version_control_1.removeResourceFromDisk)(resource.location);
121
- }
119
+ await (0, version_control_1.deleteResourcesAndUpdateRootConfig)(ctx.removedResourceIds, ctx.existingSuperblocksRootConfig, ctx.superblocksRootPath, ctx.superblocksRootConfigPath);
122
120
  }
123
121
  }
124
122
  catch (e) {
@@ -137,6 +135,7 @@ Would you like to also delete these resources from your filesystem?`,
137
135
  var _a, _b;
138
136
  task.title = `Validating git configuration...`;
139
137
  const subtasks = [];
138
+ ctx.resourceIdsToSkip = new Set();
140
139
  for (const resourceId of ctx.resourceIdsToPush) {
141
140
  const resource = (_a = ctx.existingSuperblocksRootConfig) === null || _a === void 0 ? void 0 : _a.resources[resourceId];
142
141
  // for user messages:
@@ -144,8 +143,43 @@ Would you like to also delete these resources from your filesystem?`,
144
143
  subtasks.push({
145
144
  title: `Checking ${resourceTitle}...`,
146
145
  task: async () => {
147
- const { branchName } = await this.validateGitSetup(resource === null || resource === void 0 ? void 0 : resource.resourceType, resourceId, util_1.ComponentEvent.PUSH, ctx.localBranchName);
148
- ctx.branchToPushTo.set(resourceId, branchName);
146
+ try {
147
+ const { branchName } = await this.validateGitSetup(resource === null || resource === void 0 ? void 0 : resource.resourceType, resourceId, util_1.ComponentEvent.PUSH, ctx.localBranchName);
148
+ ctx.branchToPushTo.set(resourceId, branchName);
149
+ }
150
+ catch (error) {
151
+ if ((0, version_control_1.isCI)() && error instanceof sdk_1.ValidateGitSetupError) {
152
+ this.log(`WARN: Failed to validate git setup for ${resourceTitle}. Skipping push.\n\n${error.message}.`);
153
+ }
154
+ else {
155
+ throw error;
156
+ }
157
+ ctx.resourceIdsToSkip.add(resourceId);
158
+ }
159
+ },
160
+ });
161
+ }
162
+ return task.newListr(subtasks, {
163
+ concurrent: true,
164
+ });
165
+ },
166
+ },
167
+ {
168
+ task: async (ctx, task) => {
169
+ var _a, _b;
170
+ task.title = `Validating project structure...`;
171
+ const subtasks = [];
172
+ const superblocksRootPath = node_path_1.default.resolve(node_path_1.default.dirname(ctx.superblocksRootConfigPath), "..");
173
+ for (const resourceId of ctx.resourceIdsToPush) {
174
+ const resource = (_a = ctx.existingSuperblocksRootConfig) === null || _a === void 0 ? void 0 : _a.resources[resourceId];
175
+ const resourceTitle = `${((_b = resource.resourceType) !== null && _b !== void 0 ? _b : "").toLowerCase()} at ${resource.location}`;
176
+ subtasks.push({
177
+ title: `Validating ${resourceTitle}...`,
178
+ task: async () => {
179
+ const validationError = await (0, version_control_1.validateLocalResource)(superblocksRootPath, resource);
180
+ if (validationError) {
181
+ this.error(`Push failed for resource '${resource.location}' (id: ${resourceId}). ${validationError}`);
182
+ }
149
183
  },
150
184
  });
151
185
  }
@@ -158,6 +192,8 @@ Would you like to also delete these resources from your filesystem?`,
158
192
  task: async (ctx, task) => {
159
193
  var _a;
160
194
  task.title = `Pushing resources to branch ${ctx.localBranchName}...`;
195
+ // Remove resources to skip from list of resources to push
196
+ ctx.resourceIdsToPush = ctx.resourceIdsToPush.filter((id) => !ctx.resourceIdsToSkip.has(id));
161
197
  const subtasks = [];
162
198
  const superblocksRootPath = node_path_1.default.resolve(node_path_1.default.dirname(ctx.superblocksRootConfigPath), "..");
163
199
  for (const resourceId of ctx.resourceIdsToPush) {
@@ -168,17 +204,31 @@ Would you like to also delete these resources from your filesystem?`,
168
204
  title: `Pushing application ${resource.location}...`,
169
205
  task: async (_ctx, task) => {
170
206
  var _a;
171
- const applicationConfig = {
172
- ...(await (0, version_control_1.readApplicationFromDisk)(superblocksRootPath, resource.location)),
173
- commitId: ctx.headCommitId,
174
- commitMessage: ctx.headCommitMessage,
175
- };
207
+ const fileStructureType = await (0, version_control_1.getFileStructureType)(superblocksRootPath, resource.location);
208
+ if (fileStructureType === version_control_1.FileStructureType.SINGLE_PAGE &&
209
+ (await this.getSdk().fetchCurrentUser()).flagBootstrap["server.multipage.enabled"]) {
210
+ this.error(`Application files at ${resource.location} are in single page format, but the multi-page feature is enabled for your account. Please run \`superblocks migrate\` to convert your application to multi-page format and commit the changes before pushing.`);
211
+ }
212
+ const applicationConfig = fileStructureType === version_control_1.FileStructureType.SINGLE_PAGE
213
+ ? {
214
+ ...(await (0, version_control_1.readApplicationFromDisk)(superblocksRootPath, resource.location)),
215
+ commitId: ctx.headCommitId,
216
+ commitMessage: ctx.headCommitMessage,
217
+ }
218
+ : {
219
+ ...(await (0, version_control_1.readMultiPageApplicationFromDisk)(superblocksRootPath, resource.location)),
220
+ commitId: ctx.headCommitId,
221
+ commitMessage: ctx.headCommitMessage,
222
+ };
176
223
  task.title += `: read from disk`;
177
224
  try {
225
+ const branch = (_a = ctx.branchToPushTo.get(resourceId)) !== null && _a !== void 0 ? _a : ctx.localBranchName;
226
+ task.title += `: going to push commit ${applicationConfig.commitId} to branch ${branch}`;
178
227
  await this.getSdk().pushApplication({
179
228
  applicationId: resourceId,
180
229
  applicationConfig,
181
- branch: (_a = ctx.branchToPushTo.get(resourceId)) !== null && _a !== void 0 ? _a : ctx.localBranchName,
230
+ branch,
231
+ multiPage: fileStructureType === version_control_1.FileStructureType.MULTI_PAGE,
182
232
  });
183
233
  }
184
234
  catch (error) {
@@ -210,14 +260,16 @@ Would you like to also delete these resources from your filesystem?`,
210
260
  };
211
261
  task.title += `: read from disk`;
212
262
  try {
263
+ const branch = (_a = ctx.branchToPushTo.get(resourceId)) !== null && _a !== void 0 ? _a : ctx.localBranchName;
264
+ task.title += `: going to push commit ${apiConfig.commitId} to branch ${branch}`;
213
265
  await this.getSdk().pushApi({
214
266
  apiId: resourceId,
215
267
  apiConfig,
216
- branch: (_a = ctx.branchToPushTo.get(resourceId)) !== null && _a !== void 0 ? _a : ctx.localBranchName,
268
+ branch,
217
269
  });
218
270
  }
219
271
  catch (error) {
220
- if ((error === null || error === void 0 ? void 0 : error.name) === "BranchNotCheckedOutError") {
272
+ if (error instanceof sdk_1.BranchNotCheckedOutError) {
221
273
  this.log(`WARN: Workflow/Scheduled Job ${(0, version_control_1.extractApiName)(apiConfig)} failed to push, please check branch out in the Superblocks UI first in order to check this branch out for this resource.`);
222
274
  }
223
275
  else if (error instanceof sdk_1.CommitAlreadyExistsError) {
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
+ const path_1 = tslib_1.__importDefault(require("path"));
4
5
  const core_1 = require("@oclif/core");
5
6
  const util_1 = require("@superblocksteam/util");
6
7
  const fs = tslib_1.__importStar(require("fs-extra"));
@@ -27,7 +28,11 @@ class Remove extends authenticated_command_1.AuthenticatedCommand {
27
28
  ctx.fetchedResources = {};
28
29
  ctx.removedResourceIds = [];
29
30
  try {
30
- ctx.existingSuperblocksConfig = (await (0, util_1.getSuperblocksMonorepoConfigJson)(true))[0];
31
+ [
32
+ ctx.existingSuperblocksRootConfig,
33
+ ctx.superblocksRootConfigPath,
34
+ ] = await (0, util_1.getSuperblocksMonorepoConfigJson)(true);
35
+ ctx.superblocksRootPath = path_1.default.resolve(path_1.default.dirname(ctx.superblocksRootConfigPath), "..");
31
36
  }
32
37
  catch {
33
38
  // no existing superblocks config
@@ -83,14 +88,14 @@ class Remove extends authenticated_command_1.AuthenticatedCommand {
83
88
  var _a;
84
89
  const subtasks = [];
85
90
  for (const resourceId of ctx.resourceIdsToRemove) {
86
- const resourceLocation = (_a = ctx.existingSuperblocksConfig) === null || _a === void 0 ? void 0 : _a.resources[resourceId].location;
91
+ const resourceLocation = (_a = ctx.existingSuperblocksRootConfig) === null || _a === void 0 ? void 0 : _a.resources[resourceId].location;
87
92
  if (!resourceLocation) {
88
93
  this.error(`Resource location not found for resource with id ${resourceId}`);
89
94
  }
90
95
  subtasks.push({
91
96
  title: `Removing ${resourceId} from ${resourceLocation}...`,
92
97
  task: async (_ctx, task) => {
93
- await (0, version_control_1.removeResourceFromDisk)(resourceLocation);
98
+ await (0, version_control_1.removeResourceFromDisk)(ctx.superblocksRootPath, resourceLocation);
94
99
  ctx.removedResourceIds.push(resourceId);
95
100
  task.title += `: done`;
96
101
  },
@@ -126,7 +131,7 @@ class Remove extends authenticated_command_1.AuthenticatedCommand {
126
131
  else {
127
132
  const choices = [];
128
133
  for (const [resourceId, resource] of Object.entries(ctx.fetchedResources)) {
129
- if ((_a = ctx.existingSuperblocksConfig) === null || _a === void 0 ? void 0 : _a.resources[resourceId]) {
134
+ if ((_a = ctx.existingSuperblocksRootConfig) === null || _a === void 0 ? void 0 : _a.resources[resourceId]) {
130
135
  choices.push({
131
136
  name: resourceId,
132
137
  message: `${resource.name} (${resource.resourceType})`,
@@ -191,7 +196,7 @@ Remove.args = {
191
196
  exports.default = Remove;
192
197
  function getResourceIdFromLocation(ctx, resourceLocation) {
193
198
  var _a, _b;
194
- for (const [resourceId, resource] of Object.entries((_b = (_a = ctx.existingSuperblocksConfig) === null || _a === void 0 ? void 0 : _a.resources) !== null && _b !== void 0 ? _b : {})) {
199
+ for (const [resourceId, resource] of Object.entries((_b = (_a = ctx.existingSuperblocksRootConfig) === null || _a === void 0 ? void 0 : _a.resources) !== null && _b !== void 0 ? _b : {})) {
195
200
  if (resource.location === resourceLocation) {
196
201
  return [resourceId, resource.resourceType];
197
202
  }
@@ -121,7 +121,7 @@ class AuthenticatedApplicationCommand extends AuthenticatedCommand {
121
121
  async getEditModeUrl() {
122
122
  const result = await (0, util_1.getLocalTokenWithUrlIfExists)();
123
123
  const baseUrl = typeof result === "string" ? result : result === null || result === void 0 ? void 0 : result.superblocksBaseUrl;
124
- return new URL(`/applications/${this.applicationConfig.id}/pages/${this.applicationConfig.defaultPageId}/edit`, baseUrl).toString();
124
+ return new URL(`/applications/edit/${this.applicationConfig.id}`, baseUrl).toString();
125
125
  }
126
126
  async init() {
127
127
  var _a, _b;
@@ -1,20 +1,29 @@
1
- import { ApiWrapper, ApplicationWrapper, ViewMode } from "@superblocksteam/sdk";
2
- import { LocalGitRepoState } from "@superblocksteam/util";
1
+ import { ApiWrapper, ApplicationWrapper, MultiPageApplicationWrapper, ViewMode } from "@superblocksteam/sdk";
2
+ import { LocalGitRepoState, SuperblocksMonorepoConfig } from "@superblocksteam/util";
3
3
  import { VersionedResourceConfig } from "@superblocksteam/util";
4
4
  export declare const LATEST_EDITS_MODE = "latest-edits";
5
5
  export declare const MOST_RECENT_COMMIT_MODE = "most-recent-commit";
6
6
  export declare const DEPLOYED_MODE = "deployed";
7
7
  export declare const DEFAULT_BRANCH = "main";
8
8
  export declare const modeFlagValuesMap: Record<string, string>;
9
+ export declare enum FileStructureType {
10
+ SINGLE_PAGE = "single-page",
11
+ MULTI_PAGE = "multi-page"
12
+ }
9
13
  export declare function modeFlagToViewMode(modeFlag: ModeFlag): ViewMode;
10
14
  export type ModeFlag = (keyof typeof modeFlagValuesMap)[number];
11
15
  export declare const SELECT_PROMPT_HELP = "Use \u2191/\u2193 arrow keys, Enter to confirm";
12
16
  export declare const MULTI_SELECT_PROMPT_HELP = "Type to filter, Use \u2191/\u2193 arrow keys, Space to select, Enter to confirm";
13
17
  export declare const atLeastOneSelection: (value: string[]) => string | true;
18
+ export declare function getFileStructureType(rootPath: string, existingRelativeLocation: string): Promise<FileStructureType>;
14
19
  export declare function readApplicationFromDisk(rootPath: string, existingRelativeLocation: string): Promise<ApplicationWrapper>;
20
+ export declare function readMultiPageApplicationFromDisk(rootPath: string, existingRelativeLocation: string): Promise<MultiPageApplicationWrapper>;
15
21
  export declare function readApiFromDisk(rootPath: string, existingRelativeLocation: string): Promise<ApiWrapper>;
16
22
  export declare function writeResourceToDisk(resourceType: string, resourceId: string, resource: any, rootPath: string, existingRelativeLocation?: string): Promise<VersionedResourceConfig>;
17
- export declare function removeResourceFromDisk(resourceLocation: string): Promise<void>;
23
+ export declare function writeMultiPageApplicationToDisk(resource: MultiPageApplicationWrapper & {
24
+ componentFiles: any;
25
+ }, rootPath: string, existingRelativeLocation?: string, migrateFromSinglePage?: boolean): Promise<VersionedResourceConfig>;
26
+ export declare function removeResourceFromDisk(rootPath: string, resourceRelativeLocation: string): Promise<void>;
18
27
  export declare function getMode(task: any, mode: ModeFlag): Promise<ViewMode>;
19
28
  export declare function sortByKey(obj: unknown): unknown;
20
29
  export declare function getLocalGitRepoState(overrideLocalBranch?: string): Promise<LocalGitRepoState>;
@@ -27,5 +36,8 @@ export declare function getCurrentGitBranchIfGit(): Promise<string | null>;
27
36
  */
28
37
  export declare function getCurrentGitBranch(): Promise<string>;
29
38
  export declare function getHeadCommit(branch: string): Promise<[string, string]>;
39
+ export declare function isCI(): boolean;
30
40
  export declare function isGitRepoDirty(): Promise<boolean>;
31
41
  export declare function extractApiName(api: ApiWrapper): string;
42
+ export declare function validateLocalResource(superblocksRootPath: string, resource: VersionedResourceConfig): Promise<string | undefined>;
43
+ export declare function deleteResourcesAndUpdateRootConfig(removedResourceIds: string[], existingSuperblocksRootConfig: SuperblocksMonorepoConfig, superblocksRootPath: string, superblocksRootConfigPath: string): Promise<void>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.extractApiName = exports.isGitRepoDirty = exports.getHeadCommit = exports.getCurrentGitBranch = exports.getCurrentGitBranchIfGit = exports.getLocalGitRepoState = exports.sortByKey = exports.getMode = exports.removeResourceFromDisk = exports.writeResourceToDisk = exports.readApiFromDisk = exports.readApplicationFromDisk = exports.atLeastOneSelection = exports.MULTI_SELECT_PROMPT_HELP = exports.SELECT_PROMPT_HELP = exports.modeFlagToViewMode = exports.modeFlagValuesMap = exports.DEFAULT_BRANCH = exports.DEPLOYED_MODE = exports.MOST_RECENT_COMMIT_MODE = exports.LATEST_EDITS_MODE = void 0;
3
+ exports.deleteResourcesAndUpdateRootConfig = exports.validateLocalResource = exports.extractApiName = exports.isGitRepoDirty = exports.isCI = exports.getHeadCommit = exports.getCurrentGitBranch = exports.getCurrentGitBranchIfGit = exports.getLocalGitRepoState = exports.sortByKey = exports.getMode = exports.removeResourceFromDisk = exports.writeMultiPageApplicationToDisk = exports.writeResourceToDisk = exports.readApiFromDisk = exports.readMultiPageApplicationFromDisk = exports.readApplicationFromDisk = exports.getFileStructureType = exports.atLeastOneSelection = exports.MULTI_SELECT_PROMPT_HELP = exports.SELECT_PROMPT_HELP = exports.modeFlagToViewMode = exports.FileStructureType = exports.modeFlagValuesMap = exports.DEFAULT_BRANCH = exports.DEPLOYED_MODE = exports.MOST_RECENT_COMMIT_MODE = exports.LATEST_EDITS_MODE = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const https = tslib_1.__importStar(require("https"));
6
6
  const node_path_1 = tslib_1.__importDefault(require("node:path"));
@@ -23,6 +23,11 @@ exports.modeFlagValuesMap = {
23
23
  [exports.MOST_RECENT_COMMIT_MODE]: "Most recent commit",
24
24
  [exports.DEPLOYED_MODE]: "Deployed",
25
25
  };
26
+ var FileStructureType;
27
+ (function (FileStructureType) {
28
+ FileStructureType["SINGLE_PAGE"] = "single-page";
29
+ FileStructureType["MULTI_PAGE"] = "multi-page";
30
+ })(FileStructureType || (exports.FileStructureType = FileStructureType = {}));
26
31
  function modeFlagToViewMode(modeFlag) {
27
32
  switch (modeFlag) {
28
33
  case exports.LATEST_EDITS_MODE:
@@ -93,8 +98,20 @@ async function downloadFile(rootDirectory, filepath, url) {
93
98
  async function readYamlFile(path) {
94
99
  return (0, yaml_1.parse)(await fs.readFile(path, "utf8"));
95
100
  }
101
+ function getFileStructureTypeFromResourceConfig(superblocksConfig) {
102
+ if (superblocksConfig.pages) {
103
+ return FileStructureType.MULTI_PAGE;
104
+ }
105
+ return FileStructureType.SINGLE_PAGE;
106
+ }
107
+ async function getFileStructureType(rootPath, existingRelativeLocation) {
108
+ const superblocksConfig = await (0, util_1.getSuperblocksApplicationConfigJson)(`${rootPath}/${existingRelativeLocation}`);
109
+ return getFileStructureTypeFromResourceConfig(superblocksConfig);
110
+ }
111
+ exports.getFileStructureType = getFileStructureType;
96
112
  // NOTE: If a change is made to how applications are read from disk, please update
97
113
  // logic to write applications to disk in the "writeResourceToDisk" function accordingly.
114
+ // @deprecated this can be removed once all customers move to multi page applications
98
115
  async function readApplicationFromDisk(rootPath, existingRelativeLocation) {
99
116
  const application = await readYamlFile(`${rootPath}/${existingRelativeLocation}/application.yaml`);
100
117
  const page = await readYamlFile(`${rootPath}/${existingRelativeLocation}/page.yaml`);
@@ -118,6 +135,59 @@ async function readApplicationFromDisk(rootPath, existingRelativeLocation) {
118
135
  };
119
136
  }
120
137
  exports.readApplicationFromDisk = readApplicationFromDisk;
138
+ // NOTE: If a change is made to how applications are read from disk, please update
139
+ // logic to write applications to disk in the "writeResourceToDisk" function accordingly.
140
+ async function readMultiPageApplicationFromDisk(rootPath, existingRelativeLocation) {
141
+ var _a;
142
+ const superblocksApplicationConfig = await (0, util_1.getSuperblocksApplicationConfigJson)(`${rootPath}/${existingRelativeLocation}`);
143
+ const application = await readYamlFile(`${rootPath}/${existingRelativeLocation}/application.yaml`);
144
+ const pagesDirName = `${rootPath}/${existingRelativeLocation}/pages`;
145
+ const pages = [];
146
+ if (await fs.pathExists(pagesDirName)) {
147
+ for (const page of Object.values((_a = superblocksApplicationConfig.pages) !== null && _a !== void 0 ? _a : {})) {
148
+ const pageContent = await readYamlFile(`${pagesDirName}/${slugifyName(page.name)}/page.yaml`);
149
+ const pageApisDirName = `${pagesDirName}/${slugifyName(page.name)}/apis`;
150
+ const apis = [];
151
+ if (await fs.pathExists(pageApisDirName)) {
152
+ for (const apiName of Object.values(page.apis)) {
153
+ const apiContent = await readYamlFile(`${pageApisDirName}/${slugifyName(apiName)}.yaml`);
154
+ // This mimics the shape of the ApiV3Dto object
155
+ apis.push({
156
+ id: apiContent.metadata.id,
157
+ pageId: page.id,
158
+ apiPb: apiContent,
159
+ });
160
+ }
161
+ }
162
+ pages.push({
163
+ id: pageContent.id,
164
+ name: pageContent.name,
165
+ applicationId: pageContent.applicationId,
166
+ isHidden: pageContent.isHidden,
167
+ layouts: pageContent.layouts,
168
+ apis,
169
+ });
170
+ }
171
+ }
172
+ const appApisDirName = `${rootPath}/${existingRelativeLocation}/apis`;
173
+ const apis = [];
174
+ if (await fs.pathExists(appApisDirName)) {
175
+ for (const apiName of Object.values(superblocksApplicationConfig.apis)) {
176
+ const apiContent = await readYamlFile(`${appApisDirName}/${slugifyName(apiName)}.yaml`);
177
+ // This mimics the shape of the ApiV3Dto object
178
+ apis.push({
179
+ id: apiContent.metadata.id,
180
+ apiPb: apiContent,
181
+ });
182
+ }
183
+ }
184
+ return {
185
+ application,
186
+ apis,
187
+ pages,
188
+ };
189
+ }
190
+ exports.readMultiPageApplicationFromDisk = readMultiPageApplicationFromDisk;
121
191
  async function readApiFromDisk(rootPath, existingRelativeLocation) {
122
192
  return {
123
193
  apiPb: await readYamlFile(`${rootPath}/${existingRelativeLocation}/api.yaml`),
@@ -179,7 +249,9 @@ async function writeResourceToDisk(resourceType, resourceId, resource, rootPath,
179
249
  for (const existingApiId of Object.keys(existingApplicationConfig.apis)) {
180
250
  const existingApiName = existingApplicationConfig.apis[existingApiId];
181
251
  const existingApiPath = `${apisDirName}/${slugifyName(existingApiName)}.yaml`;
182
- if (!(existingApiId in newApplicationConfig.apis)) {
252
+ const newApiName = newApplicationConfig.apis[existingApiId];
253
+ if (!(existingApiId in newApplicationConfig.apis) ||
254
+ slugifyName(newApiName) !== slugifyName(existingApiName)) {
183
255
  await fs.remove(existingApiPath);
184
256
  }
185
257
  }
@@ -231,7 +303,144 @@ async function writeResourceToDisk(resourceType, resourceId, resource, rootPath,
231
303
  }
232
304
  }
233
305
  exports.writeResourceToDisk = writeResourceToDisk;
234
- async function removeResourceFromDisk(resourceLocation) {
306
+ // NOTE: If a change is made to how applications are written to disk, please update
307
+ // logic to read applications from disk in the "readMultiPageApplicationFromDisk" function accordingly.
308
+ async function writeMultiPageApplicationToDisk(resource, rootPath, existingRelativeLocation, migrateFromSinglePage) {
309
+ var _a, _b, _c, _d;
310
+ const parentDirName = "apps";
311
+ const newRelativeLocation = `${parentDirName}/${slugifyName(resource.application.name)}`;
312
+ const relativeLocation = existingRelativeLocation !== null && existingRelativeLocation !== void 0 ? existingRelativeLocation : newRelativeLocation;
313
+ const appDirName = node_path_1.default.resolve(rootPath, relativeLocation);
314
+ if (!(await fs.pathExists(appDirName))) {
315
+ await fs.mkdir(appDirName, { recursive: true });
316
+ }
317
+ const applicationContent = (0, yaml_1.stringify)(resource.application, {
318
+ sortMapEntries: true,
319
+ });
320
+ await fs.outputFile(`${appDirName}/application.yaml`, applicationContent);
321
+ const newApplicationConfig = {
322
+ configType: "APPLICATION",
323
+ apis: {},
324
+ id: resource.application.id,
325
+ };
326
+ newApplicationConfig.pages = {};
327
+ const apiPromises = [];
328
+ for (const page of Object.values(resource.pages)) {
329
+ const pageId = page.id;
330
+ const pageDirName = `${appDirName}/pages/${slugifyName(page.name)}`;
331
+ if (!(await fs.pathExists(pageDirName))) {
332
+ await fs.mkdir(pageDirName, { recursive: true });
333
+ }
334
+ newApplicationConfig.pages[pageId] = {
335
+ id: page.id,
336
+ name: page.name,
337
+ apis: {},
338
+ };
339
+ const pageContent = (0, yaml_1.stringify)({
340
+ id: page.id,
341
+ name: page.name,
342
+ applicationId: page.applicationId,
343
+ isHidden: page.isHidden,
344
+ layouts: page.layouts,
345
+ }, {
346
+ sortMapEntries: true,
347
+ blockQuote: "literal",
348
+ });
349
+ await fs.outputFile(`${pageDirName}/page.yaml`, pageContent);
350
+ const pageApisDirName = `${pageDirName}/apis`;
351
+ for (const api of page.apis) {
352
+ const originalApiName = extractApiName(api);
353
+ apiPromises.push(writeApi(api, originalApiName, pageApisDirName));
354
+ newApplicationConfig.pages[pageId].apis[api.id] = originalApiName;
355
+ }
356
+ }
357
+ const apisDirName = `${appDirName}/apis`;
358
+ if (resource.apis && resource.apis.length) {
359
+ await fs.ensureDir(apisDirName);
360
+ for (const api of resource.apis) {
361
+ const originalApiName = extractApiName(api);
362
+ apiPromises.push(writeApi(api, originalApiName, apisDirName));
363
+ newApplicationConfig.apis[api.id] = originalApiName;
364
+ }
365
+ await Promise.all(apiPromises);
366
+ }
367
+ const existingApplicationConfig = await (0, util_1.getSuperblocksApplicationConfigIfExists)(appDirName);
368
+ // iterate through existing app-level apis and remove from disk any that are no longer present
369
+ for (const existingApiId of Object.keys((_a = existingApplicationConfig === null || existingApplicationConfig === void 0 ? void 0 : existingApplicationConfig.apis) !== null && _a !== void 0 ? _a : {})) {
370
+ const existingApiName = existingApplicationConfig === null || existingApplicationConfig === void 0 ? void 0 : existingApplicationConfig.apis[existingApiId];
371
+ const existingApiPath = `${apisDirName}/${slugifyName(existingApiName)}.yaml`;
372
+ const newApiName = newApplicationConfig.apis[existingApiId];
373
+ if (!(existingApiId in newApplicationConfig.apis) ||
374
+ slugifyName(newApiName) !== slugifyName(existingApiName)) {
375
+ await fs.remove(existingApiPath);
376
+ }
377
+ }
378
+ // iterate through existing pages and remove from disk any that are no longer present
379
+ for (const existingPage of Object.values((_b = existingApplicationConfig === null || existingApplicationConfig === void 0 ? void 0 : existingApplicationConfig.pages) !== null && _b !== void 0 ? _b : {})) {
380
+ const existingPageId = existingPage.id;
381
+ const existingPageDir = `${appDirName}/pages/${slugifyName(existingPage.name)}`;
382
+ if (existingPageId in newApplicationConfig.pages) {
383
+ // page still exists
384
+ const currentPage = newApplicationConfig.pages[existingPageId];
385
+ const newPageName = currentPage.name;
386
+ if (slugifyName(newPageName) !== slugifyName(existingPage.name)) {
387
+ // page was renamed
388
+ await fs.remove(existingPageDir);
389
+ }
390
+ // iterate through existing page-level apis and remove from disk any that are no longer present
391
+ for (const [existingApiId, existingApiName] of Object.entries(existingPage.apis)) {
392
+ const existingApiPath = `${existingPageDir}/apis/${slugifyName(existingApiName)}.yaml`;
393
+ if (existingApiId in currentPage.apis) {
394
+ // api still exists
395
+ const newApiName = newApplicationConfig.pages[existingPageId].apis[existingApiId];
396
+ if (slugifyName(newApiName) !== slugifyName(existingApiName)) {
397
+ // api was renamed
398
+ await fs.remove(existingApiPath);
399
+ }
400
+ }
401
+ else {
402
+ // api was removed
403
+ await fs.remove(existingApiPath);
404
+ }
405
+ }
406
+ }
407
+ else {
408
+ // page was removed
409
+ await fs.remove(existingPageDir);
410
+ // remove all page-level apis
411
+ for (const existingApiName of Object.values(existingPage.apis)) {
412
+ const existingApiPath = `${existingPageDir}/apis/${slugifyName(existingApiName)}.yaml`;
413
+ await fs.remove(existingApiPath);
414
+ }
415
+ }
416
+ }
417
+ await fs.ensureDir(`${appDirName}/${util_2.SUPERBLOCKS_HOME_FOLDER_NAME}`);
418
+ await fs.writeFile(`${appDirName}/${util_2.RESOURCE_CONFIG_PATH}`, JSON.stringify(sortByKey(newApplicationConfig), null, 2));
419
+ const createdFiles = await Promise.resolve(
420
+ // Defensive check for when application settings are missing componentFiles
421
+ (_d = (_c = resource.componentFiles) === null || _c === void 0 ? void 0 : _c.map((file) => downloadFile(appDirName, file.filename, file.url))) !== null && _d !== void 0 ? _d : []);
422
+ // print out failed downloads synchronously here
423
+ createdFiles
424
+ .filter((createdFiles) => createdFiles.length)
425
+ .forEach((createdFile) => {
426
+ console.log(`Unable to download ${createdFile}`);
427
+ });
428
+ // do a post-migration cleanup if necessary
429
+ if (migrateFromSinglePage) {
430
+ await fs.remove(`${appDirName}/page.yaml`);
431
+ // delete app-level apis if they are not present in the new application config
432
+ if (!resource.apis || !resource.apis.length) {
433
+ await fs.remove(`${appDirName}/apis`);
434
+ }
435
+ }
436
+ return {
437
+ location: relativeLocation,
438
+ resourceType: "APPLICATION",
439
+ };
440
+ }
441
+ exports.writeMultiPageApplicationToDisk = writeMultiPageApplicationToDisk;
442
+ async function removeResourceFromDisk(rootPath, resourceRelativeLocation) {
443
+ const resourceLocation = node_path_1.default.resolve(rootPath, resourceRelativeLocation);
235
444
  await fs.remove(resourceLocation);
236
445
  }
237
446
  exports.removeResourceFromDisk = removeResourceFromDisk;
@@ -365,8 +574,12 @@ async function getHeadCommit(branch) {
365
574
  return [headCommitId, headCommitMessage];
366
575
  }
367
576
  exports.getHeadCommit = getHeadCommit;
577
+ function isCI() {
578
+ return process.env.CI === "true";
579
+ }
580
+ exports.isCI = isCI;
368
581
  async function isGitRepoDirty() {
369
- if (process.env.CI === "true") {
582
+ if (isCI()) {
370
583
  // Skip dirtiness check in CI environments
371
584
  return false;
372
585
  }
@@ -380,3 +593,155 @@ function extractApiName(api) {
380
593
  return (_d = (_c = (_b = (_a = api.apiPb) === null || _a === void 0 ? void 0 : _a.metadata) === null || _b === void 0 ? void 0 : _b.name) !== null && _c !== void 0 ? _c : api === null || api === void 0 ? void 0 : api.name) !== null && _d !== void 0 ? _d : "Unknown";
381
594
  }
382
595
  exports.extractApiName = extractApiName;
596
+ function writeApi(api, originalApiName, appDirName) {
597
+ const apiName = slugifyName(originalApiName);
598
+ const apiContent = (0, yaml_1.stringify)(api.apiPb, {
599
+ sortMapEntries: true,
600
+ blockQuote: "literal",
601
+ });
602
+ const handleApi = async () => {
603
+ await fs.outputFile(`${appDirName}/${apiName}.yaml`, apiContent);
604
+ };
605
+ return handleApi();
606
+ }
607
+ function validateSinglePageApplication(applicationConfig, superblocksRootPath, location) {
608
+ // validate app level APIs
609
+ for (const apiName of Object.values(applicationConfig.apis)) {
610
+ const apiPath = node_path_1.default.resolve(superblocksRootPath, location, "apis", `${slugifyName(apiName)}.yaml`);
611
+ const validateApiError = validateYamlFile(apiPath);
612
+ if (validateApiError) {
613
+ return validateApiError;
614
+ }
615
+ }
616
+ // validate page
617
+ const pagePath = node_path_1.default.resolve(superblocksRootPath, location, "page.yaml");
618
+ const validatePageError = validateYamlFile(pagePath);
619
+ if (validatePageError) {
620
+ return validatePageError;
621
+ }
622
+ }
623
+ async function validateMultiPageApplication(applicationConfig, superblocksRootPath, location) {
624
+ var _a;
625
+ // validate app level APIs
626
+ for (const apiName of Object.values(applicationConfig.apis)) {
627
+ const apiPath = node_path_1.default.resolve(superblocksRootPath, location, "apis", `${slugifyName(apiName)}.yaml`);
628
+ const validateApiError = await validateYamlFile(apiPath);
629
+ if (validateApiError) {
630
+ return validateApiError;
631
+ }
632
+ }
633
+ // validate pages
634
+ for (const page of Object.values((_a = applicationConfig.pages) !== null && _a !== void 0 ? _a : {})) {
635
+ const pagePath = node_path_1.default.resolve(superblocksRootPath, location, "pages", slugifyName(page.name), "page.yaml");
636
+ const validatePageError = await validateYamlFile(pagePath);
637
+ if (validatePageError) {
638
+ return validatePageError;
639
+ }
640
+ // validate page level APIs
641
+ for (const apiName of Object.values(page.apis)) {
642
+ const apiPath = node_path_1.default.resolve(superblocksRootPath, location, "pages", slugifyName(page.name), "apis", `${slugifyName(apiName)}.yaml`);
643
+ const validateApiError = await validateYamlFile(apiPath);
644
+ if (validateApiError) {
645
+ return validateApiError;
646
+ }
647
+ }
648
+ }
649
+ return undefined;
650
+ }
651
+ async function validateLocalResource(superblocksRootPath, resource) {
652
+ switch (resource.resourceType) {
653
+ case "APPLICATION": {
654
+ // make sure application config exists
655
+ const applicationConfigPath = node_path_1.default.resolve(superblocksRootPath, resource.location, util_2.RESOURCE_CONFIG_PATH);
656
+ if (!(await fs.pathExists(applicationConfigPath))) {
657
+ return `File ${relativeToCurrentDir(applicationConfigPath)} not found. Superblocks CLI commands cannot function without it.`;
658
+ }
659
+ let fileStructureType = undefined;
660
+ let applicationConfig = undefined;
661
+ try {
662
+ // make sure it's a well-formed application config
663
+ applicationConfig = await fs.readJSON(applicationConfigPath);
664
+ if (!applicationConfig) {
665
+ throw new Error();
666
+ }
667
+ fileStructureType =
668
+ getFileStructureTypeFromResourceConfig(applicationConfig);
669
+ }
670
+ catch {
671
+ return `File ${relativeToCurrentDir(applicationConfigPath)} is not a valid JSON file. Please be sure it's valid JSON and rerun the command.`;
672
+ }
673
+ const applicationYamlPath = node_path_1.default.resolve(superblocksRootPath, resource.location, "application.yaml");
674
+ // make sure application.yaml is a well-formed yaml file
675
+ try {
676
+ await readYamlFile(applicationYamlPath);
677
+ }
678
+ catch {
679
+ return `File ${relativeToCurrentDir(applicationYamlPath)} is not a valid YAML file. Please be sure it's valid YAML and rerun the command.`;
680
+ }
681
+ switch (fileStructureType) {
682
+ case FileStructureType.SINGLE_PAGE: {
683
+ validateSinglePageApplication(applicationConfig, superblocksRootPath, resource.location);
684
+ break;
685
+ }
686
+ case FileStructureType.MULTI_PAGE: {
687
+ const validationError = validateMultiPageApplication(applicationConfig, superblocksRootPath, resource.location);
688
+ if (validationError) {
689
+ return validationError;
690
+ }
691
+ break;
692
+ }
693
+ default: {
694
+ return `Invalid file structure type ${fileStructureType}`;
695
+ }
696
+ }
697
+ break;
698
+ }
699
+ case "BACKEND": {
700
+ // make sure the backend config exists
701
+ const backendConfigPath = node_path_1.default.resolve(superblocksRootPath, resource.location, util_2.RESOURCE_CONFIG_PATH);
702
+ if (!(await fs.pathExists(backendConfigPath))) {
703
+ return `File ${relativeToCurrentDir(backendConfigPath)} not found. Superblocks CLI commands cannot function without it.`;
704
+ }
705
+ // make sure it's a well-formed backend config
706
+ try {
707
+ await fs.readJSON(backendConfigPath);
708
+ }
709
+ catch {
710
+ return `File ${relativeToCurrentDir(backendConfigPath)} is not a valid JSON file. Please be sure it's valid JSON and rerun the command.`;
711
+ }
712
+ // make sure that api.yaml exists
713
+ const apiYamlPath = node_path_1.default.resolve(superblocksRootPath, resource.location, "api.yaml");
714
+ const validateYamlFileError = await validateYamlFile(apiYamlPath);
715
+ if (validateYamlFileError) {
716
+ return validateYamlFileError;
717
+ }
718
+ break;
719
+ }
720
+ }
721
+ return undefined;
722
+ }
723
+ exports.validateLocalResource = validateLocalResource;
724
+ async function deleteResourcesAndUpdateRootConfig(removedResourceIds, existingSuperblocksRootConfig, superblocksRootPath, superblocksRootConfigPath) {
725
+ for (const resourceId of removedResourceIds) {
726
+ const resource = existingSuperblocksRootConfig === null || existingSuperblocksRootConfig === void 0 ? void 0 : existingSuperblocksRootConfig.resources[resourceId];
727
+ await removeResourceFromDisk(superblocksRootPath, resource.location);
728
+ }
729
+ for (const removedResourceId of removedResourceIds) {
730
+ delete existingSuperblocksRootConfig.resources[removedResourceId];
731
+ }
732
+ // update superblocks.json file with removed resources
733
+ await fs.writeFile(superblocksRootConfigPath, JSON.stringify(sortByKey(existingSuperblocksRootConfig), null, 2));
734
+ }
735
+ exports.deleteResourcesAndUpdateRootConfig = deleteResourcesAndUpdateRootConfig;
736
+ async function validateYamlFile(yamlPath) {
737
+ // make sure a yaml file is well-formed
738
+ try {
739
+ await readYamlFile(yamlPath);
740
+ }
741
+ catch {
742
+ return `File ${relativeToCurrentDir(yamlPath)} is not a valid YAML file. Please be sure it's valid YAML and rerun the command.`;
743
+ }
744
+ }
745
+ function relativeToCurrentDir(applicationConfigPath) {
746
+ return `./${node_path_1.default.relative(process.cwd(), applicationConfigPath)}`;
747
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.5.2",
2
+ "version": "1.7.0",
3
3
  "commands": {
4
4
  "init": {
5
5
  "id": "init",
@@ -30,7 +30,7 @@
30
30
  "args": {
31
31
  "resource_url": {
32
32
  "name": "resource_url",
33
- "description": "Superblocks resource URL (i.e. https://app.superblocks.com/applications/<application_id>/pages/<page_id>)",
33
+ "description": "Superblocks resource URL (i.e. https://app.superblocks.com/applications/<application_id> or https://app.superblocks.com/applications/edit/<application_id>)",
34
34
  "required": false
35
35
  }
36
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superblocksteam/cli",
3
- "version": "1.5.2",
3
+ "version": "1.7.0",
4
4
  "description": "Official Superblocks CLI",
5
5
  "bin": {
6
6
  "superblocks": "bin/run"
@@ -18,11 +18,11 @@
18
18
  "@oclif/core": "^2.11.7",
19
19
  "@oclif/plugin-help": "^5.2.16",
20
20
  "@oclif/plugin-plugins": "^3.1.10",
21
- "@superblocksteam/css-plugin": "1.5.2",
22
- "@superblocksteam/react-shim": "1.5.2",
23
- "@superblocksteam/sdk": "1.5.2",
24
- "@superblocksteam/util": "1.5.2",
25
- "@superblocksteam/vite-custom-component-reload-plugin": "1.5.2",
21
+ "@superblocksteam/css-plugin": "1.7.0",
22
+ "@superblocksteam/react-shim": "1.7.0",
23
+ "@superblocksteam/sdk": "1.7.0",
24
+ "@superblocksteam/util": "1.7.0",
25
+ "@superblocksteam/vite-custom-component-reload-plugin": "1.7.0",
26
26
  "@vitejs/plugin-react": "^4.1.0",
27
27
  "colorette": "^2.0.19",
28
28
  "enquirer": "^2.3.6",