termcast 1.3.33 → 1.3.35

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 (88) hide show
  1. package/dist/apis/cache.d.ts +1 -2
  2. package/dist/apis/cache.d.ts.map +1 -1
  3. package/dist/apis/cache.js +134 -52
  4. package/dist/apis/cache.js.map +1 -1
  5. package/dist/build.d.ts.map +1 -1
  6. package/dist/build.js +25 -0
  7. package/dist/build.js.map +1 -1
  8. package/dist/cli.js +6 -8
  9. package/dist/cli.js.map +1 -1
  10. package/dist/components/dropdown.js +3 -3
  11. package/dist/components/dropdown.js.map +1 -1
  12. package/dist/components/footer.d.ts.map +1 -1
  13. package/dist/components/footer.js +1 -1
  14. package/dist/components/footer.js.map +1 -1
  15. package/dist/components/icon.d.ts.map +1 -1
  16. package/dist/components/icon.js +386 -23
  17. package/dist/components/icon.js.map +1 -1
  18. package/dist/components/list.d.ts.map +1 -1
  19. package/dist/components/list.js +90 -22
  20. package/dist/components/list.js.map +1 -1
  21. package/dist/examples/list-controlled-search.d.ts +2 -0
  22. package/dist/examples/list-controlled-search.d.ts.map +1 -0
  23. package/dist/examples/list-controlled-search.js +12 -0
  24. package/dist/examples/list-controlled-search.js.map +1 -0
  25. package/dist/extensions/home.js +1 -1
  26. package/dist/extensions/home.js.map +1 -1
  27. package/dist/extensions/react-refresh-init.d.ts.map +1 -1
  28. package/dist/extensions/react-refresh-init.js +4 -3
  29. package/dist/extensions/react-refresh-init.js.map +1 -1
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js.map +1 -1
  33. package/dist/internal/dialog.d.ts.map +1 -1
  34. package/dist/internal/dialog.js +4 -5
  35. package/dist/internal/dialog.js.map +1 -1
  36. package/dist/internal/providers.d.ts.map +1 -1
  37. package/dist/internal/providers.js +18 -5
  38. package/dist/internal/providers.js.map +1 -1
  39. package/dist/state.d.ts +1 -0
  40. package/dist/state.d.ts.map +1 -1
  41. package/dist/state.js.map +1 -1
  42. package/dist/theme.d.ts.map +1 -1
  43. package/dist/theme.js +6 -2
  44. package/dist/theme.js.map +1 -1
  45. package/dist/utils/run-command.js +3 -3
  46. package/dist/utils/run-command.js.map +1 -1
  47. package/dist/utils.d.ts +16 -1
  48. package/dist/utils.d.ts.map +1 -1
  49. package/dist/utils.js +28 -1
  50. package/dist/utils.js.map +1 -1
  51. package/dist/watcher.d.ts.map +1 -1
  52. package/dist/watcher.js +24 -4
  53. package/dist/watcher.js.map +1 -1
  54. package/package.json +10 -9
  55. package/src/apis/cache.test.ts +35 -3
  56. package/src/apis/cache.tsx +180 -57
  57. package/src/build.tsx +28 -0
  58. package/src/cli.tsx +8 -10
  59. package/src/compile.vitest.tsx +42 -24
  60. package/src/components/dropdown.tsx +3 -3
  61. package/src/components/footer.tsx +4 -2
  62. package/src/components/icon.tsx +385 -23
  63. package/src/components/list.tsx +104 -28
  64. package/src/examples/github.vitest.tsx +37 -37
  65. package/src/examples/list-controlled-search.tsx +28 -0
  66. package/src/examples/list-controlled-search.vitest.tsx +49 -0
  67. package/src/examples/list-detail-metadata.vitest.tsx +1 -1
  68. package/src/examples/list-dropdown-default.vitest.tsx +9 -9
  69. package/src/examples/list-scrollbox.vitest.tsx +55 -41
  70. package/src/examples/list-with-detail.vitest.tsx +35 -36
  71. package/src/examples/list-with-dropdown.vitest.tsx +2 -2
  72. package/src/examples/list-with-sections.vitest.tsx +153 -118
  73. package/src/examples/simple-file-picker.vitest.tsx +1 -1
  74. package/src/examples/simple-grid.vitest.tsx +44 -44
  75. package/src/examples/simple-navigation.vitest.tsx +43 -12
  76. package/src/examples/store.vitest.tsx +1 -1
  77. package/src/examples/swift-extension.vitest.tsx +3 -3
  78. package/src/extensions/dev.vitest.tsx +69 -34
  79. package/src/extensions/home.tsx +1 -1
  80. package/src/extensions/react-refresh-init.tsx +4 -3
  81. package/src/index.tsx +1 -0
  82. package/src/internal/dialog.tsx +21 -23
  83. package/src/internal/providers.tsx +18 -5
  84. package/src/state.tsx +1 -0
  85. package/src/theme.tsx +6 -2
  86. package/src/utils/run-command.tsx +3 -3
  87. package/src/utils.tsx +41 -1
  88. package/src/watcher.tsx +26 -6
@@ -151,7 +151,6 @@ export function DialogOverlay(): any {
151
151
  const dialogStack = useStore((state) => state.dialogStack)
152
152
  const showActionsDialog = useStore((state) => state.showActionsDialog)
153
153
  const navContext = useContext(NavigationContext)
154
- const theme = useTheme()
155
154
 
156
155
  const setActionsPortalTargetRef = useCallback((node: any) => {
157
156
  if (!node) {
@@ -177,13 +176,33 @@ export function DialogOverlay(): any {
177
176
 
178
177
  return (
179
178
  <>
179
+ {item && (
180
+ <box position='absolute' width='100%' height='100%' flexDirection='column'>
181
+ <InFocus inFocus={true}>
182
+ <Dialog
183
+ position={item.position}
184
+ onClickOutside={() => {
185
+ const state = useStore.getState()
186
+ if (state.dialogStack.length > 0) {
187
+ useStore.setState({
188
+ dialogStack: state.dialogStack.slice(0, -1),
189
+ })
190
+ }
191
+ }}
192
+ >
193
+ <NavigationContext.Provider value={navContext}>
194
+ {item.element}
195
+ </NavigationContext.Provider>
196
+ </Dialog>
197
+ </InFocus>
198
+ </box>
199
+ )}
180
200
  {showActionsDialog && (
181
201
  <box
182
202
  position='absolute'
183
203
  width='100%'
184
204
  height='100%'
185
205
  flexDirection='column'
186
- backgroundColor={theme.background}
187
206
  >
188
207
  <InFocus inFocus={true}>
189
208
  <Dialog
@@ -201,27 +220,6 @@ export function DialogOverlay(): any {
201
220
  </InFocus>
202
221
  </box>
203
222
  )}
204
- {item && (
205
- <box position='absolute' width='100%' height='100%' flexDirection='column'>
206
- <InFocus inFocus={true}>
207
- <Dialog
208
- position={item.position}
209
- onClickOutside={() => {
210
- const state = useStore.getState()
211
- if (state.dialogStack.length > 0) {
212
- useStore.setState({
213
- dialogStack: state.dialogStack.slice(0, -1),
214
- })
215
- }
216
- }}
217
- >
218
- <NavigationContext.Provider value={navContext}>
219
- {item.element}
220
- </NavigationContext.Provider>
221
- </Dialog>
222
- </InFocus>
223
- </box>
224
- )}
225
223
  </>
226
224
  )
227
225
  }
@@ -35,20 +35,33 @@ const queryClient = new QueryClient({
35
35
  },
36
36
  })
37
37
 
38
- // Create a custom persister using the Cache class
39
- const queryCache = new Cache({ namespace: 'tanstack-query' })
38
+ // Lazy-initialized Cache for TanStack Query persistence.
39
+ // Must not be created at module load time because extensionPath may not be set yet
40
+ // (e.g. when renderWithProviders sets state right before rendering).
41
+ // Tracks the extensionPath it was created with so it can be recreated if the path changes.
42
+ let queryCache: Cache | null = null
43
+ let queryCachePath: string | null = null
44
+
45
+ function getQueryCache(): Cache {
46
+ const currentPath = useStore.getState().extensionPath
47
+ if (!queryCache || currentPath !== queryCachePath) {
48
+ queryCache = new Cache({ namespace: 'tanstack-query' })
49
+ queryCachePath = currentPath
50
+ }
51
+ return queryCache
52
+ }
40
53
 
41
54
  const persister = {
42
55
  persistClient: async (client: any) => {
43
56
  const serialized = JSON.stringify(client)
44
- queryCache.set('query-client-data', serialized)
57
+ getQueryCache().set('query-client-data', serialized)
45
58
  },
46
59
  restoreClient: async () => {
47
- const data = queryCache.get('query-client-data')
60
+ const data = getQueryCache().get('query-client-data')
48
61
  return data ? JSON.parse(data) : undefined
49
62
  },
50
63
  removeClient: async () => {
51
- queryCache.remove('query-client-data')
64
+ getQueryCache().remove('query-client-data')
52
65
  },
53
66
  }
54
67
 
package/src/state.tsx CHANGED
@@ -36,6 +36,7 @@ export interface DialogStackItem {
36
36
  export interface NavigationStackItem {
37
37
  element: ReactNode
38
38
  onPop?: () => void
39
+ selectedListIndex?: number
39
40
  }
40
41
 
41
42
  interface AppState {
package/src/theme.tsx CHANGED
@@ -3,12 +3,16 @@ import { getResolvedTheme, type ResolvedTheme, defaultThemeName, themeNames } fr
3
3
  import { useStore } from './state'
4
4
  import { Cache } from './apis/cache'
5
5
 
6
- // Global cache for theme persistence (no namespace = global storage)
6
+ // Global cache for theme persistence (no namespace = global storage).
7
+ // Tracks extensionPath so the cache is recreated if the path changes.
7
8
  let globalCache: Cache | null = null
9
+ let globalCachePath: string | null = null
8
10
 
9
11
  function getGlobalCache(): Cache {
10
- if (!globalCache) {
12
+ const currentPath = useStore.getState().extensionPath
13
+ if (!globalCache || currentPath !== globalCachePath) {
11
14
  globalCache = new Cache()
15
+ globalCachePath = currentPath
12
16
  }
13
17
  return globalCache
14
18
  }
@@ -1,5 +1,5 @@
1
1
  import React from 'react'
2
- import { cac } from 'cac'
2
+ import { goke } from 'goke'
3
3
  import { useStore } from 'termcast/src/state'
4
4
  import { showToast, Toast } from 'termcast/src/apis/toast'
5
5
  import { LocalStorage } from 'termcast/src/apis/localstorage'
@@ -31,13 +31,13 @@ export interface ParsedExtensionArgs {
31
31
  export function parseExtensionArgs({
32
32
  skipArgv = 0,
33
33
  }: { skipArgv?: number } = {}): ParsedExtensionArgs {
34
- // Build argv for cac: keep first 2 (binary + script), skip subcommand args, keep the rest
34
+ // Build argv for goke: keep first 2 (binary + script), skip subcommand args, keep the rest
35
35
  const argv = [
36
36
  process.argv[0],
37
37
  process.argv[1],
38
38
  ...process.argv.slice(2 + skipArgv),
39
39
  ]
40
- const parsed = cac().parse(argv, { run: false })
40
+ const parsed = goke().parse(argv, { run: false })
41
41
  return {
42
42
  commandName: parsed.args[0] as string | undefined,
43
43
  showHelp: Boolean(parsed.options.help || parsed.options.h),
package/src/utils.tsx CHANGED
@@ -12,13 +12,53 @@ import {
12
12
  type CommandWithFile,
13
13
  } from './package-json'
14
14
  import { logger } from './logger'
15
+ import { useStore } from './state'
16
+
17
+ export interface RenderWithProvidersOptions {
18
+ extensionName?: string
19
+ extensionPath?: string
20
+ packageJson?: RaycastPackageJson
21
+ }
22
+
23
+ /**
24
+ * Render a React element wrapped in TermcastProvider with all necessary state initialized.
25
+ *
26
+ * Sets up extensionPath (for LocalStorage, Cache, assets) and extensionPackageJson
27
+ * (for preferences, environment metadata, action panel config) before rendering.
28
+ *
29
+ * - extensionName defaults to 'termcast-app'
30
+ * - extensionPath defaults to ~/.termcast/compiled/{extensionName}
31
+ * - packageJson defaults to a minimal { name, title, commands: [] }
32
+ */
33
+ export async function renderWithProviders(
34
+ element: ReactNode,
35
+ options?: RenderWithProvidersOptions,
36
+ ): Promise<void> {
37
+ const extensionName = options?.extensionName || 'termcast-app'
38
+ const extensionPath =
39
+ options?.extensionPath ||
40
+ path.join(os.homedir(), '.termcast', 'compiled', extensionName)
41
+ const packageJson: RaycastPackageJson = options?.packageJson || {
42
+ name: extensionName,
43
+ title: extensionName,
44
+ description: '',
45
+ commands: [],
46
+ }
47
+
48
+ // Reset store to initial state then set extension fields, matching
49
+ // startDevMode/startCompiledExtension behavior to avoid stale navigation/dialog state
50
+ useStore.setState({
51
+ ...useStore.getInitialState(),
52
+ extensionPath,
53
+ extensionPackageJson: packageJson,
54
+ })
15
55
 
16
- export async function renderWithProviders(element: ReactNode): Promise<void> {
17
56
  const renderer = await createCliRenderer({
18
57
  onDestroy: () => {
19
58
  process.exit(0)
20
59
  },
21
60
  })
61
+
22
62
  createRoot(renderer).render(<TermcastProvider>{element}</TermcastProvider>)
23
63
  }
24
64
 
package/src/watcher.tsx CHANGED
@@ -6,14 +6,34 @@ import type ParcelWatcher from '@parcel/watcher'
6
6
 
7
7
  let _watcher: typeof ParcelWatcher | undefined
8
8
 
9
+ function getWatcherPackageName(): string {
10
+ const suffix = process.platform === 'linux' ? '-glibc' : ''
11
+ return `@parcel/watcher-${process.platform}-${process.arch}${suffix}`
12
+ }
13
+
9
14
  export function getWatcher(): typeof ParcelWatcher {
10
15
  if (_watcher) return _watcher
11
- // Use require with template literal so Bun can analyze and bundle the native module
12
- // Linux requires -glibc suffix for the native binding
13
- const suffix = process.platform === 'linux' ? '-glibc' : ''
14
- const binding = require(
15
- `@parcel/watcher-${process.platform}-${process.arch}${suffix}`,
16
- )
16
+ const watcherPackageName = getWatcherPackageName()
17
+ let binding: unknown
18
+ try {
19
+ // Use require with template literal so Bun can analyze and bundle the native module
20
+ binding = require(watcherPackageName)
21
+ } catch (error) {
22
+ const message = error instanceof Error ? error.message : String(error)
23
+ throw new Error(
24
+ [
25
+ `Failed to load native watcher package \"${watcherPackageName}\".`,
26
+ `Current platform: ${process.platform}-${process.arch}.`,
27
+ '',
28
+ 'This usually means Bun blocked install scripts for watcher packages.',
29
+ 'Fix with:',
30
+ ' bun pm trust @parcel/watcher',
31
+ ' bun install',
32
+ '',
33
+ `Original error: ${message}`,
34
+ ].join('\n'),
35
+ )
36
+ }
17
37
  _watcher = createWrapper(binding)
18
38
  return _watcher!
19
39
  }