@epic-web/workshop-utils 6.61.3 → 6.62.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/dist/apps.server.d.ts +13 -13
- package/dist/apps.server.js +144 -58
- package/dist/cache.server.d.ts +2 -2
- package/dist/cache.server.js +1 -1
- package/package.json +1 -1
package/dist/apps.server.d.ts
CHANGED
|
@@ -472,7 +472,7 @@ declare const SolutionAppSchema: z.ZodObject<{
|
|
|
472
472
|
instructionsCode?: string | undefined;
|
|
473
473
|
epicVideoEmbeds?: string[] | undefined;
|
|
474
474
|
}>;
|
|
475
|
-
declare const
|
|
475
|
+
declare const ExtraAppSchema: z.ZodObject<{
|
|
476
476
|
/** a unique identifier for the app */
|
|
477
477
|
name: z.ZodString;
|
|
478
478
|
/** the title of the app used for display (comes from the package.json title prop) */
|
|
@@ -550,7 +550,7 @@ declare const ExampleAppSchema: z.ZodObject<{
|
|
|
550
550
|
}>]>;
|
|
551
551
|
stackBlitzUrl: z.ZodNullable<z.ZodString>;
|
|
552
552
|
} & {
|
|
553
|
-
type: z.ZodLiteral<"
|
|
553
|
+
type: z.ZodLiteral<"extra">;
|
|
554
554
|
}, "strip", z.ZodTypeAny, {
|
|
555
555
|
test: {
|
|
556
556
|
type: "browser";
|
|
@@ -562,7 +562,7 @@ declare const ExampleAppSchema: z.ZodObject<{
|
|
|
562
562
|
} | {
|
|
563
563
|
type: "none";
|
|
564
564
|
};
|
|
565
|
-
type: "
|
|
565
|
+
type: "extra";
|
|
566
566
|
title: string;
|
|
567
567
|
name: string;
|
|
568
568
|
fullPath: string;
|
|
@@ -595,7 +595,7 @@ declare const ExampleAppSchema: z.ZodObject<{
|
|
|
595
595
|
} | {
|
|
596
596
|
type: "none";
|
|
597
597
|
};
|
|
598
|
-
type: "
|
|
598
|
+
type: "extra";
|
|
599
599
|
title: string;
|
|
600
600
|
name: string;
|
|
601
601
|
fullPath: string;
|
|
@@ -3683,7 +3683,7 @@ declare const AppSchema: z.ZodUnion<[z.ZodUnion<[z.ZodObject<{
|
|
|
3683
3683
|
}>]>;
|
|
3684
3684
|
stackBlitzUrl: z.ZodNullable<z.ZodString>;
|
|
3685
3685
|
} & {
|
|
3686
|
-
type: z.ZodLiteral<"
|
|
3686
|
+
type: z.ZodLiteral<"extra">;
|
|
3687
3687
|
}, "strip", z.ZodTypeAny, {
|
|
3688
3688
|
test: {
|
|
3689
3689
|
type: "browser";
|
|
@@ -3695,7 +3695,7 @@ declare const AppSchema: z.ZodUnion<[z.ZodUnion<[z.ZodObject<{
|
|
|
3695
3695
|
} | {
|
|
3696
3696
|
type: "none";
|
|
3697
3697
|
};
|
|
3698
|
-
type: "
|
|
3698
|
+
type: "extra";
|
|
3699
3699
|
title: string;
|
|
3700
3700
|
name: string;
|
|
3701
3701
|
fullPath: string;
|
|
@@ -3728,7 +3728,7 @@ declare const AppSchema: z.ZodUnion<[z.ZodUnion<[z.ZodObject<{
|
|
|
3728
3728
|
} | {
|
|
3729
3729
|
type: "none";
|
|
3730
3730
|
};
|
|
3731
|
-
type: "
|
|
3731
|
+
type: "extra";
|
|
3732
3732
|
title: string;
|
|
3733
3733
|
name: string;
|
|
3734
3734
|
fullPath: string;
|
|
@@ -3754,7 +3754,7 @@ declare const AppSchema: z.ZodUnion<[z.ZodUnion<[z.ZodObject<{
|
|
|
3754
3754
|
export type BaseExerciseStepApp = z.infer<typeof BaseExerciseStepAppSchema>;
|
|
3755
3755
|
export type ProblemApp = z.infer<typeof ProblemAppSchema>;
|
|
3756
3756
|
export type SolutionApp = z.infer<typeof SolutionAppSchema>;
|
|
3757
|
-
export type
|
|
3757
|
+
export type ExtraApp = z.infer<typeof ExtraAppSchema>;
|
|
3758
3758
|
export type PlaygroundApp = z.infer<typeof PlaygroundAppSchema>;
|
|
3759
3759
|
export type ExerciseStepApp = z.infer<typeof ExerciseStepAppSchema>;
|
|
3760
3760
|
export type App = z.infer<typeof AppSchema>;
|
|
@@ -3770,7 +3770,7 @@ export declare function isFirstStepSolutionApp(app: App): app is SolutionApp & {
|
|
|
3770
3770
|
stepNumber: 1;
|
|
3771
3771
|
};
|
|
3772
3772
|
export declare function isPlaygroundApp(app: any): app is PlaygroundApp;
|
|
3773
|
-
export declare function
|
|
3773
|
+
export declare function isExtraApp(app: any): app is ExtraApp;
|
|
3774
3774
|
export declare function isExerciseStepApp(app: any): app is ExerciseStepApp;
|
|
3775
3775
|
export declare const modifiedTimes: Map<string, number>;
|
|
3776
3776
|
export declare function init(workshopRoot?: string): Promise<void>;
|
|
@@ -4518,7 +4518,7 @@ export declare function getAppByName(name: string, { request, timings }?: Cachif
|
|
|
4518
4518
|
} | {
|
|
4519
4519
|
type: "none";
|
|
4520
4520
|
};
|
|
4521
|
-
type: "
|
|
4521
|
+
type: "extra";
|
|
4522
4522
|
title: string;
|
|
4523
4523
|
name: string;
|
|
4524
4524
|
fullPath: string;
|
|
@@ -4812,7 +4812,7 @@ export declare function getAppFromFile(filePath: string): Promise<{
|
|
|
4812
4812
|
} | {
|
|
4813
4813
|
type: "none";
|
|
4814
4814
|
};
|
|
4815
|
-
type: "
|
|
4815
|
+
type: "extra";
|
|
4816
4816
|
title: string;
|
|
4817
4817
|
name: string;
|
|
4818
4818
|
fullPath: string;
|
|
@@ -4903,7 +4903,7 @@ export declare function getWorkshopInstructions({ request, }?: {
|
|
|
4903
4903
|
readonly file: string;
|
|
4904
4904
|
readonly relativePath: "exercises";
|
|
4905
4905
|
}>;
|
|
4906
|
-
export declare function
|
|
4906
|
+
export declare function getExtrasInstructions({ request, }?: {
|
|
4907
4907
|
request?: Request;
|
|
4908
4908
|
}): Promise<{
|
|
4909
4909
|
readonly compiled: {
|
|
@@ -4916,7 +4916,7 @@ export declare function getExamplesInstructions({ request, }?: {
|
|
|
4916
4916
|
readonly error: string;
|
|
4917
4917
|
};
|
|
4918
4918
|
readonly file: string;
|
|
4919
|
-
readonly relativePath: "examples/README.mdx";
|
|
4919
|
+
readonly relativePath: "extra/README.mdx" | "example/README.mdx" | "examples/README.mdx";
|
|
4920
4920
|
}>;
|
|
4921
4921
|
export declare function getWorkshopFinished({ request, }?: {
|
|
4922
4922
|
request?: Request;
|
package/dist/apps.server.js
CHANGED
|
@@ -12,7 +12,7 @@ import { execa } from 'execa';
|
|
|
12
12
|
import fsExtra from 'fs-extra';
|
|
13
13
|
import { globby, isGitIgnored } from 'globby';
|
|
14
14
|
import { z } from 'zod';
|
|
15
|
-
import { cachified,
|
|
15
|
+
import { cachified, extraAppCache, playgroundAppCache, problemAppCache, solutionAppCache, directoryEmptyCache, } from "./cache.server.js";
|
|
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";
|
|
@@ -24,6 +24,57 @@ import { getServerTimeHeader, time } from "./timing.server.js";
|
|
|
24
24
|
import { dayjs } from "./utils.server.js";
|
|
25
25
|
import { getErrorMessage } from "./utils.js";
|
|
26
26
|
const log = logger('epic:apps');
|
|
27
|
+
const EXTRA_DIRNAME = 'extra';
|
|
28
|
+
const LEGACY_EXAMPLE_DIRNAME = 'example';
|
|
29
|
+
const LEGACY_EXAMPLES_DIRNAME = 'examples';
|
|
30
|
+
const EXTRA_DIR_CANDIDATES = [
|
|
31
|
+
EXTRA_DIRNAME,
|
|
32
|
+
LEGACY_EXAMPLE_DIRNAME,
|
|
33
|
+
LEGACY_EXAMPLES_DIRNAME,
|
|
34
|
+
];
|
|
35
|
+
async function resolveExtraDir() {
|
|
36
|
+
for (const dirName of EXTRA_DIR_CANDIDATES) {
|
|
37
|
+
const fullPath = path.join(getWorkshopRoot(), dirName);
|
|
38
|
+
if (await exists(fullPath)) {
|
|
39
|
+
return { dirName, fullPath };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
async function getExtraDirName() {
|
|
45
|
+
const resolved = await resolveExtraDir();
|
|
46
|
+
return resolved?.dirName ?? EXTRA_DIRNAME;
|
|
47
|
+
}
|
|
48
|
+
function getExtraDirInfoFromPath(fullPath) {
|
|
49
|
+
const normalizedFullPath = fullPath.replace(/\\/g, '/');
|
|
50
|
+
const normalizedRoot = getWorkshopRoot().replace(/\\/g, '/');
|
|
51
|
+
for (const dirName of EXTRA_DIR_CANDIDATES) {
|
|
52
|
+
const prefix = `${normalizedRoot}/${dirName}/`;
|
|
53
|
+
if (normalizedFullPath.startsWith(prefix)) {
|
|
54
|
+
return {
|
|
55
|
+
dirName,
|
|
56
|
+
restOfPath: normalizedFullPath.slice(prefix.length),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
function parseExtraAppName(appName) {
|
|
63
|
+
const prefixes = ['extra.', '.extra', 'example.', '.example'];
|
|
64
|
+
for (const prefix of prefixes) {
|
|
65
|
+
if (appName.startsWith(prefix)) {
|
|
66
|
+
let relativePath = appName.slice(prefix.length);
|
|
67
|
+
// Only strip the leading dot if the prefix didn't end with a dot
|
|
68
|
+
// (i.e., for .extra and .example, not for extra. and example.)
|
|
69
|
+
// This preserves hidden directories like .my-hidden-app
|
|
70
|
+
if (!prefix.endsWith('.') && relativePath.startsWith('.')) {
|
|
71
|
+
relativePath = relativePath.slice(1);
|
|
72
|
+
}
|
|
73
|
+
return relativePath.length ? relativePath : null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
27
78
|
global.__epicshop_apps_initialized__ ??= false;
|
|
28
79
|
export function setWorkshopRoot(root = process.env.EPICSHOP_CONTEXT_CWD ?? process.cwd()) {
|
|
29
80
|
process.env.EPICSHOP_CONTEXT_CWD = root;
|
|
@@ -83,8 +134,8 @@ const SolutionAppSchema = BaseExerciseStepAppSchema.extend({
|
|
|
83
134
|
type: z.literal('solution'),
|
|
84
135
|
problemName: z.string().nullable(),
|
|
85
136
|
});
|
|
86
|
-
const
|
|
87
|
-
type: z.literal('
|
|
137
|
+
const ExtraAppSchema = BaseAppSchema.extend({
|
|
138
|
+
type: z.literal('extra'),
|
|
88
139
|
});
|
|
89
140
|
const PlaygroundAppSchema = BaseAppSchema.extend({
|
|
90
141
|
type: z.literal('playground'),
|
|
@@ -131,7 +182,7 @@ const ExerciseStepAppSchema = z.union([ProblemAppSchema, SolutionAppSchema]);
|
|
|
131
182
|
const AppSchema = z.union([
|
|
132
183
|
ExerciseStepAppSchema,
|
|
133
184
|
PlaygroundAppSchema,
|
|
134
|
-
|
|
185
|
+
ExtraAppSchema,
|
|
135
186
|
]);
|
|
136
187
|
export function isApp(app) {
|
|
137
188
|
return AppSchema.safeParse(app).success;
|
|
@@ -151,8 +202,8 @@ export function isFirstStepSolutionApp(app) {
|
|
|
151
202
|
export function isPlaygroundApp(app) {
|
|
152
203
|
return isApp(app) && app.type === 'playground';
|
|
153
204
|
}
|
|
154
|
-
export function
|
|
155
|
-
return isApp(app) && app.type === '
|
|
205
|
+
export function isExtraApp(app) {
|
|
206
|
+
return isApp(app) && app.type === 'extra';
|
|
156
207
|
}
|
|
157
208
|
export function isExerciseStepApp(app) {
|
|
158
209
|
return isProblemApp(app) || isSolutionApp(app);
|
|
@@ -196,9 +247,10 @@ export async function init(workshopRoot) {
|
|
|
196
247
|
if (!getEnv().EPICSHOP_DEPLOYED &&
|
|
197
248
|
process.env.EPICSHOP_ENABLE_WATCHER === 'true') {
|
|
198
249
|
const isIgnored = await isGitIgnored({ cwd: getWorkshopRoot() });
|
|
250
|
+
const extraDirName = await getExtraDirName();
|
|
199
251
|
// watch the README, FINISHED, and package.json for changes that affect the apps
|
|
200
252
|
const filesToWatch = ['README.mdx', 'FINISHED.mdx', 'package.json'];
|
|
201
|
-
const chok = chokidar.watch([
|
|
253
|
+
const chok = chokidar.watch([extraDirName, 'playground', 'exercises'], {
|
|
202
254
|
cwd: getWorkshopRoot(),
|
|
203
255
|
// we want to load up the modified times immediately
|
|
204
256
|
ignoreInitial: false,
|
|
@@ -211,8 +263,11 @@ export async function init(workshopRoot) {
|
|
|
211
263
|
if (filePath.endsWith('playground'))
|
|
212
264
|
return false;
|
|
213
265
|
const pathParts = filePath.split(path.sep);
|
|
214
|
-
|
|
266
|
+
const parentDir = pathParts.at(-2);
|
|
267
|
+
if (parentDir &&
|
|
268
|
+
EXTRA_DIR_CANDIDATES.includes(parentDir)) {
|
|
215
269
|
return false;
|
|
270
|
+
}
|
|
216
271
|
// steps
|
|
217
272
|
if (pathParts.at(-3) === 'exercises')
|
|
218
273
|
return false;
|
|
@@ -367,7 +422,7 @@ export const getExercises = requestStorageify(_getExercises);
|
|
|
367
422
|
async function _getApps({ timings, request, } = {}) {
|
|
368
423
|
await init();
|
|
369
424
|
const apps = await time(async () => {
|
|
370
|
-
const [playgroundApp, problemApps, solutionApps,
|
|
425
|
+
const [playgroundApp, problemApps, solutionApps, extraApps] = await Promise.all([
|
|
371
426
|
time(() => getPlaygroundApp({ request, timings }), {
|
|
372
427
|
type: 'getPlaygroundApp',
|
|
373
428
|
timings,
|
|
@@ -380,8 +435,8 @@ async function _getApps({ timings, request, } = {}) {
|
|
|
380
435
|
type: 'getSolutionApps',
|
|
381
436
|
timings,
|
|
382
437
|
}),
|
|
383
|
-
time(() =>
|
|
384
|
-
type: '
|
|
438
|
+
time(() => getExtraApps({ request, timings }), {
|
|
439
|
+
type: 'getExtraApps',
|
|
385
440
|
timings,
|
|
386
441
|
}),
|
|
387
442
|
]);
|
|
@@ -389,7 +444,7 @@ async function _getApps({ timings, request, } = {}) {
|
|
|
389
444
|
playgroundApp,
|
|
390
445
|
...problemApps,
|
|
391
446
|
...solutionApps,
|
|
392
|
-
...
|
|
447
|
+
...extraApps,
|
|
393
448
|
]
|
|
394
449
|
.filter(Boolean)
|
|
395
450
|
.sort((a, b) => {
|
|
@@ -401,13 +456,13 @@ async function _getApps({ timings, request, } = {}) {
|
|
|
401
456
|
}
|
|
402
457
|
if (isPlaygroundApp(b))
|
|
403
458
|
return 1;
|
|
404
|
-
if (
|
|
405
|
-
if (
|
|
459
|
+
if (isExtraApp(a)) {
|
|
460
|
+
if (isExtraApp(b))
|
|
406
461
|
return a.name.localeCompare(b.name);
|
|
407
462
|
else
|
|
408
463
|
return 1;
|
|
409
464
|
}
|
|
410
|
-
if (
|
|
465
|
+
if (isExtraApp(b))
|
|
411
466
|
return -1;
|
|
412
467
|
if (a.type === b.type) {
|
|
413
468
|
if (a.exerciseNumber === b.exerciseNumber) {
|
|
@@ -559,9 +614,9 @@ function getPathname(fullPath) {
|
|
|
559
614
|
function getAppName(fullPath) {
|
|
560
615
|
if (/playground\/?$/.test(fullPath))
|
|
561
616
|
return 'playground';
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
return `
|
|
617
|
+
const extraDirInfo = getExtraDirInfoFromPath(fullPath);
|
|
618
|
+
if (extraDirInfo) {
|
|
619
|
+
return `extra.${extraDirInfo.restOfPath.split('/').join('__sep__')}`;
|
|
565
620
|
}
|
|
566
621
|
const appIdInfo = extractNumbersAndTypeFromAppNameOrPath(fullPath);
|
|
567
622
|
if (appIdInfo) {
|
|
@@ -576,12 +631,11 @@ function getAppName(fullPath) {
|
|
|
576
631
|
export async function getFullPathFromAppName(appName) {
|
|
577
632
|
if (appName === 'playground')
|
|
578
633
|
return path.join(getWorkshopRoot(), 'playground');
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
return path.join(getWorkshopRoot(), 'examples', relativePath);
|
|
634
|
+
const extraRelativePath = parseExtraAppName(appName);
|
|
635
|
+
if (extraRelativePath) {
|
|
636
|
+
const relativePath = extraRelativePath.split('__sep__').join(path.sep);
|
|
637
|
+
const extraDirName = await getExtraDirName();
|
|
638
|
+
return path.join(getWorkshopRoot(), extraDirName, relativePath);
|
|
585
639
|
}
|
|
586
640
|
if (appName.includes('__sep__')) {
|
|
587
641
|
const relativePath = appName.replaceAll('__sep__', path.sep);
|
|
@@ -759,12 +813,12 @@ export async function getPlaygroundApp({ timings, request, } = {}) {
|
|
|
759
813
|
return null;
|
|
760
814
|
});
|
|
761
815
|
}
|
|
762
|
-
async function
|
|
816
|
+
async function getExtraAppFromPath(fullPath, index, request) {
|
|
763
817
|
const dirName = path.basename(fullPath);
|
|
764
818
|
const compiledReadme = await compileMdxIfExists(path.join(fullPath, 'README.mdx'), { request });
|
|
765
819
|
const name = getAppName(fullPath);
|
|
766
820
|
const portNumber = 8000 + index;
|
|
767
|
-
const type = '
|
|
821
|
+
const type = 'extra';
|
|
768
822
|
const title = compiledReadme?.title ?? name;
|
|
769
823
|
return {
|
|
770
824
|
name,
|
|
@@ -784,44 +838,46 @@ async function getExampleAppFromPath(fullPath, index, request) {
|
|
|
784
838
|
}),
|
|
785
839
|
};
|
|
786
840
|
}
|
|
787
|
-
async function
|
|
788
|
-
const
|
|
841
|
+
async function getExtraApps({ timings, request, } = {}) {
|
|
842
|
+
const extraDirInfo = await resolveExtraDir();
|
|
843
|
+
if (!extraDirInfo)
|
|
844
|
+
return [];
|
|
789
845
|
// Filter to only include directories, not files like README.mdx
|
|
790
846
|
const entries = await fs.promises
|
|
791
|
-
.readdir(
|
|
847
|
+
.readdir(extraDirInfo.fullPath, { withFileTypes: true })
|
|
792
848
|
.catch(() => []);
|
|
793
|
-
const
|
|
849
|
+
const extraDirs = entries
|
|
794
850
|
.filter((entry) => {
|
|
795
851
|
if (entry.isDirectory())
|
|
796
852
|
return true;
|
|
797
|
-
log(`Skipping non-directory in
|
|
853
|
+
log(`Skipping non-directory in extras: ${entry.name}`);
|
|
798
854
|
return false;
|
|
799
855
|
})
|
|
800
|
-
.map((entry) => path.join(
|
|
801
|
-
const
|
|
802
|
-
for (const
|
|
803
|
-
const index =
|
|
804
|
-
const key = `${
|
|
805
|
-
const
|
|
856
|
+
.map((entry) => path.join(extraDirInfo.fullPath, entry.name));
|
|
857
|
+
const extraApps = [];
|
|
858
|
+
for (const extraDir of extraDirs) {
|
|
859
|
+
const index = extraDirs.indexOf(extraDir);
|
|
860
|
+
const key = `${extraDir}-${index}`;
|
|
861
|
+
const extraApp = await cachified({
|
|
806
862
|
key,
|
|
807
|
-
cache:
|
|
863
|
+
cache: extraAppCache,
|
|
808
864
|
ttl: 1000 * 60 * 5,
|
|
809
865
|
swr: 1000 * 60 * 60 * 24 * 30,
|
|
810
866
|
timings,
|
|
811
|
-
timingKey:
|
|
867
|
+
timingKey: extraDir.replace(`${extraDirInfo.fullPath}${path.sep}`, ''),
|
|
812
868
|
request,
|
|
813
|
-
forceFresh: await getForceFreshForDir(
|
|
869
|
+
forceFresh: await getForceFreshForDir(extraAppCache.get(key), extraDir),
|
|
814
870
|
getFreshValue: async () => {
|
|
815
|
-
return
|
|
871
|
+
return getExtraAppFromPath(extraDir, index, request).catch((error) => {
|
|
816
872
|
console.error(error);
|
|
817
873
|
return null;
|
|
818
874
|
});
|
|
819
875
|
},
|
|
820
876
|
});
|
|
821
|
-
if (
|
|
822
|
-
|
|
877
|
+
if (extraApp)
|
|
878
|
+
extraApps.push(extraApp);
|
|
823
879
|
}
|
|
824
|
-
return
|
|
880
|
+
return extraApps;
|
|
825
881
|
}
|
|
826
882
|
async function getSolutionAppFromPath(fullPath, request) {
|
|
827
883
|
const dirName = path.basename(fullPath);
|
|
@@ -996,7 +1052,7 @@ export async function getExerciseApp(params, { request, timings } = {}) {
|
|
|
996
1052
|
const { type, exerciseNumber, stepNumber } = result.data;
|
|
997
1053
|
const apps = (await getApps({ request, timings })).filter(isExerciseStepApp);
|
|
998
1054
|
const exerciseApp = apps.find((app) => {
|
|
999
|
-
if (
|
|
1055
|
+
if (isExtraApp(app))
|
|
1000
1056
|
return false;
|
|
1001
1057
|
return (app.exerciseNumber === exerciseNumber &&
|
|
1002
1058
|
app.stepNumber === stepNumber &&
|
|
@@ -1009,7 +1065,24 @@ export async function getExerciseApp(params, { request, timings } = {}) {
|
|
|
1009
1065
|
}
|
|
1010
1066
|
export async function getAppByName(name, { request, timings } = {}) {
|
|
1011
1067
|
const apps = await getApps({ request, timings });
|
|
1012
|
-
|
|
1068
|
+
// First try exact match
|
|
1069
|
+
const exactMatch = apps.find((a) => a.name === name);
|
|
1070
|
+
if (exactMatch)
|
|
1071
|
+
return exactMatch;
|
|
1072
|
+
// For backward compatibility, check if this is a legacy app name
|
|
1073
|
+
// (e.g., example.my-app) and find the corresponding extra app (extra.my-app)
|
|
1074
|
+
const relativePath = parseExtraAppName(name);
|
|
1075
|
+
if (relativePath) {
|
|
1076
|
+
// Try to find an app with the same relative path but different prefix
|
|
1077
|
+
const alternativePrefixes = ['extra.', 'example.'];
|
|
1078
|
+
for (const prefix of alternativePrefixes) {
|
|
1079
|
+
const alternativeName = `${prefix}${relativePath.split('/').join('__sep__')}`;
|
|
1080
|
+
const match = apps.find((a) => a.name === alternativeName);
|
|
1081
|
+
if (match)
|
|
1082
|
+
return match;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
return undefined;
|
|
1013
1086
|
}
|
|
1014
1087
|
export async function getNextExerciseApp(app, { request, timings } = {}) {
|
|
1015
1088
|
const apps = (await getApps({ request, timings })).filter(isExerciseStepApp);
|
|
@@ -1271,8 +1344,8 @@ export function getAppDisplayName(a, allApps) {
|
|
|
1271
1344
|
displayName = `🛝 ${a.appName}`;
|
|
1272
1345
|
}
|
|
1273
1346
|
}
|
|
1274
|
-
else if (
|
|
1275
|
-
displayName = `📚 ${a.title} (
|
|
1347
|
+
else if (isExtraApp(a)) {
|
|
1348
|
+
displayName = `📚 ${a.title} (extra)`;
|
|
1276
1349
|
}
|
|
1277
1350
|
return displayName;
|
|
1278
1351
|
}
|
|
@@ -1284,16 +1357,18 @@ export async function getWorkshopInstructions({ request, } = {}) {
|
|
|
1284
1357
|
});
|
|
1285
1358
|
return { compiled, file: readmeFilepath, relativePath: 'exercises' };
|
|
1286
1359
|
}
|
|
1287
|
-
export async function
|
|
1288
|
-
const
|
|
1360
|
+
export async function getExtrasInstructions({ request, } = {}) {
|
|
1361
|
+
const extraDirInfo = await resolveExtraDir();
|
|
1362
|
+
const dirName = extraDirInfo?.dirName ?? EXTRA_DIRNAME;
|
|
1363
|
+
const readmeFilepath = path.join(getWorkshopRoot(), dirName, 'README.mdx');
|
|
1289
1364
|
const compiled = await compileMdx(readmeFilepath, { request }).then((r) => ({ ...r, status: 'success' }), (e) => {
|
|
1290
|
-
console.error(`There was an error compiling the
|
|
1365
|
+
console.error(`There was an error compiling the extras README.mdx`, readmeFilepath, e);
|
|
1291
1366
|
return { status: 'error', error: getErrorMessage(e) };
|
|
1292
1367
|
});
|
|
1293
1368
|
return {
|
|
1294
1369
|
compiled,
|
|
1295
1370
|
file: readmeFilepath,
|
|
1296
|
-
relativePath:
|
|
1371
|
+
relativePath: `${dirName}/README.mdx`,
|
|
1297
1372
|
};
|
|
1298
1373
|
}
|
|
1299
1374
|
export async function getWorkshopFinished({ request, } = {}) {
|
|
@@ -1324,16 +1399,27 @@ export function getAppPathFromFilePath(filePath) {
|
|
|
1324
1399
|
if (!withinWorkshopRootHalf) {
|
|
1325
1400
|
return null;
|
|
1326
1401
|
}
|
|
1327
|
-
const
|
|
1328
|
-
|
|
1329
|
-
|
|
1402
|
+
const pathParts = withinWorkshopRootHalf.split(path.sep).filter(Boolean);
|
|
1403
|
+
const part1 = pathParts[0] ?? '';
|
|
1404
|
+
const part2 = pathParts[1] ?? '';
|
|
1405
|
+
const part3 = pathParts[2] ?? '';
|
|
1330
1406
|
// Check if the file is in the playground
|
|
1331
1407
|
if (part1 === 'playground') {
|
|
1332
1408
|
return path.join(getWorkshopRoot(), 'playground');
|
|
1333
1409
|
}
|
|
1334
|
-
// Check if the file is in an
|
|
1335
|
-
if (part1 ===
|
|
1336
|
-
|
|
1410
|
+
// Check if the file is in an extra app (or legacy examples)
|
|
1411
|
+
if (part1 === EXTRA_DIRNAME ||
|
|
1412
|
+
part1 === LEGACY_EXAMPLE_DIRNAME ||
|
|
1413
|
+
part1 === LEGACY_EXAMPLES_DIRNAME) {
|
|
1414
|
+
if (!part2)
|
|
1415
|
+
return null;
|
|
1416
|
+
invariant(part2.length > 0, 'Expected extra app directory name');
|
|
1417
|
+
const extraRoot = part1 === EXTRA_DIRNAME
|
|
1418
|
+
? EXTRA_DIRNAME
|
|
1419
|
+
: part1 === LEGACY_EXAMPLE_DIRNAME
|
|
1420
|
+
? LEGACY_EXAMPLE_DIRNAME
|
|
1421
|
+
: LEGACY_EXAMPLES_DIRNAME;
|
|
1422
|
+
return path.join(getWorkshopRoot(), extraRoot, part2);
|
|
1337
1423
|
}
|
|
1338
1424
|
// Check if the file is in an exercise
|
|
1339
1425
|
if (part1 === 'exercises' && part2 && part3) {
|
package/dist/cache.server.d.ts
CHANGED
|
@@ -85,7 +85,7 @@ export declare const problemAppCache: C.Cache<{
|
|
|
85
85
|
instructionsCode?: string | undefined;
|
|
86
86
|
epicVideoEmbeds?: string[] | undefined;
|
|
87
87
|
}>;
|
|
88
|
-
export declare const
|
|
88
|
+
export declare const extraAppCache: C.Cache<{
|
|
89
89
|
test: {
|
|
90
90
|
type: "browser";
|
|
91
91
|
pathname: string;
|
|
@@ -96,7 +96,7 @@ export declare const exampleAppCache: C.Cache<{
|
|
|
96
96
|
} | {
|
|
97
97
|
type: "none";
|
|
98
98
|
};
|
|
99
|
-
type: "
|
|
99
|
+
type: "extra";
|
|
100
100
|
title: string;
|
|
101
101
|
name: string;
|
|
102
102
|
fullPath: string;
|
package/dist/cache.server.js
CHANGED
|
@@ -164,7 +164,7 @@ export function epicCacheReporter({ formatDuration = defaultFormatDuration, perf
|
|
|
164
164
|
}
|
|
165
165
|
export const solutionAppCache = makeSingletonFsCache('SolutionAppCache');
|
|
166
166
|
export const problemAppCache = makeSingletonFsCache('ProblemAppCache');
|
|
167
|
-
export const
|
|
167
|
+
export const extraAppCache = makeSingletonFsCache('ExtraAppCache');
|
|
168
168
|
export const playgroundAppCache = makeSingletonFsCache('PlaygroundAppCache');
|
|
169
169
|
export const diffCodeCache = makeSingletonFsCache('DiffCodeCache');
|
|
170
170
|
export const diffFilesCache = makeSingletonFsCache('DiffFilesCache');
|