Skip to content

Commit e20869c

Browse files
fix: Missing playground app error (#552)
Co-authored-by: me <me@kentcdodds.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent 371d811 commit e20869c

2 files changed

Lines changed: 93 additions & 13 deletions

File tree

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import fs from 'node:fs/promises'
2+
import os from 'node:os'
3+
import path from 'node:path'
4+
import { afterEach, expect, test, vi } from 'vitest'
5+
6+
const originalContext = process.env.EPICSHOP_CONTEXT_CWD
7+
8+
async function createTempWorkshop() {
9+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'epicshop-playground-'))
10+
await fs.writeFile(
11+
path.join(root, 'package.json'),
12+
JSON.stringify(
13+
{
14+
name: 'epicshop-playground-test',
15+
epicshop: {
16+
title: 'Playground Test',
17+
githubRepo: 'https://github.com/example/workshop',
18+
},
19+
},
20+
null,
21+
2,
22+
),
23+
)
24+
await fs.mkdir(path.join(root, 'playground'), { recursive: true })
25+
const cacheDir = path.join(root, 'node_modules', '.cache', 'epicshop')
26+
await fs.mkdir(cacheDir, { recursive: true })
27+
await fs.writeFile(
28+
path.join(cacheDir, 'playground.json'),
29+
JSON.stringify({ appName: '05.02.problem' }, null, 2),
30+
)
31+
32+
return {
33+
root,
34+
async [Symbol.asyncDispose]() {
35+
await fs.rm(root, { recursive: true, force: true })
36+
},
37+
}
38+
}
39+
40+
afterEach(() => {
41+
if (originalContext) {
42+
process.env.EPICSHOP_CONTEXT_CWD = originalContext
43+
} else {
44+
delete process.env.EPICSHOP_CONTEXT_CWD
45+
}
46+
})
47+
48+
test('returns playground info when base app is missing', async () => {
49+
await using workshop = await createTempWorkshop()
50+
51+
process.env.EPICSHOP_CONTEXT_CWD = workshop.root
52+
;(
53+
globalThis as { __epicshop_apps_initialized__?: boolean }
54+
).__epicshop_apps_initialized__ = false
55+
vi.resetModules()
56+
57+
const { getPlaygroundApp, setWorkshopRoot } = await import('./apps.server.ts')
58+
setWorkshopRoot(workshop.root)
59+
60+
const playgroundApp = await getPlaygroundApp()
61+
expect(playgroundApp).not.toBeNull()
62+
expect(playgroundApp?.appName).toBe('05.02.problem')
63+
expect(playgroundApp?.isUpToDate).toBe(false)
64+
}, 15000)

packages/workshop-utils/src/apps.server.ts

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,13 @@ export async function getFullPathFromAppName(appName: string) {
806806
return dir ?? appName
807807
}
808808

809+
async function resolveExistingAppPath(appName: string) {
810+
const fullPath = await getFullPathFromAppName(appName)
811+
if (!path.isAbsolute(fullPath)) return null
812+
if (!(await exists(fullPath))) return null
813+
return fullPath
814+
}
815+
809816
export async function findSolutionDir({
810817
fullPath,
811818
}: {
@@ -827,10 +834,11 @@ export async function findSolutionDir({
827834
}
828835
} else if (fullPath.endsWith('playground')) {
829836
const appName = await getPlaygroundAppName()
830-
if (appName) {
831-
return findSolutionDir({
832-
fullPath: await getFullPathFromAppName(appName),
833-
})
837+
const baseAppFullPath = appName
838+
? await resolveExistingAppPath(appName)
839+
: null
840+
if (baseAppFullPath) {
841+
return findSolutionDir({ fullPath: baseAppFullPath })
834842
}
835843
}
836844
return null
@@ -857,8 +865,11 @@ export async function findProblemDir({
857865
}
858866
} else if (fullPath.endsWith('playground')) {
859867
const appName = await getPlaygroundAppName()
860-
if (appName) {
861-
return findProblemDir({ fullPath: await getFullPathFromAppName(appName) })
868+
const baseAppFullPath = appName
869+
? await resolveExistingAppPath(appName)
870+
: null
871+
if (baseAppFullPath) {
872+
return findProblemDir({ fullPath: baseAppFullPath })
862873
}
863874
}
864875
return null
@@ -944,11 +955,14 @@ export async function getPlaygroundApp({
944955
}: CachifiedOptions = {}): Promise<PlaygroundApp | null> {
945956
const playgroundDir = path.join(getWorkshopRoot(), 'playground')
946957
const baseAppName = await getPlaygroundAppName()
947-
const key = `playground-${baseAppName}`
948-
949958
const baseAppFullPath = baseAppName
950-
? await getFullPathFromAppName(baseAppName)
959+
? await resolveExistingAppPath(baseAppName)
951960
: null
961+
if (baseAppName && !baseAppFullPath) {
962+
log.warn(`Playground base app missing: ${baseAppName}`)
963+
}
964+
const key = `playground-${baseAppName}`
965+
952966
const playgroundCacheEntry = await playgroundAppCache.get(key)
953967
return cachified({
954968
key,
@@ -978,18 +992,20 @@ export async function getPlaygroundApp({
978992
getDevInfo({ fullPath: playgroundDir, portNumber }),
979993
])
980994

981-
const appModifiedTime = await getDirModifiedTime(
982-
await getFullPathFromAppName(baseAppName),
983-
)
984995
const playgroundAppModifiedTime = await getDirModifiedTime(playgroundDir)
996+
const appModifiedTime = baseAppFullPath
997+
? await getDirModifiedTime(baseAppFullPath)
998+
: -1
985999
const type = 'playground'
9861000

9871001
const title = compiledReadme?.title ?? name
9881002
return {
9891003
name,
9901004
appName: baseAppName,
9911005
type,
992-
isUpToDate: appModifiedTime <= playgroundAppModifiedTime,
1006+
isUpToDate: baseAppFullPath
1007+
? appModifiedTime <= playgroundAppModifiedTime
1008+
: false,
9931009
fullPath: playgroundDir,
9941010
relativePath: playgroundDir.replace(
9951011
`${getWorkshopRoot()}${path.sep}`,

0 commit comments

Comments
 (0)