@superblocksteam/cli 1.9.2 → 1.10.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.
@@ -1,16 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
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;
3
+ exports.deleteResourcesAndUpdateRootConfig = exports.validateLocalResource = exports.writeAppApi = exports.extractApiName = exports.isGitRepoDirty = exports.isCI = exports.getHeadCommit = exports.getCurrentGitBranch = exports.getCurrentGitBranchIfGit = exports.getLocalGitRepoState = exports.sortByKey = exports.getMode = exports.removeResourceFromDisk = exports.addExistingFilePathsForApi = exports.writeMultiPageApplicationToDisk = exports.writeResourceToDisk = exports.readApiFromDisk = exports.readMultiPageApplicationFromDisk = exports.readApplicationFromDisk = exports.getFileStructureType = exports.readAppApiYamlFile = exports.getApiRepresentation = 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"));
7
7
  const path_1 = require("path");
8
+ const sdk_1 = require("@superblocksteam/sdk");
8
9
  const util_1 = require("@superblocksteam/util");
9
- const util_2 = require("@superblocksteam/util");
10
10
  const colorette_1 = require("colorette");
11
11
  const fs = tslib_1.__importStar(require("fs-extra"));
12
12
  const lodash_1 = require("lodash");
13
13
  const lodash_2 = tslib_1.__importDefault(require("lodash"));
14
+ const semver = tslib_1.__importStar(require("semver"));
14
15
  const simple_git_1 = require("simple-git");
15
16
  const slugify_1 = tslib_1.__importDefault(require("slugify"));
16
17
  const yaml_1 = require("yaml");
@@ -18,6 +19,10 @@ exports.LATEST_EDITS_MODE = "latest-edits";
18
19
  exports.MOST_RECENT_COMMIT_MODE = "most-recent-commit";
19
20
  exports.DEPLOYED_MODE = "deployed";
20
21
  exports.DEFAULT_BRANCH = "main";
22
+ const LANGUAGE_STEP_EXTENSIONS = {
23
+ javascript: "js",
24
+ python: "py",
25
+ };
21
26
  exports.modeFlagValuesMap = {
22
27
  [exports.LATEST_EDITS_MODE]: "Latest edits",
23
28
  [exports.MOST_RECENT_COMMIT_MODE]: "Most recent commit",
@@ -50,6 +55,34 @@ const atLeastOneSelection = (value) => {
50
55
  return true;
51
56
  };
52
57
  exports.atLeastOneSelection = atLeastOneSelection;
58
+ const DEFAULT_FILE_VERSION = "0.1.0";
59
+ const SPLIT_LARGE_API_STEPS_VERSION = "0.2.0";
60
+ const LATEST_FILE_VERSION = SPLIT_LARGE_API_STEPS_VERSION;
61
+ function getApiRepresentation(featureFlags, resourceConfig) {
62
+ var _a;
63
+ const linesForLargeSteps = (_a = featureFlags.linesForLargeSteps()) !== null && _a !== void 0 ? _a : sdk_1.DEFAULT_LINES_FOR_LARGE_STEPS;
64
+ if (featureFlags.splitLargeApiStepsEnabled() &&
65
+ isPostSplitLargeApiSteps(resourceConfig)) {
66
+ return {
67
+ extractLargeSourceFiles: true,
68
+ minLinesForExtraction: linesForLargeSteps,
69
+ };
70
+ }
71
+ // Return the default
72
+ return {
73
+ extractLargeSourceFiles: false,
74
+ minLinesForExtraction: linesForLargeSteps,
75
+ };
76
+ }
77
+ exports.getApiRepresentation = getApiRepresentation;
78
+ function isPostSplitLargeApiSteps(resourceConfig) {
79
+ var _a, _b;
80
+ if (!resourceConfig) {
81
+ return false;
82
+ }
83
+ const version = (_b = (_a = resourceConfig.metadata) === null || _a === void 0 ? void 0 : _a.fileVersion) !== null && _b !== void 0 ? _b : DEFAULT_FILE_VERSION;
84
+ return semver.compare(version, SPLIT_LARGE_API_STEPS_VERSION) >= 0;
85
+ }
53
86
  function slugifyName(originalName) {
54
87
  return (0, slugify_1.default)(originalName, {
55
88
  replacement: "_",
@@ -95,6 +128,95 @@ async function downloadFile(rootDirectory, filepath, url) {
95
128
  }
96
129
  return Promise.resolve("");
97
130
  }
131
+ async function readAppApiYamlFile(parentDirectory, apiName) {
132
+ var _a;
133
+ // The API is either stored in its entirety in a single YAML file, or
134
+ // or in a subdirectory containing the YAML file and zero or more language-specific files.
135
+ const apiNameSlug = slugifyName(apiName !== null && apiName !== void 0 ? apiName : "api");
136
+ const singularApiYamlPath = `${parentDirectory}/${apiNameSlug}.yaml`;
137
+ const apiDirPath = `${parentDirectory}/${apiNameSlug}`;
138
+ const nestedApiYamlPath = `${apiDirPath}/${apiNameSlug}.yaml`;
139
+ // Read the YAML file
140
+ let yamlPath;
141
+ let yamlParentPath;
142
+ if (await fs.pathExists(singularApiYamlPath)) {
143
+ // The API YAML file is in the parent directory
144
+ yamlPath = singularApiYamlPath;
145
+ yamlParentPath = parentDirectory;
146
+ }
147
+ else if (await fs.pathExists(nestedApiYamlPath)) {
148
+ // The API YAML file is nested in an API-specific directory
149
+ yamlPath = nestedApiYamlPath;
150
+ yamlParentPath = apiDirPath;
151
+ }
152
+ else {
153
+ throw new Error(`API ${apiName !== null && apiName !== void 0 ? apiName : ""} not found at ${parentDirectory}`);
154
+ }
155
+ // That is nested in an API-specific directory
156
+ const apiDefn = (await readYamlFile(yamlPath));
157
+ // The API YAML file may or may not have language-specific content in separate files.
158
+ // Replace any file references with the actual content
159
+ await resolveLanguageSpecificStepContentFromBlocks(yamlParentPath, (_a = apiDefn.blocks) !== null && _a !== void 0 ? _a : []);
160
+ return apiDefn;
161
+ }
162
+ exports.readAppApiYamlFile = readAppApiYamlFile;
163
+ async function resolveLanguageSpecificStepContentFromBlocks(parentPath, blocks) {
164
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
165
+ for (const block of blocks) {
166
+ // Handle language-specific step content
167
+ if (block.step) {
168
+ const step = block.step;
169
+ await resolveLanguageSpecificStepContentFromStep(parentPath, step);
170
+ }
171
+ // Handle conditional blocks
172
+ if (block.conditional) {
173
+ await resolveLanguageSpecificStepContentFromBlocks(parentPath, (_b = (_a = block.conditional.if) === null || _a === void 0 ? void 0 : _a.blocks) !== null && _b !== void 0 ? _b : []);
174
+ for (const elseIfBlock of block.conditional.elseIf) {
175
+ await resolveLanguageSpecificStepContentFromBlocks(parentPath, elseIfBlock.blocks);
176
+ }
177
+ await resolveLanguageSpecificStepContentFromBlocks(parentPath, (_d = (_c = block.conditional.else) === null || _c === void 0 ? void 0 : _c.blocks) !== null && _d !== void 0 ? _d : []);
178
+ }
179
+ // Handle loop blocks
180
+ await resolveLanguageSpecificStepContentFromBlocks(parentPath, (_f = (_e = block.loop) === null || _e === void 0 ? void 0 : _e.blocks) !== null && _f !== void 0 ? _f : []);
181
+ // Handle try-catch blocks
182
+ if (block.tryCatch) {
183
+ await resolveLanguageSpecificStepContentFromBlocks(parentPath, (_h = (_g = block.tryCatch.try) === null || _g === void 0 ? void 0 : _g.blocks) !== null && _h !== void 0 ? _h : []);
184
+ await resolveLanguageSpecificStepContentFromBlocks(parentPath, (_k = (_j = block.tryCatch.catch) === null || _j === void 0 ? void 0 : _j.blocks) !== null && _k !== void 0 ? _k : []);
185
+ await resolveLanguageSpecificStepContentFromBlocks(parentPath, (_m = (_l = block.tryCatch.finally) === null || _l === void 0 ? void 0 : _l.blocks) !== null && _m !== void 0 ? _m : []);
186
+ }
187
+ // Handle parallel blocks
188
+ if (block.parallel) {
189
+ for (const path of Object.values((_q = (_p = (_o = block.parallel) === null || _o === void 0 ? void 0 : _o.static) === null || _p === void 0 ? void 0 : _p.paths) !== null && _q !== void 0 ? _q : {})) {
190
+ await resolveLanguageSpecificStepContentFromBlocks(parentPath, path.blocks);
191
+ }
192
+ await resolveLanguageSpecificStepContentFromBlocks(parentPath, (_t = (_s = (_r = block.parallel) === null || _r === void 0 ? void 0 : _r.dynamic) === null || _s === void 0 ? void 0 : _s.blocks) !== null && _t !== void 0 ? _t : []);
193
+ }
194
+ // Handle stream blocks
195
+ if (block.stream) {
196
+ await resolveLanguageSpecificStepContentFromBlocks(parentPath, (_v = (_u = block.stream.process) === null || _u === void 0 ? void 0 : _u.blocks) !== null && _v !== void 0 ? _v : []);
197
+ await resolveLanguageSpecificStepContentFromStep(parentPath, (_x = (_w = block.stream.trigger) === null || _w === void 0 ? void 0 : _w.step) !== null && _x !== void 0 ? _x : {});
198
+ }
199
+ }
200
+ }
201
+ async function resolveLanguageSpecificStepContentFromStep(parentPath, step) {
202
+ // Handle language-specific step content
203
+ if (!(step === null || step === void 0 ? void 0 : step.integration) || !LANGUAGE_STEP_EXTENSIONS[step.integration]) {
204
+ return;
205
+ }
206
+ const languageContent = step[step.integration];
207
+ if (languageContent.body &&
208
+ typeof languageContent.body === "object" &&
209
+ "path" in languageContent.body) {
210
+ const languageFilePath = `${parentPath}/${languageContent.body.path}`;
211
+ if (await fs.pathExists(languageFilePath)) {
212
+ // Read the file content
213
+ const stepBodyContent = await fs.readFile(languageFilePath, "utf8");
214
+ if (stepBodyContent && languageContent) {
215
+ languageContent.body = stepBodyContent;
216
+ }
217
+ }
218
+ }
219
+ }
98
220
  async function readYamlFile(path) {
99
221
  return (0, yaml_1.parse)(await fs.readFile(path, "utf8"));
100
222
  }
@@ -120,7 +242,7 @@ async function readApplicationFromDisk(rootPath, existingRelativeLocation) {
120
242
  if (await fs.pathExists(apisDirName)) {
121
243
  const apiFiles = await fs.readdir(apisDirName);
122
244
  for (const apiFile of apiFiles) {
123
- const apiContent = await readYamlFile(`${apisDirName}/${apiFile}`);
245
+ const apiContent = await readAppApiYamlFile(apisDirName, apiFile);
124
246
  // This mimics the shape of the ApiV3Dto object
125
247
  apis.push({
126
248
  id: apiContent.metadata.id,
@@ -145,12 +267,16 @@ async function readMultiPageApplicationFromDisk(rootPath, existingRelativeLocati
145
267
  const pages = [];
146
268
  if (await fs.pathExists(pagesDirName)) {
147
269
  for (const page of Object.values((_a = superblocksApplicationConfig.pages) !== null && _a !== void 0 ? _a : {})) {
270
+ // Read in the page definition
148
271
  const pageContent = await readYamlFile(`${pagesDirName}/${slugifyName(page.name)}/page.yaml`);
272
+ // Read in the API definitions for this page
149
273
  const pageApisDirName = `${pagesDirName}/${slugifyName(page.name)}/apis`;
150
274
  const apis = [];
151
275
  if (await fs.pathExists(pageApisDirName)) {
152
- for (const apiName of Object.values(page.apis)) {
153
- const apiContent = await readYamlFile(`${pageApisDirName}/${slugifyName(apiName)}.yaml`);
276
+ // Get the API names from pageApis if it exists, or from apis if it doesn't
277
+ const pageApiNames = getApiNamesFromPageConfig(page);
278
+ for (const apiName of pageApiNames) {
279
+ const apiContent = await readAppApiYamlFile(pageApisDirName, apiName);
154
280
  // This mimics the shape of the ApiV3Dto object
155
281
  apis.push({
156
282
  id: apiContent.metadata.id,
@@ -172,8 +298,11 @@ async function readMultiPageApplicationFromDisk(rootPath, existingRelativeLocati
172
298
  const appApisDirName = `${rootPath}/${existingRelativeLocation}/apis`;
173
299
  const apis = [];
174
300
  if (await fs.pathExists(appApisDirName)) {
175
- for (const apiName of Object.values(superblocksApplicationConfig.apis)) {
176
- const apiContent = await readYamlFile(`${appApisDirName}/${slugifyName(apiName)}.yaml`);
301
+ // Get the API names from appApis if it exists, or from apis if it doesn't
302
+ const appConfig = superblocksApplicationConfig;
303
+ const apiNames = getApiNamesFromApplicationConfig(appConfig);
304
+ for (const apiName of apiNames) {
305
+ const apiContent = await readAppApiYamlFile(appApisDirName, apiName);
177
306
  // This mimics the shape of the ApiV3Dto object
178
307
  apis.push({
179
308
  id: apiContent.metadata.id,
@@ -188,15 +317,29 @@ async function readMultiPageApplicationFromDisk(rootPath, existingRelativeLocati
188
317
  };
189
318
  }
190
319
  exports.readMultiPageApplicationFromDisk = readMultiPageApplicationFromDisk;
320
+ function getApiNamesFromApplicationConfig(applicationConfig) {
321
+ var _a;
322
+ return applicationConfig.appApis
323
+ ? Object.values(applicationConfig.appApis).map((api) => api.name)
324
+ : Object.values((_a = applicationConfig.apis) !== null && _a !== void 0 ? _a : {});
325
+ }
326
+ function getApiNamesFromPageConfig(pageConfig) {
327
+ var _a;
328
+ return pageConfig.pageApis
329
+ ? Object.values(pageConfig.pageApis).map((api) => api.name)
330
+ : Object.values((_a = pageConfig.apis) !== null && _a !== void 0 ? _a : {});
331
+ }
191
332
  async function readApiFromDisk(rootPath, existingRelativeLocation) {
333
+ const path = `${rootPath}/${existingRelativeLocation}`;
334
+ const apiContent = await readAppApiYamlFile(path);
192
335
  return {
193
- apiPb: await readYamlFile(`${rootPath}/${existingRelativeLocation}/api.yaml`),
336
+ apiPb: apiContent,
194
337
  };
195
338
  }
196
339
  exports.readApiFromDisk = readApiFromDisk;
197
340
  // NOTE: If a change is made to how applications are written to disk, please update
198
341
  // logic to read applications from disk in the "readApplicationFromDisk" function accordingly.
199
- async function writeResourceToDisk(resourceType, resourceId, resource, rootPath, existingRelativeLocation) {
342
+ async function writeResourceToDisk(resourceType, resourceId, resource, rootPath, featureFlags, existingRelativeLocation, preferredApiRepresentation) {
200
343
  var _a, _b, _c;
201
344
  switch (resourceType) {
202
345
  case "APPLICATION": {
@@ -221,43 +364,39 @@ async function writeResourceToDisk(resourceType, resourceId, resource, rootPath,
221
364
  const apiPromises = [];
222
365
  const apisDirName = `${appDirName}/apis`;
223
366
  await fs.ensureDir(apisDirName);
367
+ const existingApplicationConfig = await (0, util_1.getSuperblocksApplicationConfigIfExists)(appDirName);
368
+ const existingFilePaths = getExistingFilePathsForApplicationApi(existingApplicationConfig, appDirName);
224
369
  const newApplicationConfig = {
225
370
  configType: "APPLICATION",
226
- apis: {},
227
371
  defaultPageId: (_a = resource.page) === null || _a === void 0 ? void 0 : _a.id,
228
372
  id: resource.application.id,
373
+ metadata: getResourceConfigMetadata(featureFlags, existingApplicationConfig),
229
374
  };
375
+ const apiRepresentation = preferredApiRepresentation !== null && preferredApiRepresentation !== void 0 ? preferredApiRepresentation : getApiRepresentation(featureFlags, newApplicationConfig);
230
376
  if (resource.apis) {
231
377
  for (const api of resource.apis) {
232
- const originalApiName = extractApiName(api);
233
- const apiName = slugifyName(originalApiName);
234
- const apiContent = (0, yaml_1.stringify)(api.apiPb, {
235
- sortMapEntries: true,
236
- blockQuote: "literal",
237
- });
238
- const handleApi = async () => {
239
- await fs.outputFile(`${apisDirName}/${apiName}.yaml`, apiContent);
240
- };
241
- apiPromises.push(handleApi());
242
- newApplicationConfig.apis[api.id] = originalApiName;
378
+ const apiInfo = await writeAppApi(api, appDirName, existingFilePaths, apiPromises, apiRepresentation);
379
+ if (apiRepresentation.extractLargeSourceFiles) {
380
+ if (!newApplicationConfig.appApis) {
381
+ newApplicationConfig.appApis = {};
382
+ }
383
+ newApplicationConfig.appApis[api.apiPb.metadata.id] = apiInfo;
384
+ }
385
+ else {
386
+ if (!newApplicationConfig.apis) {
387
+ newApplicationConfig.apis = {};
388
+ }
389
+ newApplicationConfig.apis[api.apiPb.metadata.id] = apiInfo.name;
390
+ }
243
391
  }
244
392
  await Promise.all(apiPromises);
245
393
  }
246
- const existingApplicationConfig = await (0, util_1.getSuperblocksApplicationConfigIfExists)(appDirName);
247
- // iterate through existing apis and remove from disk any that are no longer present
248
- if (existingApplicationConfig === null || existingApplicationConfig === void 0 ? void 0 : existingApplicationConfig.apis) {
249
- for (const existingApiId of Object.keys(existingApplicationConfig.apis)) {
250
- const existingApiName = existingApplicationConfig.apis[existingApiId];
251
- const existingApiPath = `${apisDirName}/${slugifyName(existingApiName)}.yaml`;
252
- const newApiName = newApplicationConfig.apis[existingApiId];
253
- if (!(existingApiId in newApplicationConfig.apis) ||
254
- slugifyName(newApiName) !== slugifyName(existingApiName)) {
255
- await fs.remove(existingApiPath);
256
- }
257
- }
394
+ // Delete any existing files that were not overwritten
395
+ for (const filePath of existingFilePaths) {
396
+ await fs.remove(filePath);
258
397
  }
259
- await fs.ensureDir(`${appDirName}/${util_2.SUPERBLOCKS_HOME_FOLDER_NAME}`);
260
- await fs.writeFile(`${appDirName}/${util_2.RESOURCE_CONFIG_PATH}`, JSON.stringify(sortByKey(newApplicationConfig), null, 2));
398
+ await fs.ensureDir(`${appDirName}/${util_1.SUPERBLOCKS_HOME_FOLDER_NAME}`);
399
+ await fs.writeFile(`${appDirName}/${util_1.RESOURCE_CONFIG_PATH}`, JSON.stringify(sortByKey(newApplicationConfig), null, 2));
261
400
  const createdFiles = await Promise.resolve(
262
401
  // Defensive check for when application settings are missing componentFiles
263
402
  (_c = (_b = resource.componentFiles) === null || _b === void 0 ? void 0 : _b.map((file) => downloadFile(appDirName, file.filename, file.url))) !== null && _c !== void 0 ? _c : []);
@@ -281,17 +420,26 @@ async function writeResourceToDisk(resourceType, resourceId, resource, rootPath,
281
420
  if (!(await fs.pathExists(backendDirName))) {
282
421
  await fs.mkdir(backendDirName, { recursive: true });
283
422
  }
284
- const backendContent = (0, yaml_1.stringify)(resource.apiPb, {
285
- sortMapEntries: true,
286
- blockQuote: "literal",
287
- });
288
- await fs.outputFile(`${backendDirName}/api.yaml`, backendContent);
423
+ const existingBackendConfig = await (0, util_1.getSuperblocksBackendConfigIfExists)(backendDirName);
424
+ const existingFilePaths = getExistingFilePathsForBackendApi(existingBackendConfig, backendDirName);
289
425
  const backendConfig = {
290
426
  id: resourceId,
291
427
  configType: "BACKEND",
428
+ metadata: getResourceConfigMetadata(featureFlags, existingBackendConfig),
292
429
  };
293
- await fs.ensureDir(`${backendDirName}/${util_2.SUPERBLOCKS_HOME_FOLDER_NAME}`);
294
- await fs.writeFile(`${backendDirName}/${util_2.RESOURCE_CONFIG_PATH}`, JSON.stringify(sortByKey(backendConfig), null, 2));
430
+ const apiRepresentation = preferredApiRepresentation !== null && preferredApiRepresentation !== void 0 ? preferredApiRepresentation : getApiRepresentation(featureFlags, backendConfig);
431
+ // Write the API file(s)
432
+ const apiInfo = await writeBackendApi(resource, backendDirName, [], apiRepresentation, existingFilePaths);
433
+ if (apiRepresentation.extractLargeSourceFiles) {
434
+ backendConfig.sourceFiles = apiInfo.sourceFiles;
435
+ }
436
+ // Write the backend config file
437
+ await fs.ensureDir(`${backendDirName}/${util_1.SUPERBLOCKS_HOME_FOLDER_NAME}`);
438
+ await fs.writeFile(`${backendDirName}/${util_1.RESOURCE_CONFIG_PATH}`, JSON.stringify(sortByKey(backendConfig), null, 2));
439
+ // Delete any existing files that were not overwritten
440
+ for (const filePath of existingFilePaths) {
441
+ await fs.remove(filePath);
442
+ }
295
443
  return {
296
444
  location: relativeLocation,
297
445
  resourceType: "BACKEND",
@@ -305,8 +453,8 @@ async function writeResourceToDisk(resourceType, resourceId, resource, rootPath,
305
453
  exports.writeResourceToDisk = writeResourceToDisk;
306
454
  // NOTE: If a change is made to how applications are written to disk, please update
307
455
  // 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;
456
+ async function writeMultiPageApplicationToDisk(resource, rootPath, featureFlags, existingRelativeLocation, migrateFromSinglePage, preferredApiRepresentation) {
457
+ var _a, _b, _c;
310
458
  const parentDirName = "apps";
311
459
  const newRelativeLocation = `${parentDirName}/${slugifyName(resource.application.name)}`;
312
460
  const relativeLocation = existingRelativeLocation !== null && existingRelativeLocation !== void 0 ? existingRelativeLocation : newRelativeLocation;
@@ -318,11 +466,15 @@ async function writeMultiPageApplicationToDisk(resource, rootPath, existingRelat
318
466
  sortMapEntries: true,
319
467
  });
320
468
  await fs.outputFile(`${appDirName}/application.yaml`, applicationContent);
469
+ // Find the existing application config and existing file paths
470
+ const existingApplicationConfig = await (0, util_1.getSuperblocksApplicationConfigIfExists)(appDirName);
471
+ const existingFilePaths = getExistingFilePathsForApplicationApi(existingApplicationConfig, appDirName);
321
472
  const newApplicationConfig = {
322
473
  configType: "APPLICATION",
323
- apis: {},
324
474
  id: resource.application.id,
475
+ metadata: getResourceConfigMetadata(featureFlags, existingApplicationConfig),
325
476
  };
477
+ const apiRepresentation = preferredApiRepresentation !== null && preferredApiRepresentation !== void 0 ? preferredApiRepresentation : getApiRepresentation(featureFlags, newApplicationConfig);
326
478
  newApplicationConfig.pages = {};
327
479
  const apiPromises = [];
328
480
  for (const page of Object.values(resource.pages)) {
@@ -334,8 +486,8 @@ async function writeMultiPageApplicationToDisk(resource, rootPath, existingRelat
334
486
  newApplicationConfig.pages[pageId] = {
335
487
  id: page.id,
336
488
  name: page.name,
337
- apis: {},
338
489
  };
490
+ const pageConfig = newApplicationConfig.pages[pageId];
339
491
  const pageContent = (0, yaml_1.stringify)({
340
492
  id: page.id,
341
493
  name: page.name,
@@ -346,79 +498,63 @@ async function writeMultiPageApplicationToDisk(resource, rootPath, existingRelat
346
498
  sortMapEntries: true,
347
499
  blockQuote: "literal",
348
500
  });
349
- await fs.outputFile(`${pageDirName}/page.yaml`, pageContent);
350
- const pageApisDirName = `${pageDirName}/apis`;
501
+ const pageFilePath = `${pageDirName}/page.yaml`;
502
+ await fs.outputFile(pageFilePath, pageContent);
503
+ existingFilePaths.delete(pageFilePath);
351
504
  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;
505
+ const apiInfo = await writeAppApi(api, pageDirName, existingFilePaths, apiPromises, apiRepresentation);
506
+ // Update the page config with the new api info
507
+ const apiId = api.apiPb.metadata.id;
508
+ if (apiRepresentation.extractLargeSourceFiles) {
509
+ // Add the new pageApis field whenever large source files might be extracted
510
+ if (!pageConfig.pageApis) {
511
+ pageConfig.pageApis = {};
512
+ }
513
+ pageConfig.pageApis[apiId] = {
514
+ name: apiInfo.name,
515
+ sourceFiles: (_a = apiInfo.sourceFiles) === null || _a === void 0 ? void 0 : _a.sort(),
516
+ };
517
+ }
518
+ else {
519
+ if (!pageConfig.apis) {
520
+ pageConfig.apis = {};
521
+ }
522
+ pageConfig.apis[apiId] = apiInfo.name;
523
+ }
355
524
  }
356
525
  }
357
- const apisDirName = `${appDirName}/apis`;
526
+ const appApis = {};
358
527
  if (resource.apis && resource.apis.length) {
359
- await fs.ensureDir(apisDirName);
360
528
  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);
529
+ const apiInfo = await writeAppApi(api, appDirName, existingFilePaths, apiPromises, apiRepresentation);
530
+ const apiId = api.apiPb.metadata.id;
531
+ if (apiRepresentation.extractLargeSourceFiles) {
532
+ if (!newApplicationConfig.appApis) {
533
+ newApplicationConfig.appApis = {};
404
534
  }
535
+ newApplicationConfig.appApis[apiId] = apiInfo;
405
536
  }
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);
537
+ else {
538
+ if (!newApplicationConfig.apis) {
539
+ newApplicationConfig.apis = {};
540
+ }
541
+ newApplicationConfig.apis[apiId] = apiInfo.name;
414
542
  }
415
543
  }
544
+ await Promise.all(apiPromises);
545
+ }
546
+ if (apiRepresentation.extractLargeSourceFiles) {
547
+ newApplicationConfig.appApis = appApis;
416
548
  }
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));
549
+ // Delete any existing files that were not overwritten
550
+ for (const filePath of existingFilePaths) {
551
+ await fs.remove(filePath);
552
+ }
553
+ await fs.ensureDir(`${appDirName}/${util_1.SUPERBLOCKS_HOME_FOLDER_NAME}`);
554
+ await fs.writeFile(`${appDirName}/${util_1.RESOURCE_CONFIG_PATH}`, JSON.stringify(sortByKey(newApplicationConfig), null, 2));
419
555
  const createdFiles = await Promise.resolve(
420
556
  // 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 : []);
557
+ (_c = (_b = resource.componentFiles) === null || _b === void 0 ? void 0 : _b.map((file) => downloadFile(appDirName, file.filename, file.url))) !== null && _c !== void 0 ? _c : []);
422
558
  // print out failed downloads synchronously here
423
559
  createdFiles
424
560
  .filter((createdFiles) => createdFiles.length)
@@ -439,6 +575,95 @@ async function writeMultiPageApplicationToDisk(resource, rootPath, existingRelat
439
575
  };
440
576
  }
441
577
  exports.writeMultiPageApplicationToDisk = writeMultiPageApplicationToDisk;
578
+ function getExistingFilePathsForApplicationApi(superblocksApplicationConfig, location) {
579
+ const paths = new Set();
580
+ if (!superblocksApplicationConfig) {
581
+ return paths;
582
+ }
583
+ // Pages
584
+ if (superblocksApplicationConfig.pages) {
585
+ for (const page of Object.values(superblocksApplicationConfig.pages)) {
586
+ // Page YAML
587
+ const pageNameSlug = slugifyName(page.name);
588
+ const pagePath = `${location}/pages/${pageNameSlug}`;
589
+ paths.add(`${pagePath}/page.yaml`);
590
+ // Page APIs
591
+ if (page.pageApis) {
592
+ for (const api of Object.values(page.pageApis)) {
593
+ addExistingFilePathsForApi(api, `${pagePath}/apis`, paths, true);
594
+ }
595
+ }
596
+ else if (page.apis) {
597
+ for (const apiName of Object.values(page.apis)) {
598
+ addExistingFilePathsForApi({ name: apiName }, `${pagePath}/apis`, paths, true);
599
+ }
600
+ }
601
+ }
602
+ }
603
+ // APIs
604
+ if (superblocksApplicationConfig.appApis) {
605
+ // For file version >= 0.2.0, there are API files either the 'apis' folder or in an API-specific folder
606
+ for (const api of Object.values(superblocksApplicationConfig.appApis)) {
607
+ addExistingFilePathsForApi(api, `${location}/apis`, paths, true);
608
+ }
609
+ }
610
+ else if (superblocksApplicationConfig.apis) {
611
+ // For file version < 0.2.0, there are only API file
612
+ for (const apiName of Object.values(superblocksApplicationConfig.apis)) {
613
+ addExistingFilePathsForApi({ name: apiName }, `${location}/apis`, paths, true);
614
+ }
615
+ }
616
+ return paths;
617
+ }
618
+ function getExistingFilePathsForBackendApi(superblocksBackendConfig, location) {
619
+ const paths = new Set();
620
+ if (!superblocksBackendConfig) {
621
+ return paths;
622
+ }
623
+ // Pages
624
+ if (superblocksBackendConfig.sourceFiles) {
625
+ addExistingFilePathsForApi({ name: "api", sourceFiles: superblocksBackendConfig.sourceFiles }, location, paths, false);
626
+ }
627
+ else {
628
+ addExistingFilePathsForApi({ name: "api" }, location, paths, false);
629
+ }
630
+ return paths;
631
+ }
632
+ function addExistingFilePathsForApi(api, location, paths, useNestedFolder) {
633
+ var _a, _b;
634
+ const apiNameSlug = slugifyName(api.name);
635
+ if ((_a = api.sourceFiles) === null || _a === void 0 ? void 0 : _a.length) {
636
+ // API files are in an API-specific folder
637
+ const apiDirPath = useNestedFolder
638
+ ? `${location}/${apiNameSlug}`
639
+ : location;
640
+ paths.add(`${apiDirPath}/${apiNameSlug}.yaml`);
641
+ // And there are source files
642
+ for (const sourceFile of (_b = api.sourceFiles) !== null && _b !== void 0 ? _b : []) {
643
+ paths.add(`${apiDirPath}/${sourceFile}`);
644
+ }
645
+ }
646
+ else {
647
+ // API file is in the 'apis' folder with no separate source files
648
+ paths.add(`${location}/${apiNameSlug}.yaml`);
649
+ }
650
+ return paths;
651
+ }
652
+ exports.addExistingFilePathsForApi = addExistingFilePathsForApi;
653
+ function getResourceConfigMetadata(featureFlags, existingResourceConfig) {
654
+ if (!existingResourceConfig) {
655
+ // This is a new application, and we may need to add a metadata field to the application config
656
+ if (featureFlags.splitLargeApiStepsInNewEnabled()) {
657
+ return {
658
+ fileVersion: LATEST_FILE_VERSION,
659
+ };
660
+ }
661
+ }
662
+ else if (existingResourceConfig.metadata) {
663
+ return existingResourceConfig.metadata;
664
+ }
665
+ return undefined;
666
+ }
442
667
  async function removeResourceFromDisk(rootPath, resourceRelativeLocation) {
443
668
  const resourceLocation = node_path_1.default.resolve(rootPath, resourceRelativeLocation);
444
669
  await fs.remove(resourceLocation);
@@ -593,21 +818,161 @@ function extractApiName(api) {
593
818
  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";
594
819
  }
595
820
  exports.extractApiName = extractApiName;
596
- function writeApi(api, originalApiName, appDirName) {
597
- const apiName = slugifyName(originalApiName);
598
- const apiContent = (0, yaml_1.stringify)(api.apiPb, {
821
+ async function writeAppApi(api, directoryPath, existingFilePaths, apiPromises, apiRepresentation) {
822
+ const originalApiName = extractApiName(api);
823
+ const additionalStepFiles = [];
824
+ await writeApiFiles(api, slugifyName(originalApiName), `${directoryPath}/apis`, true, apiPromises, additionalStepFiles, apiRepresentation, existingFilePaths);
825
+ return {
826
+ name: originalApiName,
827
+ sourceFiles: additionalStepFiles.map((file) => file.relativePath).sort(),
828
+ };
829
+ }
830
+ exports.writeAppApi = writeAppApi;
831
+ async function writeBackendApi(api, directoryPath, apiPromises, apiRepresentation, existingFilePaths) {
832
+ const originalApiName = extractApiName(api);
833
+ const additionalStepFiles = [];
834
+ await writeApiFiles(api, "api", directoryPath, false, apiPromises, additionalStepFiles, apiRepresentation, existingFilePaths);
835
+ return {
836
+ name: originalApiName,
837
+ sourceFiles: additionalStepFiles.map((file) => file.relativePath).sort(),
838
+ };
839
+ }
840
+ async function writeApiFiles(api, apiNameSlug, directoryPath, nestedApiFiles, apiPromises, additionalStepFiles, apiRepresentation, existingFilePaths) {
841
+ let pathForApiFile = directoryPath;
842
+ // Determine whether to extract into separate files the source code from the steps
843
+ const updatedApi = extractAdditionalStepFiles(api.apiPb, additionalStepFiles, apiRepresentation);
844
+ if (additionalStepFiles.length > 0) {
845
+ if (nestedApiFiles) {
846
+ // This API has at least one large step code file, so we need a nested directory
847
+ pathForApiFile = `${pathForApiFile}/${apiNameSlug}`;
848
+ }
849
+ // Make sure the directory exists
850
+ await fs.ensureDir(pathForApiFile);
851
+ // Write any additional step source code files to the disk
852
+ for (const stepFile of additionalStepFiles) {
853
+ const sourceFilePath = `${pathForApiFile}/${stepFile.relativePath}`;
854
+ const handleApi = async () => {
855
+ await fs.outputFile(sourceFilePath, stepFile.contents);
856
+ };
857
+ apiPromises.push(handleApi());
858
+ // Mark the existing source file as being reused
859
+ existingFilePaths === null || existingFilePaths === void 0 ? void 0 : existingFilePaths.delete(sourceFilePath);
860
+ }
861
+ }
862
+ else {
863
+ // No additional step files to write, so mark the API directory as existing so that it will be removed later.
864
+ // Do this even if the current representation does not extract large source files, in case they were previously
865
+ existingFilePaths === null || existingFilePaths === void 0 ? void 0 : existingFilePaths.add(`${directoryPath}/${apiNameSlug}`);
866
+ }
867
+ // Convert the updated API to YAML and write it to disk
868
+ const apiContent = (0, yaml_1.stringify)(updatedApi, {
599
869
  sortMapEntries: true,
600
870
  blockQuote: "literal",
601
871
  });
872
+ const yamlFilePath = `${pathForApiFile}/${apiNameSlug}.yaml`;
602
873
  const handleApi = async () => {
603
- await fs.outputFile(`${appDirName}/${apiName}.yaml`, apiContent);
874
+ await fs.outputFile(yamlFilePath, apiContent);
604
875
  };
605
- return handleApi();
876
+ apiPromises.push(handleApi());
877
+ // Mark the existing source file as being reused
878
+ existingFilePaths === null || existingFilePaths === void 0 ? void 0 : existingFilePaths.delete(yamlFilePath);
879
+ }
880
+ function extractAdditionalStepFiles(api, additionalStepFiles, apiRepresentation) {
881
+ var _a, _b;
882
+ if (!api.blocks || !(apiRepresentation === null || apiRepresentation === void 0 ? void 0 : apiRepresentation.extractLargeSourceFiles)) {
883
+ return api;
884
+ }
885
+ const apiClone = lodash_2.default.cloneDeep(api);
886
+ const minLinesForExtraction = (_a = apiRepresentation === null || apiRepresentation === void 0 ? void 0 : apiRepresentation.minLinesForExtraction) !== null && _a !== void 0 ? _a : sdk_1.DEFAULT_LINES_FOR_LARGE_STEPS;
887
+ const existingFilePaths = new Set();
888
+ extractAdditionalStepFilesFromBlocks((_b = apiClone.blocks) !== null && _b !== void 0 ? _b : [], additionalStepFiles, existingFilePaths, minLinesForExtraction);
889
+ return apiClone;
890
+ }
891
+ function extractAdditionalStepFilesFromBlocks(blocks, additionalStepFiles, existingFilePaths, minLinesForExtraction) {
892
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
893
+ for (const block of blocks) {
894
+ // Handle language step files
895
+ if (block.step) {
896
+ extractAdditionalStepFilesFromStep(block.step, block.name, additionalStepFiles, existingFilePaths, minLinesForExtraction);
897
+ }
898
+ // Handle loops
899
+ extractAdditionalStepFilesFromBlocks((_b = (_a = block.loop) === null || _a === void 0 ? void 0 : _a.blocks) !== null && _b !== void 0 ? _b : [], additionalStepFiles, existingFilePaths, minLinesForExtraction);
900
+ // Handle try/catch
901
+ if (block.tryCatch) {
902
+ extractAdditionalStepFilesFromBlocks((_d = (_c = block.tryCatch.try) === null || _c === void 0 ? void 0 : _c.blocks) !== null && _d !== void 0 ? _d : [], additionalStepFiles, existingFilePaths, minLinesForExtraction);
903
+ extractAdditionalStepFilesFromBlocks((_f = (_e = block.tryCatch.catch) === null || _e === void 0 ? void 0 : _e.blocks) !== null && _f !== void 0 ? _f : [], additionalStepFiles, existingFilePaths, minLinesForExtraction);
904
+ extractAdditionalStepFilesFromBlocks((_h = (_g = block.tryCatch.finally) === null || _g === void 0 ? void 0 : _g.blocks) !== null && _h !== void 0 ? _h : [], additionalStepFiles, existingFilePaths, minLinesForExtraction);
905
+ }
906
+ // Handle conditional
907
+ if (block.conditional) {
908
+ extractAdditionalStepFilesFromBlocks((_k = (_j = block.conditional.if) === null || _j === void 0 ? void 0 : _j.blocks) !== null && _k !== void 0 ? _k : [], additionalStepFiles, existingFilePaths, minLinesForExtraction);
909
+ for (const elseIfBlock of (_l = block.conditional.elseIf) !== null && _l !== void 0 ? _l : []) {
910
+ extractAdditionalStepFilesFromBlocks(elseIfBlock.blocks, additionalStepFiles, existingFilePaths, minLinesForExtraction);
911
+ }
912
+ extractAdditionalStepFilesFromBlocks((_o = (_m = block.conditional.else) === null || _m === void 0 ? void 0 : _m.blocks) !== null && _o !== void 0 ? _o : [], additionalStepFiles, existingFilePaths, minLinesForExtraction);
913
+ }
914
+ // Handle Parallel API
915
+ if (block.parallel) {
916
+ for (const path of Object.values((_r = (_q = (_p = block.parallel) === null || _p === void 0 ? void 0 : _p.static) === null || _q === void 0 ? void 0 : _q.paths) !== null && _r !== void 0 ? _r : {})) {
917
+ extractAdditionalStepFilesFromBlocks(path.blocks, additionalStepFiles, existingFilePaths, minLinesForExtraction);
918
+ }
919
+ extractAdditionalStepFilesFromBlocks((_u = (_t = (_s = block.parallel) === null || _s === void 0 ? void 0 : _s.dynamic) === null || _t === void 0 ? void 0 : _t.blocks) !== null && _u !== void 0 ? _u : [], additionalStepFiles, existingFilePaths, minLinesForExtraction);
920
+ }
921
+ // Handle stream
922
+ if (block.stream) {
923
+ const trigger = block.stream.trigger;
924
+ if (trigger && trigger.step) {
925
+ extractAdditionalStepFilesFromStep(trigger.step, trigger.name, additionalStepFiles, existingFilePaths, minLinesForExtraction);
926
+ }
927
+ extractAdditionalStepFilesFromBlocks((_w = (_v = block.stream.process) === null || _v === void 0 ? void 0 : _v.blocks) !== null && _w !== void 0 ? _w : [], additionalStepFiles, existingFilePaths, minLinesForExtraction);
928
+ }
929
+ }
930
+ }
931
+ function extractAdditionalStepFilesFromStep(step, name, additionalStepFiles, existingFilePaths, minLinesForExtraction) {
932
+ // Handle language step files
933
+ if (step && "integration" in step) {
934
+ const integration = step.integration;
935
+ const extension = LANGUAGE_STEP_EXTENSIONS[integration];
936
+ if (extension) {
937
+ const languageContent = step[integration];
938
+ if (languageContent) {
939
+ const blockBody = languageContent.body;
940
+ if (blockBody && typeof blockBody === "string") {
941
+ const lines = blockBody === null || blockBody === void 0 ? void 0 : blockBody.split("\n");
942
+ const linesNum = lines === null || lines === void 0 ? void 0 : lines.length;
943
+ if (linesNum && linesNum > minLinesForExtraction) {
944
+ // Record the separate file is needed, with the language contents
945
+ const relativePath = findUnusedFilePath(slugifyName(name), extension, existingFilePaths);
946
+ additionalStepFiles.push({
947
+ relativePath: relativePath,
948
+ contents: blockBody,
949
+ language: integration,
950
+ });
951
+ // Replace the body with a reference to the separate file
952
+ languageContent.body = {
953
+ path: `./${relativePath}`,
954
+ };
955
+ }
956
+ }
957
+ }
958
+ }
959
+ }
960
+ }
961
+ function findUnusedFilePath(filename, extension, existingFilePaths) {
962
+ let proposedFilename = `${filename}.${extension}`;
963
+ let i = 1;
964
+ while (existingFilePaths.has(proposedFilename)) {
965
+ proposedFilename = `${filename}_${i}.${extension}`;
966
+ i++;
967
+ }
968
+ existingFilePaths.add(proposedFilename);
969
+ return proposedFilename;
606
970
  }
607
971
  async function validateMultiPageApplication(applicationConfig, superblocksRootPath, location) {
608
972
  var _a;
609
973
  // validate app level APIs
610
- for (const apiName of Object.values(applicationConfig.apis)) {
974
+ const apiNames = getApiNamesFromApplicationConfig(applicationConfig);
975
+ for (const apiName of apiNames) {
611
976
  const apiPath = node_path_1.default.resolve(superblocksRootPath, location, "apis", `${slugifyName(apiName)}.yaml`);
612
977
  const validateApiError = await validateYamlFile(apiPath);
613
978
  if (validateApiError) {
@@ -622,11 +987,26 @@ async function validateMultiPageApplication(applicationConfig, superblocksRootPa
622
987
  return validatePageError;
623
988
  }
624
989
  // validate page level APIs
625
- for (const apiName of Object.values(page.apis)) {
626
- const apiPath = node_path_1.default.resolve(superblocksRootPath, location, "pages", slugifyName(page.name), "apis", `${slugifyName(apiName)}.yaml`);
627
- const validateApiError = await validateYamlFile(apiPath);
628
- if (validateApiError) {
629
- return validateApiError;
990
+ const pageApiNames = getApiNamesFromPageConfig(page);
991
+ for (const apiName of pageApiNames) {
992
+ const apisPath = node_path_1.default.resolve(superblocksRootPath, location, "pages", slugifyName(page.name), "apis");
993
+ // Try the API path not in a nested directory
994
+ const apiPath = node_path_1.default.resolve(apisPath, `${slugifyName(apiName)}.yaml`);
995
+ if (fs.pathExistsSync(apiPath)) {
996
+ const validateApiError = await validateYamlFile(apiPath);
997
+ if (validateApiError) {
998
+ return validateApiError;
999
+ }
1000
+ }
1001
+ else {
1002
+ // Try the API path in a nested directory
1003
+ const nestedApiPath = node_path_1.default.resolve(apisPath, slugifyName(apiName), "api.yaml");
1004
+ if (fs.pathExistsSync(nestedApiPath)) {
1005
+ const validateApiError = await validateYamlFile(nestedApiPath);
1006
+ if (validateApiError) {
1007
+ return validateApiError;
1008
+ }
1009
+ }
630
1010
  }
631
1011
  }
632
1012
  }
@@ -636,7 +1016,7 @@ async function validateLocalResource(superblocksRootPath, resource) {
636
1016
  switch (resource.resourceType) {
637
1017
  case "APPLICATION": {
638
1018
  // make sure application config exists
639
- const applicationConfigPath = node_path_1.default.resolve(superblocksRootPath, resource.location, util_2.RESOURCE_CONFIG_PATH);
1019
+ const applicationConfigPath = node_path_1.default.resolve(superblocksRootPath, resource.location, util_1.RESOURCE_CONFIG_PATH);
640
1020
  if (!(await fs.pathExists(applicationConfigPath))) {
641
1021
  return `File ${relativeToCurrentDir(applicationConfigPath)} not found. Superblocks CLI commands cannot function without it.`;
642
1022
  }
@@ -667,7 +1047,7 @@ async function validateLocalResource(superblocksRootPath, resource) {
667
1047
  }
668
1048
  case "BACKEND": {
669
1049
  // make sure the backend config exists
670
- const backendConfigPath = node_path_1.default.resolve(superblocksRootPath, resource.location, util_2.RESOURCE_CONFIG_PATH);
1050
+ const backendConfigPath = node_path_1.default.resolve(superblocksRootPath, resource.location, util_1.RESOURCE_CONFIG_PATH);
671
1051
  if (!(await fs.pathExists(backendConfigPath))) {
672
1052
  return `File ${relativeToCurrentDir(backendConfigPath)} not found. Superblocks CLI commands cannot function without it.`;
673
1053
  }