@superblocksteam/cli 1.5.1 → 1.6.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.1 linux-x64 node-v18.19.1
15
+ @superblocksteam/cli/1.6.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.1",
10
+ "@superblocksteam/custom-components": "1.6.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")) {
@@ -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,
@@ -4,7 +4,6 @@ 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
6
  const util_1 = require("@superblocksteam/util");
7
- const fs = tslib_1.__importStar(require("fs-extra"));
8
7
  const listr2_1 = require("listr2");
9
8
  const authenticated_command_1 = require("../common/authenticated-command");
10
9
  const version_control_1 = require("../common/version-control");
@@ -28,6 +27,7 @@ class Pull extends authenticated_command_1.AuthenticatedCommand {
28
27
  ] = await (0, util_1.getSuperblocksMonorepoConfigJson)(true);
29
28
  ctx.existingSuperblocksResourceConfig =
30
29
  await (0, util_1.getSuperblocksResourceConfigIfExists)();
30
+ ctx.superblocksRootPath = node_path_1.default.resolve(node_path_1.default.dirname(ctx.superblocksRootConfigPath), "..");
31
31
  }
32
32
  catch {
33
33
  // no existing superblocks config
@@ -51,7 +51,7 @@ class Pull extends authenticated_command_1.AuthenticatedCommand {
51
51
  {
52
52
  title: "Checking for deleted Superblocks resources...",
53
53
  task: async (ctx, task) => {
54
- var _a, _b, _c;
54
+ var _a, _b;
55
55
  try {
56
56
  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
57
  switch (resource === null || resource === void 0 ? void 0 : resource.resourceType) {
@@ -109,10 +109,7 @@ Would you like to also delete these resources from your filesystem?`,
109
109
  },
110
110
  ]);
111
111
  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
- }
112
+ await (0, version_control_1.deleteResourcesAndUpdateRootConfig)(ctx.removedResourceIds, ctx.existingSuperblocksRootConfig, ctx.superblocksRootPath, ctx.superblocksRootConfigPath);
116
113
  }
117
114
  }
118
115
  catch (e) {
@@ -153,7 +150,6 @@ Would you like to also delete these resources from your filesystem?`,
153
150
  task.title = `Pulling resources from branch ${ctx.localBranchName}...`;
154
151
  const viewMode = await (0, version_control_1.getMode)(task, mode);
155
152
  const subtasks = [];
156
- const superblocksRootPath = node_path_1.default.resolve(node_path_1.default.dirname(ctx.superblocksRootConfigPath), "..");
157
153
  for (const resourceId of ctx.resourceIdsToPull) {
158
154
  const resource = (_a = ctx.existingSuperblocksRootConfig) === null || _a === void 0 ? void 0 : _a.resources[resourceId];
159
155
  const branchName = (_b = ctx.branchToPullFrom.get(resourceId)) !== null && _b !== void 0 ? _b : ctx.localBranchName;
@@ -166,15 +162,39 @@ Would you like to also delete these resources from your filesystem?`,
166
162
  [util_1.COMPONENT_EVENT_HEADER]: util_1.ComponentEvent.PULL,
167
163
  };
168
164
  try {
169
- const application = await this.getSdk().fetchApplicationWithComponents({
170
- applicationId: resourceId,
171
- branch: branchName,
172
- viewMode,
173
- headers,
174
- });
175
165
  task.title += `: fetched`;
176
- ctx.writtenResources[resourceId] =
177
- await (0, version_control_1.writeResourceToDisk)("APPLICATION", resourceId, application, superblocksRootPath, resource.location);
166
+ const fileStructureType = await (0, version_control_1.getFileStructureType)(ctx.superblocksRootPath, resource.location);
167
+ if (fileStructureType === version_control_1.FileStructureType.SINGLE_PAGE) {
168
+ const multiPageEnabled = (await this.getSdk().fetchCurrentUser()).flagBootstrap["server.multipage.enabled"];
169
+ if (multiPageEnabled) {
170
+ 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.`);
171
+ }
172
+ const application = await this.getSdk().fetchApplicationWithComponents({
173
+ applicationId: resourceId,
174
+ branch: branchName,
175
+ viewMode,
176
+ headers,
177
+ fetchSinglePageApplication: true,
178
+ });
179
+ if (!application) {
180
+ return;
181
+ }
182
+ ctx.writtenResources[resourceId] =
183
+ await (0, version_control_1.writeResourceToDisk)("APPLICATION", resourceId, application, ctx.superblocksRootPath, resource.location);
184
+ }
185
+ else {
186
+ const application = (await this.getSdk().fetchApplicationWithComponents({
187
+ applicationId: resourceId,
188
+ branch: branchName,
189
+ viewMode,
190
+ headers,
191
+ }));
192
+ if (!application) {
193
+ return;
194
+ }
195
+ ctx.writtenResources[resourceId] =
196
+ await (0, version_control_1.writeMultiPageApplicationToDisk)(application, ctx.superblocksRootPath, resource.location, false);
197
+ }
178
198
  task.title += `: done`;
179
199
  }
180
200
  catch (e) {
@@ -198,7 +218,7 @@ Would you like to also delete these resources from your filesystem?`,
198
218
  const backend = await this.getSdk().fetchApi(resourceId, viewMode, branchName);
199
219
  task.title += `: fetched`;
200
220
  ctx.writtenResources[resourceId] =
201
- await (0, version_control_1.writeResourceToDisk)("BACKEND", resourceId, backend, superblocksRootPath, resource.location);
221
+ await (0, version_control_1.writeResourceToDisk)("BACKEND", resourceId, backend, ctx.superblocksRootPath, resource.location);
202
222
  task.title += `: done`;
203
223
  },
204
224
  });
@@ -215,17 +235,6 @@ Would you like to also delete these resources from your filesystem?`,
215
235
  });
216
236
  },
217
237
  },
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
238
  ], {
230
239
  concurrent: false,
231
240
  });
@@ -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) {
@@ -168,17 +166,31 @@ Would you like to also delete these resources from your filesystem?`,
168
166
  title: `Pushing application ${resource.location}...`,
169
167
  task: async (_ctx, task) => {
170
168
  var _a;
171
- const applicationConfig = {
172
- ...(await (0, version_control_1.readApplicationFromDisk)(superblocksRootPath, resource.location)),
173
- commitId: ctx.headCommitId,
174
- commitMessage: ctx.headCommitMessage,
175
- };
169
+ const fileStructureType = await (0, version_control_1.getFileStructureType)(superblocksRootPath, resource.location);
170
+ if (fileStructureType === version_control_1.FileStructureType.SINGLE_PAGE &&
171
+ (await this.getSdk().fetchCurrentUser()).flagBootstrap["server.multipage.enabled"]) {
172
+ 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.`);
173
+ }
174
+ const applicationConfig = fileStructureType === version_control_1.FileStructureType.SINGLE_PAGE
175
+ ? {
176
+ ...(await (0, version_control_1.readApplicationFromDisk)(superblocksRootPath, resource.location)),
177
+ commitId: ctx.headCommitId,
178
+ commitMessage: ctx.headCommitMessage,
179
+ }
180
+ : {
181
+ ...(await (0, version_control_1.readMultiPageApplicationFromDisk)(superblocksRootPath, resource.location)),
182
+ commitId: ctx.headCommitId,
183
+ commitMessage: ctx.headCommitMessage,
184
+ };
176
185
  task.title += `: read from disk`;
177
186
  try {
187
+ const branch = (_a = ctx.branchToPushTo.get(resourceId)) !== null && _a !== void 0 ? _a : ctx.localBranchName;
188
+ task.title += `: going to push commit ${applicationConfig.commitId} to branch ${branch}`;
178
189
  await this.getSdk().pushApplication({
179
190
  applicationId: resourceId,
180
191
  applicationConfig,
181
- branch: (_a = ctx.branchToPushTo.get(resourceId)) !== null && _a !== void 0 ? _a : ctx.localBranchName,
192
+ branch,
193
+ multiPage: fileStructureType === version_control_1.FileStructureType.MULTI_PAGE,
182
194
  });
183
195
  }
184
196
  catch (error) {
@@ -210,10 +222,12 @@ Would you like to also delete these resources from your filesystem?`,
210
222
  };
211
223
  task.title += `: read from disk`;
212
224
  try {
225
+ const branch = (_a = ctx.branchToPushTo.get(resourceId)) !== null && _a !== void 0 ? _a : ctx.localBranchName;
226
+ task.title += `: going to push commit ${apiConfig.commitId} to branch ${branch}`;
213
227
  await this.getSdk().pushApi({
214
228
  apiId: resourceId,
215
229
  apiConfig,
216
- branch: (_a = ctx.branchToPushTo.get(resourceId)) !== null && _a !== void 0 ? _a : ctx.localBranchName,
230
+ branch,
217
231
  });
218
232
  }
219
233
  catch (error) {
@@ -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>;
@@ -29,3 +38,4 @@ export declare function getCurrentGitBranch(): Promise<string>;
29
38
  export declare function getHeadCommit(branch: string): Promise<[string, string]>;
30
39
  export declare function isGitRepoDirty(): Promise<boolean>;
31
40
  export declare function extractApiName(api: ApiWrapper): string;
41
+ 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.extractApiName = exports.isGitRepoDirty = 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;
@@ -380,3 +589,26 @@ function extractApiName(api) {
380
589
  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
590
  }
382
591
  exports.extractApiName = extractApiName;
592
+ function writeApi(api, originalApiName, appDirName) {
593
+ const apiName = slugifyName(originalApiName);
594
+ const apiContent = (0, yaml_1.stringify)(api.apiPb, {
595
+ sortMapEntries: true,
596
+ blockQuote: "literal",
597
+ });
598
+ const handleApi = async () => {
599
+ await fs.outputFile(`${appDirName}/${apiName}.yaml`, apiContent);
600
+ };
601
+ return handleApi();
602
+ }
603
+ async function deleteResourcesAndUpdateRootConfig(removedResourceIds, existingSuperblocksRootConfig, superblocksRootPath, superblocksRootConfigPath) {
604
+ for (const resourceId of removedResourceIds) {
605
+ const resource = existingSuperblocksRootConfig === null || existingSuperblocksRootConfig === void 0 ? void 0 : existingSuperblocksRootConfig.resources[resourceId];
606
+ await removeResourceFromDisk(superblocksRootPath, resource.location);
607
+ }
608
+ for (const removedResourceId of removedResourceIds) {
609
+ delete existingSuperblocksRootConfig.resources[removedResourceId];
610
+ }
611
+ // update superblocks.json file with removed resources
612
+ await fs.writeFile(superblocksRootConfigPath, JSON.stringify(sortByKey(existingSuperblocksRootConfig), null, 2));
613
+ }
614
+ exports.deleteResourcesAndUpdateRootConfig = deleteResourcesAndUpdateRootConfig;
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.5.1",
2
+ "version": "1.6.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.1",
3
+ "version": "1.6.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.1",
22
- "@superblocksteam/react-shim": "1.5.1",
23
- "@superblocksteam/sdk": "1.5.1",
24
- "@superblocksteam/util": "1.5.1",
25
- "@superblocksteam/vite-custom-component-reload-plugin": "1.5.1",
21
+ "@superblocksteam/css-plugin": "1.6.0",
22
+ "@superblocksteam/react-shim": "1.6.0",
23
+ "@superblocksteam/sdk": "1.6.0",
24
+ "@superblocksteam/util": "1.6.0",
25
+ "@superblocksteam/vite-custom-component-reload-plugin": "1.6.0",
26
26
  "@vitejs/plugin-react": "^4.1.0",
27
27
  "colorette": "^2.0.19",
28
28
  "enquirer": "^2.3.6",