@epic-web/workshop-utils 6.60.0 → 6.61.1

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.
@@ -4871,6 +4871,14 @@ export declare function getAppFromFile(filePath: string): Promise<{
4871
4871
  epicVideoEmbeds?: string[] | undefined;
4872
4872
  } | undefined>;
4873
4873
  export declare function savePlayground(): Promise<void>;
4874
+ export type SavedPlayground = {
4875
+ id: string;
4876
+ appName: string;
4877
+ createdAt: string;
4878
+ createdAtMs: number;
4879
+ fullPath: string;
4880
+ };
4881
+ export declare function getSavedPlaygrounds(): Promise<Array<SavedPlayground>>;
4874
4882
  export declare function setPlayground(srcDir: string, { reset }?: {
4875
4883
  reset?: boolean;
4876
4884
  }): Promise<void>;
@@ -16,12 +16,14 @@ import { cachified, exampleAppCache, playgroundAppCache, problemAppCache, soluti
16
16
  import { compileMdx } from "./compile-mdx.server.js";
17
17
  import { getAppConfig, getStackBlitzUrl } from "./config.server.js";
18
18
  import { getPreferences } from "./db.server.js";
19
+ import { logger } from "./logger.js";
19
20
  import { getDirModifiedTime } from "./modified-time.server.js";
20
21
  import { closeProcess, isAppRunning, runAppDev, waitOnApp, } from "./process-manager.server.js";
21
22
  import { requestStorageify } from "./request-context.server.js";
22
23
  import { getServerTimeHeader, time } from "./timing.server.js";
23
24
  import { dayjs } from "./utils.server.js";
24
25
  import { getErrorMessage } from "./utils.js";
26
+ const log = logger('epic:apps');
25
27
  global.__epicshop_apps_initialized__ ??= false;
26
28
  export function setWorkshopRoot(root = process.env.EPICSHOP_CONTEXT_CWD ?? process.cwd()) {
27
29
  process.env.EPICSHOP_CONTEXT_CWD = root;
@@ -656,14 +658,26 @@ async function getTestInfo({ fullPath, }) {
656
658
  }
657
659
  // tests are found in the corresponding solution directory
658
660
  const testAppFullPath = (await findSolutionDir({ fullPath })) ?? fullPath;
659
- const dirList = await fs.promises.readdir(testAppFullPath);
660
- const testFiles = dirList.filter((item) => item.includes('.test.'));
661
- if (testFiles.length) {
662
- return {
663
- type: 'browser',
664
- pathname: `${getPathname(fullPath)}test/`,
665
- testFiles,
666
- };
661
+ try {
662
+ const dirList = await fs.promises.readdir(testAppFullPath);
663
+ const testFiles = dirList.filter((item) => item.includes('.test.'));
664
+ if (testFiles.length) {
665
+ return {
666
+ type: 'browser',
667
+ pathname: `${getPathname(fullPath)}test/`,
668
+ testFiles,
669
+ };
670
+ }
671
+ }
672
+ catch (error) {
673
+ // Handle ENOTDIR error (path is a file, not a directory)
674
+ if (error instanceof Error && 'code' in error && error.code === 'ENOTDIR') {
675
+ log(`Skipping non-directory path in getTestInfo: ${testAppFullPath}`);
676
+ }
677
+ else {
678
+ // Re-throw other errors
679
+ throw error;
680
+ }
667
681
  }
668
682
  return { type: 'none' };
669
683
  }
@@ -772,7 +786,18 @@ async function getExampleAppFromPath(fullPath, index, request) {
772
786
  }
773
787
  async function getExampleApps({ timings, request, } = {}) {
774
788
  const examplesDir = path.join(getWorkshopRoot(), 'examples');
775
- const exampleDirs = (await readDir(examplesDir)).map((p) => path.join(examplesDir, p));
789
+ // Filter to only include directories, not files like README.mdx
790
+ const entries = await fs.promises
791
+ .readdir(examplesDir, { withFileTypes: true })
792
+ .catch(() => []);
793
+ const exampleDirs = entries
794
+ .filter((entry) => {
795
+ if (entry.isDirectory())
796
+ return true;
797
+ log(`Skipping non-directory in examples: ${entry.name}`);
798
+ return false;
799
+ })
800
+ .map((entry) => path.join(examplesDir, entry.name));
776
801
  const exampleApps = [];
777
802
  for (const exampleDir of exampleDirs) {
778
803
  const index = exampleDirs.indexOf(exampleDir);
@@ -1049,6 +1074,46 @@ locally and uncheck "Enable saving playground."
1049
1074
  }
1050
1075
  await fsExtra.copy(playgroundDir, path.join(savedPlaygroundsDir, savedPlaygroundDirName));
1051
1076
  }
1077
+ const savedPlaygroundTimestampPattern = /^(\d{4})\.(\d{2})\.(\d{2})_(\d{2})\.(\d{2})\.(\d{2})$/;
1078
+ function parseSavedPlaygroundDirName(dirName) {
1079
+ const parts = dirName.split('_');
1080
+ if (parts.length < 3)
1081
+ return null;
1082
+ const timestampPart = `${parts[0]}_${parts[1]}`;
1083
+ const appName = parts.slice(2).join('_') || dirName;
1084
+ const match = savedPlaygroundTimestampPattern.exec(timestampPart);
1085
+ if (!match)
1086
+ return null;
1087
+ const [, year, month, day, hour, minute, second] = match;
1088
+ const createdAt = new Date(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(second));
1089
+ if (Number.isNaN(createdAt.getTime()))
1090
+ return null;
1091
+ return { appName, createdAt };
1092
+ }
1093
+ export async function getSavedPlaygrounds() {
1094
+ const savedPlaygroundsDir = path.join(getWorkshopRoot(), 'saved-playgrounds');
1095
+ if (!(await exists(savedPlaygroundsDir)))
1096
+ return [];
1097
+ const dirEntries = await fsExtra.readdir(savedPlaygroundsDir, {
1098
+ withFileTypes: true,
1099
+ });
1100
+ const savedPlaygrounds = await Promise.all(dirEntries
1101
+ .filter((entry) => entry.isDirectory())
1102
+ .map(async (entry) => {
1103
+ const fullPath = path.join(savedPlaygroundsDir, entry.name);
1104
+ const parsed = parseSavedPlaygroundDirName(entry.name);
1105
+ const stat = await fsExtra.stat(fullPath).catch(() => null);
1106
+ const createdAt = parsed?.createdAt ?? (stat ? new Date(stat.mtimeMs) : new Date(0));
1107
+ return {
1108
+ id: entry.name,
1109
+ appName: parsed?.appName ?? entry.name,
1110
+ createdAt: createdAt.toISOString(),
1111
+ createdAtMs: createdAt.getTime(),
1112
+ fullPath,
1113
+ };
1114
+ }));
1115
+ return savedPlaygrounds.sort((a, b) => b.createdAtMs - a.createdAtMs);
1116
+ }
1052
1117
  export async function setPlayground(srcDir, { reset } = {}) {
1053
1118
  const preferences = await getPreferences();
1054
1119
  const playgroundApp = await getAppByName('playground');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epic-web/workshop-utils",
3
- "version": "6.60.0",
3
+ "version": "6.61.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },