@remix-run/cli 0.0.0 → 0.1.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.
Files changed (155) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -3
  3. package/bootstrap/AGENTS.md +35 -0
  4. package/bootstrap/README.md +27 -0
  5. package/bootstrap/app/controllers/auth.tsx +21 -0
  6. package/bootstrap/app/controllers/home.tsx +26 -0
  7. package/bootstrap/app/router.ts +10 -0
  8. package/bootstrap/app/routes.ts +6 -0
  9. package/bootstrap/app/ui/document.tsx +21 -0
  10. package/bootstrap/app/ui/layout.tsx +22 -0
  11. package/bootstrap/app/utils/render.tsx +7 -0
  12. package/bootstrap/package.json +22 -0
  13. package/bootstrap/server.ts +37 -0
  14. package/bootstrap/tsconfig.json +19 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +1 -0
  18. package/dist/lib/bootstrap-project.d.ts +17 -0
  19. package/dist/lib/bootstrap-project.d.ts.map +1 -0
  20. package/dist/lib/bootstrap-project.js +137 -0
  21. package/dist/lib/cli-context.d.ts +11 -0
  22. package/dist/lib/cli-context.d.ts.map +1 -0
  23. package/dist/lib/cli-context.js +19 -0
  24. package/dist/lib/cli.d.ts +6 -0
  25. package/dist/lib/cli.d.ts.map +1 -0
  26. package/dist/lib/cli.js +87 -0
  27. package/dist/lib/commands/completion.d.ts +3 -0
  28. package/dist/lib/commands/completion.d.ts.map +1 -0
  29. package/dist/lib/commands/completion.js +51 -0
  30. package/dist/lib/commands/doctor.d.ts +4 -0
  31. package/dist/lib/commands/doctor.d.ts.map +1 -0
  32. package/dist/lib/commands/doctor.js +293 -0
  33. package/dist/lib/commands/help.d.ts +4 -0
  34. package/dist/lib/commands/help.d.ts.map +1 -0
  35. package/dist/lib/commands/help.js +124 -0
  36. package/dist/lib/commands/new.d.ts +4 -0
  37. package/dist/lib/commands/new.d.ts.map +1 -0
  38. package/dist/lib/commands/new.js +70 -0
  39. package/dist/lib/commands/routes.d.ts +4 -0
  40. package/dist/lib/commands/routes.d.ts.map +1 -0
  41. package/dist/lib/commands/routes.js +161 -0
  42. package/dist/lib/commands/skills.d.ts +6 -0
  43. package/dist/lib/commands/skills.d.ts.map +1 -0
  44. package/dist/lib/commands/skills.js +222 -0
  45. package/dist/lib/commands/test.d.ts +4 -0
  46. package/dist/lib/commands/test.d.ts.map +1 -0
  47. package/dist/lib/commands/test.js +19 -0
  48. package/dist/lib/commands/version.d.ts +4 -0
  49. package/dist/lib/commands/version.d.ts.map +1 -0
  50. package/dist/lib/commands/version.js +29 -0
  51. package/dist/lib/completion.d.ts +12 -0
  52. package/dist/lib/completion.d.ts.map +1 -0
  53. package/dist/lib/completion.js +408 -0
  54. package/dist/lib/contained-path.d.ts +2 -0
  55. package/dist/lib/contained-path.d.ts.map +1 -0
  56. package/dist/lib/contained-path.js +10 -0
  57. package/dist/lib/controller-files.d.ts +14 -0
  58. package/dist/lib/controller-files.d.ts.map +1 -0
  59. package/dist/lib/controller-files.js +53 -0
  60. package/dist/lib/controller-ownership.d.ts +41 -0
  61. package/dist/lib/controller-ownership.d.ts.map +1 -0
  62. package/dist/lib/controller-ownership.js +224 -0
  63. package/dist/lib/display-path.d.ts +2 -0
  64. package/dist/lib/display-path.d.ts.map +1 -0
  65. package/dist/lib/display-path.js +12 -0
  66. package/dist/lib/doctor/controller-findings.d.ts +4 -0
  67. package/dist/lib/doctor/controller-findings.d.ts.map +1 -0
  68. package/dist/lib/doctor/controller-findings.js +132 -0
  69. package/dist/lib/doctor/controller-fix-plans.d.ts +4 -0
  70. package/dist/lib/doctor/controller-fix-plans.d.ts.map +1 -0
  71. package/dist/lib/doctor/controller-fix-plans.js +130 -0
  72. package/dist/lib/doctor/controller-placeholders.d.ts +4 -0
  73. package/dist/lib/doctor/controller-placeholders.d.ts.map +1 -0
  74. package/dist/lib/doctor/controller-placeholders.js +183 -0
  75. package/dist/lib/doctor/controllers.d.ts +8 -0
  76. package/dist/lib/doctor/controllers.d.ts.map +1 -0
  77. package/dist/lib/doctor/controllers.js +13 -0
  78. package/dist/lib/doctor/environment.d.ts +17 -0
  79. package/dist/lib/doctor/environment.d.ts.map +1 -0
  80. package/dist/lib/doctor/environment.js +226 -0
  81. package/dist/lib/doctor/fixes.d.ts +3 -0
  82. package/dist/lib/doctor/fixes.d.ts.map +1 -0
  83. package/dist/lib/doctor/fixes.js +65 -0
  84. package/dist/lib/doctor/project.d.ts +10 -0
  85. package/dist/lib/doctor/project.d.ts.map +1 -0
  86. package/dist/lib/doctor/project.js +371 -0
  87. package/dist/lib/doctor/types.d.ts +48 -0
  88. package/dist/lib/doctor/types.d.ts.map +1 -0
  89. package/dist/lib/doctor/types.js +15 -0
  90. package/dist/lib/errors.d.ts +173 -0
  91. package/dist/lib/errors.d.ts.map +1 -0
  92. package/dist/lib/errors.js +318 -0
  93. package/dist/lib/help-text.d.ts +13 -0
  94. package/dist/lib/help-text.d.ts.map +1 -0
  95. package/dist/lib/help-text.js +43 -0
  96. package/dist/lib/load-route-map-worker.d.ts +2 -0
  97. package/dist/lib/load-route-map-worker.d.ts.map +1 -0
  98. package/dist/lib/load-route-map-worker.js +92 -0
  99. package/dist/lib/parse-args.d.ts +22 -0
  100. package/dist/lib/parse-args.d.ts.map +1 -0
  101. package/dist/lib/parse-args.js +35 -0
  102. package/dist/lib/remix-version.d.ts +2 -0
  103. package/dist/lib/remix-version.d.ts.map +1 -0
  104. package/dist/lib/remix-version.js +58 -0
  105. package/dist/lib/reporter.d.ts +56 -0
  106. package/dist/lib/reporter.d.ts.map +1 -0
  107. package/dist/lib/reporter.js +357 -0
  108. package/dist/lib/route-map.d.ts +38 -0
  109. package/dist/lib/route-map.d.ts.map +1 -0
  110. package/dist/lib/route-map.js +195 -0
  111. package/dist/lib/skills-cache.d.ts +19 -0
  112. package/dist/lib/skills-cache.d.ts.map +1 -0
  113. package/dist/lib/skills-cache.js +89 -0
  114. package/dist/lib/skills.d.ts +30 -0
  115. package/dist/lib/skills.d.ts.map +1 -0
  116. package/dist/lib/skills.js +441 -0
  117. package/dist/lib/terminal.d.ts +17 -0
  118. package/dist/lib/terminal.d.ts.map +1 -0
  119. package/dist/lib/terminal.js +66 -0
  120. package/package.json +48 -5
  121. package/src/index.ts +1 -0
  122. package/src/lib/bootstrap-project.ts +222 -0
  123. package/src/lib/cli-context.ts +38 -0
  124. package/src/lib/cli.ts +120 -0
  125. package/src/lib/commands/completion.ts +74 -0
  126. package/src/lib/commands/doctor.ts +393 -0
  127. package/src/lib/commands/help.ts +157 -0
  128. package/src/lib/commands/new.ts +104 -0
  129. package/src/lib/commands/routes.ts +251 -0
  130. package/src/lib/commands/skills.ts +306 -0
  131. package/src/lib/commands/test.ts +25 -0
  132. package/src/lib/commands/version.ts +39 -0
  133. package/src/lib/completion.ts +561 -0
  134. package/src/lib/contained-path.ts +13 -0
  135. package/src/lib/controller-files.ts +79 -0
  136. package/src/lib/controller-ownership.ts +397 -0
  137. package/src/lib/display-path.ts +16 -0
  138. package/src/lib/doctor/controller-findings.ts +157 -0
  139. package/src/lib/doctor/controller-fix-plans.ts +184 -0
  140. package/src/lib/doctor/controller-placeholders.ts +242 -0
  141. package/src/lib/doctor/controllers.ts +23 -0
  142. package/src/lib/doctor/environment.ts +289 -0
  143. package/src/lib/doctor/fixes.ts +84 -0
  144. package/src/lib/doctor/project.ts +435 -0
  145. package/src/lib/doctor/types.ts +95 -0
  146. package/src/lib/errors.ts +379 -0
  147. package/src/lib/help-text.ts +88 -0
  148. package/src/lib/load-route-map-worker.ts +110 -0
  149. package/src/lib/parse-args.ts +82 -0
  150. package/src/lib/remix-version.ts +80 -0
  151. package/src/lib/reporter.ts +535 -0
  152. package/src/lib/route-map.ts +303 -0
  153. package/src/lib/skills-cache.ts +140 -0
  154. package/src/lib/skills.ts +706 -0
  155. package/src/lib/terminal.ts +86 -0
@@ -0,0 +1,303 @@
1
+ import * as fs from 'node:fs/promises'
2
+ import * as path from 'node:path'
3
+ import * as process from 'node:process'
4
+ import { spawn } from 'node:child_process'
5
+ import { fileURLToPath } from 'node:url'
6
+
7
+ import { inspectControllerOwnership, type OwnedSubtree } from './controller-ownership.ts'
8
+ import {
9
+ routeMapLoaderFailed,
10
+ routeMapLoaderInvalidJson,
11
+ routeMapLoaderSignal,
12
+ routeOwnerPlanUnresolved,
13
+ routesFileNotFound,
14
+ } from './errors.ts'
15
+
16
+ export type RouteOwnerKind = 'action' | 'controller'
17
+ export type RouteTreeNodeKind = 'group' | 'route'
18
+
19
+ export interface RouteTreeOwner {
20
+ exists: boolean
21
+ kind: RouteOwnerKind
22
+ path: string
23
+ }
24
+
25
+ export interface RouteTreeNode {
26
+ children: RouteTreeNode[]
27
+ key: string
28
+ kind: RouteTreeNodeKind
29
+ method?: string
30
+ name: string
31
+ owner: RouteTreeOwner
32
+ pattern?: string
33
+ }
34
+
35
+ export interface LoadedRouteMap {
36
+ appRoot: string
37
+ routesFile: string
38
+ tree: RouteTreeNode[]
39
+ }
40
+
41
+ export interface LoadedRouteManifest {
42
+ appRoot: string
43
+ routesFile: string
44
+ tree: RawRouteTreeNode[]
45
+ }
46
+
47
+ export interface RawRouteTreeNode {
48
+ children: RawRouteTreeNode[]
49
+ key: string
50
+ kind: RouteTreeNodeKind
51
+ method?: string
52
+ name: string
53
+ pattern?: string
54
+ }
55
+
56
+ export async function loadRouteMap(cwd: string = process.cwd()): Promise<LoadedRouteMap> {
57
+ let manifest = await loadRouteManifest(cwd)
58
+ let ownership = await inspectControllerOwnership(manifest.appRoot, manifest.tree)
59
+ let tree = decorateRouteTree(manifest.tree, ownership.subtrees)
60
+
61
+ return {
62
+ appRoot: manifest.appRoot,
63
+ routesFile: manifest.routesFile,
64
+ tree,
65
+ }
66
+ }
67
+
68
+ export async function loadRouteManifest(cwd: string = process.cwd()): Promise<LoadedRouteManifest> {
69
+ let appRoot = await findRemixAppRoot(cwd)
70
+ return loadRouteManifestFromAppRoot(appRoot)
71
+ }
72
+
73
+ export async function loadRouteManifestFromAppRoot(appRoot: string): Promise<LoadedRouteManifest> {
74
+ let routesFile = path.join(appRoot, 'app', 'routes.ts')
75
+ let tree = await loadRawRouteMap(appRoot, routesFile)
76
+
77
+ return {
78
+ appRoot,
79
+ routesFile,
80
+ tree,
81
+ }
82
+ }
83
+
84
+ async function loadRawRouteMap(appRoot: string, routesFile: string): Promise<RawRouteTreeNode[]> {
85
+ let workerPath = getRouteMapWorkerPath()
86
+ let child = spawn(process.execPath, [workerPath, routesFile], {
87
+ cwd: appRoot,
88
+ env: createRouteMapWorkerEnv(),
89
+ stdio: ['ignore', 'pipe', 'pipe'],
90
+ })
91
+
92
+ let stdout = ''
93
+ let stderr = ''
94
+
95
+ child.stdout.setEncoding('utf8')
96
+ child.stderr.setEncoding('utf8')
97
+
98
+ child.stdout.on('data', (chunk: string) => {
99
+ stdout += chunk
100
+ })
101
+
102
+ child.stderr.on('data', (chunk: string) => {
103
+ stderr += chunk
104
+ })
105
+
106
+ let exitResult = await new Promise<{ code: number | null; signal: NodeJS.Signals | null }>(
107
+ (resolve, reject) => {
108
+ child.once('error', reject)
109
+ child.once('close', (code, signal) => {
110
+ resolve({ code, signal })
111
+ })
112
+ },
113
+ )
114
+
115
+ if (exitResult.signal != null) {
116
+ throw routeMapLoaderSignal(exitResult.signal)
117
+ }
118
+
119
+ if (exitResult.code !== 0) {
120
+ let message = stderr.trim()
121
+ if (message.length === 0) {
122
+ message = 'Route-map loader failed.'
123
+ }
124
+
125
+ throw routeMapLoaderFailed(message)
126
+ }
127
+
128
+ let parsed: unknown
129
+ try {
130
+ parsed = JSON.parse(stdout)
131
+ } catch {
132
+ throw routeMapLoaderInvalidJson()
133
+ }
134
+
135
+ return assertRawRouteTree(parsed)
136
+ }
137
+
138
+ function decorateRouteTree(
139
+ rawTree: RawRouteTreeNode[],
140
+ subtrees: OwnedSubtree[],
141
+ parentSegments: string[] = [],
142
+ ): RouteTreeNode[] {
143
+ let subtreesByRouteName = new Map(subtrees.map((subtree) => [subtree.routeName, subtree]))
144
+
145
+ return decorateRouteTreeWithLookup(rawTree, subtreesByRouteName, parentSegments)
146
+ }
147
+
148
+ function decorateRouteTreeWithLookup(
149
+ rawTree: RawRouteTreeNode[],
150
+ subtreesByRouteName: Map<string, OwnedSubtree>,
151
+ parentSegments: string[] = [],
152
+ ): RouteTreeNode[] {
153
+ return rawTree.map((rawNode) => {
154
+ let owner = getRouteOwner(rawNode, parentSegments, subtreesByRouteName)
155
+ let nextParentSegments =
156
+ rawNode.kind === 'group' ? [...parentSegments, rawNode.key] : parentSegments
157
+
158
+ return {
159
+ children:
160
+ rawNode.kind === 'group'
161
+ ? decorateRouteTreeWithLookup(rawNode.children, subtreesByRouteName, nextParentSegments)
162
+ : [],
163
+ key: rawNode.key,
164
+ kind: rawNode.kind,
165
+ method: rawNode.method,
166
+ name: rawNode.name,
167
+ owner,
168
+ pattern: rawNode.pattern,
169
+ } satisfies RouteTreeNode
170
+ })
171
+ }
172
+
173
+ function getRouteOwner(
174
+ rawNode: RawRouteTreeNode,
175
+ parentSegments: string[],
176
+ subtreesByRouteName: Map<string, OwnedSubtree>,
177
+ ): RouteTreeOwner {
178
+ let ownerRouteName =
179
+ rawNode.kind === 'group'
180
+ ? rawNode.name
181
+ : parentSegments.length === 0
182
+ ? rawNode.name
183
+ : parentSegments.join('.')
184
+ let subtree = subtreesByRouteName.get(ownerRouteName)
185
+
186
+ if (subtree == null) {
187
+ throw routeOwnerPlanUnresolved(rawNode.name)
188
+ }
189
+
190
+ return {
191
+ exists: subtree.actualEntryPath != null,
192
+ kind: subtree.kind,
193
+ path: subtree.actualEntryPath ?? subtree.entryDisplayPath,
194
+ }
195
+ }
196
+
197
+ function assertRawRouteTree(value: unknown): RawRouteTreeNode[] {
198
+ if (!Array.isArray(value)) {
199
+ throw new Error('Route-map loader returned an invalid tree.')
200
+ }
201
+
202
+ return value.map((entry) => assertRawRouteTreeNode(entry))
203
+ }
204
+
205
+ function assertRawRouteTreeNode(value: unknown): RawRouteTreeNode {
206
+ if (typeof value !== 'object' || value == null) {
207
+ throw new Error('Route-map loader returned an invalid route node.')
208
+ }
209
+
210
+ let key = Reflect.get(value, 'key')
211
+ let name = Reflect.get(value, 'name')
212
+ let kind = Reflect.get(value, 'kind')
213
+ let children = Reflect.get(value, 'children')
214
+
215
+ if (typeof key !== 'string' || typeof name !== 'string') {
216
+ throw new Error('Route-map loader returned a route node without a valid name.')
217
+ }
218
+
219
+ if (kind !== 'group' && kind !== 'route') {
220
+ throw new Error(`Route-map loader returned an unknown node kind for "${name}".`)
221
+ }
222
+
223
+ if (!Array.isArray(children)) {
224
+ throw new Error(`Route-map loader returned invalid children for "${name}".`)
225
+ }
226
+
227
+ if (kind === 'group') {
228
+ return {
229
+ children: children.map((child) => assertRawRouteTreeNode(child)),
230
+ key,
231
+ kind,
232
+ name,
233
+ }
234
+ }
235
+
236
+ let method = Reflect.get(value, 'method')
237
+ let pattern = Reflect.get(value, 'pattern')
238
+
239
+ if (typeof method !== 'string' || typeof pattern !== 'string') {
240
+ throw new Error(`Route-map loader returned an invalid route leaf for "${name}".`)
241
+ }
242
+
243
+ return {
244
+ children: [],
245
+ key,
246
+ kind,
247
+ method,
248
+ name,
249
+ pattern,
250
+ }
251
+ }
252
+
253
+ async function findRemixAppRoot(startDir: string): Promise<string> {
254
+ let currentDir = path.resolve(startDir)
255
+
256
+ while (true) {
257
+ if (await pathExists(path.join(currentDir, 'app', 'routes.ts'))) {
258
+ return currentDir
259
+ }
260
+
261
+ let parentDir = path.dirname(currentDir)
262
+ if (parentDir === currentDir) {
263
+ break
264
+ }
265
+
266
+ currentDir = parentDir
267
+ }
268
+
269
+ throw routesFileNotFound(startDir)
270
+ }
271
+
272
+ async function pathExists(filePath: string): Promise<boolean> {
273
+ try {
274
+ await fs.access(filePath)
275
+ return true
276
+ } catch (error) {
277
+ let nodeError = error as NodeJS.ErrnoException
278
+ if (nodeError.code === 'ENOENT') {
279
+ return false
280
+ }
281
+
282
+ throw error
283
+ }
284
+ }
285
+
286
+ function getRouteMapWorkerPath(): string {
287
+ let currentFilePath = fileURLToPath(import.meta.url)
288
+ let extension = currentFilePath.endsWith('.ts') ? '.ts' : '.js'
289
+
290
+ return fileURLToPath(new URL(`./load-route-map-worker${extension}`, import.meta.url))
291
+ }
292
+
293
+ function createRouteMapWorkerEnv(): NodeJS.ProcessEnv {
294
+ let env = { ...process.env }
295
+
296
+ for (let key of Object.keys(env)) {
297
+ if (key.startsWith('NODE_TEST_')) {
298
+ delete env[key]
299
+ }
300
+ }
301
+
302
+ return env
303
+ }
@@ -0,0 +1,140 @@
1
+ import * as crypto from 'node:crypto'
2
+ import * as fs from 'node:fs/promises'
3
+ import * as os from 'node:os'
4
+ import * as path from 'node:path'
5
+ import * as process from 'node:process'
6
+
7
+ const REMIX_SKILLS_CACHE_VERSION = 1
8
+ const REMIX_SKILLS_CACHE_REPO = 'remix-run/remix'
9
+ const REMIX_SKILLS_CACHE_REF = 'main'
10
+
11
+ export interface SkillsCacheFileEntry {
12
+ localHash: string
13
+ remoteSha: string
14
+ }
15
+
16
+ export interface SkillsCacheSkillEntry {
17
+ files: Record<string, SkillsCacheFileEntry>
18
+ }
19
+
20
+ export interface SkillsCacheManifest {
21
+ ref: string
22
+ repo: string
23
+ skills: Record<string, SkillsCacheSkillEntry>
24
+ skillsDir: string
25
+ version: number
26
+ }
27
+
28
+ export function createSkillsCacheManifest(
29
+ skillsDir: string,
30
+ skills: Record<string, SkillsCacheSkillEntry>,
31
+ ): SkillsCacheManifest {
32
+ return {
33
+ ref: REMIX_SKILLS_CACHE_REF,
34
+ repo: REMIX_SKILLS_CACHE_REPO,
35
+ skills,
36
+ skillsDir: path.resolve(skillsDir),
37
+ version: REMIX_SKILLS_CACHE_VERSION,
38
+ }
39
+ }
40
+
41
+ export function getSkillsCacheFilePath(skillsDir: string): string {
42
+ let cacheKey = crypto.createHash('sha256').update(path.resolve(skillsDir)).digest('hex')
43
+ return path.join(getSkillsCacheDir(), `${cacheKey}.json`)
44
+ }
45
+
46
+ export async function readSkillsCache(skillsDir: string): Promise<SkillsCacheManifest | null> {
47
+ try {
48
+ let filePath = getSkillsCacheFilePath(skillsDir)
49
+ let raw = JSON.parse(await fs.readFile(filePath, 'utf8')) as unknown
50
+ return isSkillsCacheManifest(raw, skillsDir) ? raw : null
51
+ } catch (error) {
52
+ if (error instanceof SyntaxError) {
53
+ return null
54
+ }
55
+
56
+ let nodeError = error as NodeJS.ErrnoException
57
+ if (nodeError.code === 'ENOENT') {
58
+ return null
59
+ }
60
+
61
+ throw error
62
+ }
63
+ }
64
+
65
+ export async function writeSkillsCache(
66
+ skillsDir: string,
67
+ manifest: SkillsCacheManifest,
68
+ ): Promise<void> {
69
+ let filePath = getSkillsCacheFilePath(skillsDir)
70
+ let tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`
71
+
72
+ await fs.mkdir(path.dirname(filePath), { recursive: true })
73
+ await fs.writeFile(tempPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8')
74
+ await fs.rename(tempPath, filePath)
75
+ }
76
+
77
+ function getSkillsCacheDir(): string {
78
+ if (process.platform === 'win32') {
79
+ let localAppData = process.env.LOCALAPPDATA ?? path.join(os.homedir(), 'AppData', 'Local')
80
+ return path.join(localAppData, 'remix', 'Cache', 'skills')
81
+ }
82
+
83
+ if (process.platform === 'darwin') {
84
+ return path.join(
85
+ process.env.HOME ?? os.homedir(),
86
+ 'Library',
87
+ 'Caches',
88
+ 'remix',
89
+ 'cli',
90
+ 'skills',
91
+ )
92
+ }
93
+
94
+ let xdgCacheHome = process.env.XDG_CACHE_HOME ?? path.join(os.homedir(), '.cache')
95
+ return path.join(xdgCacheHome, 'remix', 'cli', 'skills')
96
+ }
97
+
98
+ function isSkillsCacheManifest(value: unknown, skillsDir: string): value is SkillsCacheManifest {
99
+ if (typeof value !== 'object' || value == null) {
100
+ return false
101
+ }
102
+
103
+ if (
104
+ Reflect.get(value, 'version') !== REMIX_SKILLS_CACHE_VERSION ||
105
+ Reflect.get(value, 'repo') !== REMIX_SKILLS_CACHE_REPO ||
106
+ Reflect.get(value, 'ref') !== REMIX_SKILLS_CACHE_REF ||
107
+ Reflect.get(value, 'skillsDir') !== path.resolve(skillsDir)
108
+ ) {
109
+ return false
110
+ }
111
+
112
+ let skills = Reflect.get(value, 'skills')
113
+ if (typeof skills !== 'object' || skills == null) {
114
+ return false
115
+ }
116
+
117
+ for (let skillEntry of Object.values(skills)) {
118
+ if (typeof skillEntry !== 'object' || skillEntry == null) {
119
+ return false
120
+ }
121
+
122
+ let files = Reflect.get(skillEntry, 'files')
123
+ if (typeof files !== 'object' || files == null) {
124
+ return false
125
+ }
126
+
127
+ for (let fileEntry of Object.values(files as object)) {
128
+ if (
129
+ typeof fileEntry !== 'object' ||
130
+ fileEntry == null ||
131
+ typeof Reflect.get(fileEntry, 'localHash') !== 'string' ||
132
+ typeof Reflect.get(fileEntry, 'remoteSha') !== 'string'
133
+ ) {
134
+ return false
135
+ }
136
+ }
137
+ }
138
+
139
+ return true
140
+ }