termcast 1.3.30 → 1.3.32
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/apis/cache.d.ts.map +1 -1
- package/dist/apis/cache.js +4 -39
- package/dist/apis/cache.js.map +1 -1
- package/dist/apis/hud.d.ts.map +1 -1
- package/dist/apis/hud.js +13 -31
- package/dist/apis/hud.js.map +1 -1
- package/dist/apis/localstorage.d.ts.map +1 -1
- package/dist/apis/localstorage.js +3 -27
- package/dist/apis/localstorage.js.map +1 -1
- package/dist/apis/toast.d.ts +16 -43
- package/dist/apis/toast.d.ts.map +1 -1
- package/dist/apis/toast.js +78 -177
- package/dist/apis/toast.js.map +1 -1
- package/dist/build.d.ts +3 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +52 -2
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +206 -25
- package/dist/cli.js.map +1 -1
- package/dist/colors.d.ts.map +1 -1
- package/dist/colors.js +1 -0
- package/dist/colors.js.map +1 -1
- package/dist/compile.d.ts +0 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +18 -23
- package/dist/compile.js.map +1 -1
- package/dist/components/actions.d.ts.map +1 -1
- package/dist/components/actions.js +30 -15
- package/dist/components/actions.js.map +1 -1
- package/dist/components/animation-tick.d.ts +12 -0
- package/dist/components/animation-tick.d.ts.map +1 -0
- package/dist/components/animation-tick.js +63 -0
- package/dist/components/animation-tick.js.map +1 -0
- package/dist/components/detail.d.ts.map +1 -1
- package/dist/components/detail.js +10 -13
- package/dist/components/detail.js.map +1 -1
- package/dist/components/dropdown.d.ts +1 -0
- package/dist/components/dropdown.d.ts.map +1 -1
- package/dist/components/dropdown.js +27 -26
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/extension-preferences.d.ts.map +1 -1
- package/dist/components/extension-preferences.js +15 -10
- package/dist/components/extension-preferences.js.map +1 -1
- package/dist/components/footer.d.ts +13 -0
- package/dist/components/footer.d.ts.map +1 -0
- package/dist/components/footer.js +106 -0
- package/dist/components/footer.js.map +1 -0
- package/dist/components/form/file-autocomplete.d.ts +19 -4
- package/dist/components/form/file-autocomplete.d.ts.map +1 -1
- package/dist/components/form/file-autocomplete.js +56 -55
- package/dist/components/form/file-autocomplete.js.map +1 -1
- package/dist/components/form/file-picker.d.ts.map +1 -1
- package/dist/components/form/file-picker.js +26 -15
- package/dist/components/form/file-picker.js.map +1 -1
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +17 -15
- package/dist/components/form/index.js.map +1 -1
- package/dist/components/form/with-left-border.d.ts.map +1 -1
- package/dist/components/form/with-left-border.js +4 -12
- package/dist/components/form/with-left-border.js.map +1 -1
- package/dist/components/list.d.ts.map +1 -1
- package/dist/components/list.js +126 -86
- package/dist/components/list.js.map +1 -1
- package/dist/components/loading-bar.d.ts.map +1 -1
- package/dist/components/loading-bar.js +5 -22
- package/dist/components/loading-bar.js.map +1 -1
- package/dist/components/loading-text.d.ts.map +1 -1
- package/dist/components/loading-text.js +3 -22
- package/dist/components/loading-text.js.map +1 -1
- package/dist/components/theme-picker.d.ts +2 -0
- package/dist/components/theme-picker.d.ts.map +1 -0
- package/dist/components/theme-picker.js +37 -0
- package/dist/components/theme-picker.js.map +1 -0
- package/dist/descendants.d.ts +6 -0
- package/dist/descendants.d.ts.map +1 -1
- package/dist/descendants.js +74 -8
- package/dist/descendants.js.map +1 -1
- package/dist/examples/internal/descendants-rerender.d.ts +14 -0
- package/dist/examples/internal/descendants-rerender.d.ts.map +1 -0
- package/dist/examples/internal/descendants-rerender.js +145 -0
- package/dist/examples/internal/descendants-rerender.js.map +1 -0
- package/dist/examples/internal/simple-dialog.js +4 -1
- package/dist/examples/internal/simple-dialog.js.map +1 -1
- package/dist/examples/internal/simple-scrollbox.js +1 -1
- package/dist/examples/internal/simple-scrollbox.js.map +1 -1
- package/dist/examples/list-with-dropdown.js +1 -1
- package/dist/examples/list-with-dropdown.js.map +1 -1
- package/dist/examples/miscellaneous.js +1 -1
- package/dist/examples/miscellaneous.js.map +1 -1
- package/dist/examples/toast-action.d.ts +2 -0
- package/dist/examples/toast-action.d.ts.map +1 -0
- package/dist/examples/toast-action.js +76 -0
- package/dist/examples/toast-action.js.map +1 -0
- package/dist/examples/toast-variations.js +38 -36
- package/dist/examples/toast-variations.js.map +1 -1
- package/dist/extensions/dev.d.ts +1 -1
- package/dist/extensions/dev.d.ts.map +1 -1
- package/dist/extensions/dev.js +62 -30
- package/dist/extensions/dev.js.map +1 -1
- package/dist/extensions/home.d.ts.map +1 -1
- package/dist/extensions/home.js +4 -3
- package/dist/extensions/home.js.map +1 -1
- package/dist/extensions/react-refresh-init.d.ts +5 -0
- package/dist/extensions/react-refresh-init.d.ts.map +1 -0
- package/dist/extensions/react-refresh-init.js +52 -0
- package/dist/extensions/react-refresh-init.js.map +1 -0
- package/dist/internal/date-picker-widget.js +1 -1
- package/dist/internal/date-picker-widget.js.map +1 -1
- package/dist/internal/dialog.d.ts +8 -3
- package/dist/internal/dialog.d.ts.map +1 -1
- package/dist/internal/dialog.js +37 -53
- package/dist/internal/dialog.js.map +1 -1
- package/dist/internal/navigation.d.ts +1 -0
- package/dist/internal/navigation.d.ts.map +1 -1
- package/dist/internal/navigation.js +25 -1
- package/dist/internal/navigation.js.map +1 -1
- package/dist/internal/providers.d.ts.map +1 -1
- package/dist/internal/providers.js +9 -197
- package/dist/internal/providers.js.map +1 -1
- package/dist/internal/scrollbox.d.ts.map +1 -1
- package/dist/internal/scrollbox.js +1 -0
- package/dist/internal/scrollbox.js.map +1 -1
- package/dist/release.d.ts +1 -0
- package/dist/release.d.ts.map +1 -1
- package/dist/release.js +16 -9
- package/dist/release.js.map +1 -1
- package/dist/state.d.ts +27 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +6 -0
- package/dist/state.js.map +1 -1
- package/dist/theme.d.ts +6 -19
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +76 -45
- package/dist/theme.js.map +1 -1
- package/dist/themes/aura.json +69 -0
- package/dist/themes/ayu.json +80 -0
- package/dist/themes/catppuccin-frappe.json +233 -0
- package/dist/themes/catppuccin-macchiato.json +233 -0
- package/dist/themes/catppuccin.json +112 -0
- package/dist/themes/cobalt2.json +228 -0
- package/dist/themes/cursor.json +249 -0
- package/dist/themes/dracula.json +219 -0
- package/dist/themes/everforest.json +241 -0
- package/dist/themes/flexoki.json +237 -0
- package/dist/themes/github-light.json +56 -0
- package/dist/themes/github.json +241 -0
- package/dist/themes/gruvbox.json +95 -0
- package/dist/themes/kanagawa.json +77 -0
- package/dist/themes/lucent-orng.json +227 -0
- package/dist/themes/material.json +235 -0
- package/dist/themes/matrix.json +77 -0
- package/dist/themes/mercury.json +245 -0
- package/dist/themes/monokai.json +221 -0
- package/dist/themes/nightowl.json +221 -0
- package/dist/themes/nord.json +223 -0
- package/dist/themes/one-dark.json +84 -0
- package/dist/themes/opencode-light.json +62 -0
- package/dist/themes/opencode.json +245 -0
- package/dist/themes/orng.json +245 -0
- package/dist/themes/palenight.json +222 -0
- package/dist/themes/rosepine.json +234 -0
- package/dist/themes/solarized.json +223 -0
- package/dist/themes/synthwave84.json +226 -0
- package/dist/themes/termcast.json +226 -0
- package/dist/themes/tokyonight.json +243 -0
- package/dist/themes/vercel.json +255 -0
- package/dist/themes/vesper.json +218 -0
- package/dist/themes/zenburn.json +223 -0
- package/dist/themes.d.ts +57 -0
- package/dist/themes.d.ts.map +1 -0
- package/dist/themes.js +181 -0
- package/dist/themes.js.map +1 -0
- package/dist/utils/run-command.d.ts +2 -1
- package/dist/utils/run-command.d.ts.map +1 -1
- package/dist/utils/run-command.js +20 -10
- package/dist/utils/run-command.js.map +1 -1
- package/dist/utils.d.ts +2 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +90 -17
- package/dist/utils.js.map +1 -1
- package/dist/watcher.d.ts +3 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +16 -0
- package/dist/watcher.js.map +1 -0
- package/package.json +16 -10
- package/src/apis/cache.tsx +5 -44
- package/src/apis/hud.tsx +17 -62
- package/src/apis/localstorage.tsx +3 -32
- package/src/apis/toast.tsx +91 -275
- package/src/build.test.tsx +10 -0
- package/src/build.tsx +61 -1
- package/src/cli.tsx +365 -103
- package/src/colors.tsx +1 -0
- package/src/compile.tsx +21 -29
- package/src/compile.vitest.tsx +300 -0
- package/src/components/actions.tsx +64 -45
- package/src/components/animation-tick.tsx +85 -0
- package/src/components/detail.tsx +31 -35
- package/src/components/dropdown.tsx +32 -21
- package/src/components/extension-preferences.tsx +14 -10
- package/src/components/footer.tsx +241 -0
- package/src/components/form/file-autocomplete.tsx +80 -60
- package/src/components/form/file-picker.tsx +37 -25
- package/src/components/form/index.tsx +45 -41
- package/src/components/form/with-left-border.tsx +4 -14
- package/src/components/list.tsx +181 -121
- package/src/components/loading-bar.tsx +5 -25
- package/src/components/loading-text.tsx +4 -23
- package/src/components/theme-picker.tsx +57 -0
- package/src/descendants.tsx +98 -9
- package/src/examples/actions-dialog-layout.vitest.tsx +112 -0
- package/src/examples/file-autocomplete.vitest.tsx +131 -122
- package/src/examples/form-basic.vitest.tsx +463 -644
- package/src/examples/form-dropdown.vitest.tsx +553 -571
- package/src/examples/form-scroll.vitest.tsx +112 -102
- package/src/examples/form-tagpicker.vitest.tsx +364 -338
- package/src/examples/internal/descendants-rerender.tsx +273 -0
- package/src/examples/internal/descendants-rerender.vitest.tsx +194 -0
- package/src/examples/internal/simple-dialog.tsx +4 -4
- package/src/examples/internal/simple-scrollbox.tsx +2 -2
- package/src/examples/internal/simple-scrollbox.vitest.tsx +43 -31
- package/src/examples/list-detail-metadata.vitest.tsx +34 -30
- package/src/examples/list-dropdown-default.vitest.tsx +84 -72
- package/src/examples/list-empty-view.vitest.tsx +93 -0
- package/src/examples/list-fetch-data.vitest.tsx +36 -30
- package/src/examples/list-scrollbox.vitest.tsx +59 -39
- package/src/examples/list-with-detail.vitest.tsx +339 -314
- package/src/examples/list-with-dropdown.tsx +1 -0
- package/src/examples/list-with-dropdown.vitest.tsx +176 -150
- package/src/examples/list-with-sections.vitest.tsx +289 -270
- package/src/examples/list-with-toast.vitest.tsx +44 -44
- package/src/examples/miscellaneous.tsx +10 -0
- package/src/examples/simple-file-picker.vitest.tsx +90 -86
- package/src/examples/simple-grid.vitest.tsx +275 -249
- package/src/examples/simple-navigation.vitest.tsx +192 -168
- package/src/examples/store.vitest.tsx +6 -4
- package/src/examples/swift-extension.vitest.tsx +31 -19
- package/src/examples/synonyms.vitest.tsx +93 -83
- package/src/examples/toast-action.tsx +160 -0
- package/src/examples/toast-action.vitest.tsx +404 -0
- package/src/examples/toast-variations.tsx +58 -57
- package/src/examples/toast-variations.vitest.tsx +186 -166
- package/src/extensions/dev.tsx +74 -33
- package/src/extensions/dev.vitest.tsx +162 -69
- package/src/extensions/home.tsx +5 -6
- package/src/extensions/react-refresh-init.tsx +59 -0
- package/src/internal/date-picker-widget.tsx +1 -1
- package/src/internal/dialog.tsx +59 -83
- package/src/internal/navigation.tsx +37 -4
- package/src/internal/providers.tsx +27 -315
- package/src/internal/scrollbox.tsx +1 -0
- package/src/release.tsx +16 -10
- package/src/state.tsx +36 -3
- package/src/theme.tsx +82 -51
- package/src/themes/aura.json +69 -0
- package/src/themes/ayu.json +80 -0
- package/src/themes/catppuccin-frappe.json +233 -0
- package/src/themes/catppuccin-macchiato.json +233 -0
- package/src/themes/catppuccin.json +112 -0
- package/src/themes/cobalt2.json +228 -0
- package/src/themes/cursor.json +249 -0
- package/src/themes/dracula.json +219 -0
- package/src/themes/everforest.json +241 -0
- package/src/themes/flexoki.json +237 -0
- package/src/themes/github-light.json +56 -0
- package/src/themes/github.json +241 -0
- package/src/themes/gruvbox.json +95 -0
- package/src/themes/kanagawa.json +77 -0
- package/src/themes/lucent-orng.json +227 -0
- package/src/themes/material.json +235 -0
- package/src/themes/matrix.json +77 -0
- package/src/themes/mercury.json +252 -0
- package/src/themes/monokai.json +221 -0
- package/src/themes/nightowl.json +221 -0
- package/src/themes/nord.json +223 -0
- package/src/themes/one-dark.json +84 -0
- package/src/themes/opencode-light.json +62 -0
- package/src/themes/opencode.json +245 -0
- package/src/themes/orng.json +245 -0
- package/src/themes/palenight.json +222 -0
- package/src/themes/rosepine.json +234 -0
- package/src/themes/solarized.json +223 -0
- package/src/themes/synthwave84.json +226 -0
- package/src/themes/termcast.json +227 -0
- package/src/themes/tokyonight.json +243 -0
- package/src/themes/vercel.json +255 -0
- package/src/themes/vesper.json +218 -0
- package/src/themes/zenburn.json +223 -0
- package/src/themes.ts +291 -0
- package/src/utils/run-command.tsx +23 -12
- package/src/utils.tsx +115 -18
- package/src/watcher.tsx +19 -0
package/src/compile.tsx
CHANGED
|
@@ -15,6 +15,7 @@ const raycastAliasPlugin: BunPlugin = {
|
|
|
15
15
|
build.onResolve({ filter: /^termcast/ }, (args) => ({
|
|
16
16
|
path: require.resolve(args.path),
|
|
17
17
|
}))
|
|
18
|
+
|
|
18
19
|
build.onResolve({ filter: /^react$/ }, () => ({
|
|
19
20
|
path: require.resolve('react'),
|
|
20
21
|
}))
|
|
@@ -24,6 +25,8 @@ const raycastAliasPlugin: BunPlugin = {
|
|
|
24
25
|
build.onResolve({ filter: /^react\/jsx-dev-runtime$/ }, () => ({
|
|
25
26
|
path: require.resolve('react/jsx-dev-runtime'),
|
|
26
27
|
}))
|
|
28
|
+
|
|
29
|
+
|
|
27
30
|
},
|
|
28
31
|
}
|
|
29
32
|
|
|
@@ -34,26 +37,32 @@ export function generateEntryCode({
|
|
|
34
37
|
packageJson: import('./package-json').RaycastPackageJson
|
|
35
38
|
commands: Array<{ name: string; bundledPath: string }>
|
|
36
39
|
}): string {
|
|
37
|
-
|
|
38
|
-
.map(
|
|
39
|
-
(cmd, i) =>
|
|
40
|
-
` const { default: Command${i} } = await import(${JSON.stringify(cmd.bundledPath)});`,
|
|
41
|
-
)
|
|
42
|
-
.join('\n')
|
|
43
|
-
|
|
40
|
+
// Generate lazy loaders instead of importing all commands at startup
|
|
44
41
|
const commandsArray = commands
|
|
45
42
|
.map(
|
|
46
|
-
(cmd
|
|
43
|
+
(cmd) => ` {
|
|
47
44
|
name: ${JSON.stringify(cmd.name)},
|
|
48
|
-
|
|
45
|
+
loadComponent: () => import(${JSON.stringify(cmd.bundledPath)}).then(m => m.default),
|
|
49
46
|
}`,
|
|
50
47
|
)
|
|
51
48
|
.join(',\n')
|
|
52
49
|
|
|
53
50
|
return `
|
|
54
51
|
async function main() {
|
|
55
|
-
|
|
52
|
+
// Set state BEFORE importing commands so module-scope code (e.g. getPreferenceValues) can access extensionPackageJson
|
|
53
|
+
const { useStore } = await import('termcast');
|
|
54
|
+
const os = await import('node:os');
|
|
55
|
+
const path = await import('node:path');
|
|
56
|
+
|
|
57
|
+
const packageJson = ${JSON.stringify(packageJson)};
|
|
58
|
+
const compiledExtensionPath = path.join(os.homedir(), '.termcast', 'compiled', packageJson.name);
|
|
56
59
|
|
|
60
|
+
useStore.setState({
|
|
61
|
+
extensionPath: compiledExtensionPath,
|
|
62
|
+
extensionPackageJson: packageJson,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Commands are lazily loaded when selected (not at startup)
|
|
57
66
|
const compiledCommands = [
|
|
58
67
|
${commandsArray}
|
|
59
68
|
];
|
|
@@ -61,7 +70,7 @@ ${commandsArray}
|
|
|
61
70
|
const { startCompiledExtension } = await import('termcast/src/extensions/dev');
|
|
62
71
|
|
|
63
72
|
await startCompiledExtension({
|
|
64
|
-
packageJson
|
|
73
|
+
packageJson,
|
|
65
74
|
compiledCommands,
|
|
66
75
|
skipArgv: 0,
|
|
67
76
|
});
|
|
@@ -112,24 +121,6 @@ export function targetToFileSuffix(target: CompileTarget): string {
|
|
|
112
121
|
return parts.join('-') + ext
|
|
113
122
|
}
|
|
114
123
|
|
|
115
|
-
// Same as targetToFileSuffix but without .exe extension (for archive names)
|
|
116
|
-
export function targetToArchiveSuffix(target: CompileTarget): string {
|
|
117
|
-
const os =
|
|
118
|
-
target.os === 'win32'
|
|
119
|
-
? 'windows'
|
|
120
|
-
: target.os === 'darwin'
|
|
121
|
-
? 'darwin'
|
|
122
|
-
: 'linux'
|
|
123
|
-
const parts = [os, target.arch]
|
|
124
|
-
if (target.abi) {
|
|
125
|
-
parts.push(target.abi)
|
|
126
|
-
}
|
|
127
|
-
if (target.avx2 === false) {
|
|
128
|
-
parts.push('baseline')
|
|
129
|
-
}
|
|
130
|
-
return parts.join('-')
|
|
131
|
-
}
|
|
132
|
-
|
|
133
124
|
export function getArchiveExtension(target: CompileTarget): string {
|
|
134
125
|
return target.os === 'linux' ? '.tar.gz' : '.zip'
|
|
135
126
|
}
|
|
@@ -232,6 +223,7 @@ export async function compileExtension({
|
|
|
232
223
|
},
|
|
233
224
|
define: {
|
|
234
225
|
'process.env.VERSION': JSON.stringify(version || ''),
|
|
226
|
+
// Use 'development' to avoid React bundling issues with CJS/ESM interop
|
|
235
227
|
'process.env.NODE_ENV': JSON.stringify('production'),
|
|
236
228
|
},
|
|
237
229
|
plugins: [raycastAliasPlugin, swiftLoaderPlugin],
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { test, expect, afterEach, beforeAll } from 'vitest'
|
|
2
|
+
import { launchTerminal, Session } from 'tuistory/src'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import fs from 'node:fs'
|
|
5
|
+
import { execSync } from 'node:child_process'
|
|
6
|
+
|
|
7
|
+
const fixtureDir = path.resolve(__dirname, '../fixtures/simple-extension')
|
|
8
|
+
const distDir = path.join(fixtureDir, 'dist')
|
|
9
|
+
const executablePath = path.join(distDir, 'simple-extension')
|
|
10
|
+
|
|
11
|
+
let session: Session
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
session?.close()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
// Compile once before all tests
|
|
18
|
+
function ensureCompiled() {
|
|
19
|
+
if (!fs.existsSync(executablePath)) {
|
|
20
|
+
// Clean dist directory
|
|
21
|
+
if (fs.existsSync(distDir)) {
|
|
22
|
+
fs.rmSync(distDir, { recursive: true, force: true })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Compile using CLI (runs in Bun context)
|
|
26
|
+
execSync(`bun src/cli.tsx compile ${fixtureDir}`, {
|
|
27
|
+
cwd: path.resolve(__dirname, '..'),
|
|
28
|
+
stdio: 'pipe',
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!fs.existsSync(executablePath)) {
|
|
33
|
+
throw new Error(`Compiled executable not found at ${executablePath}`)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
beforeAll(() => {
|
|
38
|
+
if (fs.existsSync(executablePath)) {
|
|
39
|
+
fs.unlinkSync(executablePath)
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
test('compile extension and run executable', async () => {
|
|
45
|
+
ensureCompiled()
|
|
46
|
+
|
|
47
|
+
// Run the compiled executable
|
|
48
|
+
session = await launchTerminal({
|
|
49
|
+
command: executablePath,
|
|
50
|
+
args: [],
|
|
51
|
+
cols: 60,
|
|
52
|
+
rows: 16,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
await session.text({
|
|
56
|
+
waitFor: (text) => /Simple Test Extension/i.test(text),
|
|
57
|
+
timeout: 10000,
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const snapshot = await session.text()
|
|
61
|
+
expect(snapshot).toMatchInlineSnapshot(`
|
|
62
|
+
"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
Simple Test Extension ────────────────────────────────
|
|
66
|
+
|
|
67
|
+
> Search commands...
|
|
68
|
+
|
|
69
|
+
Commands ▲
|
|
70
|
+
›List Items Displays a simple list with some ite view ▀
|
|
71
|
+
Search Items Search and filter through a list o view
|
|
72
|
+
Google Oauth view
|
|
73
|
+
usePromise Demo Shows how to use the usePromise view ▼
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
↵ run command ↑↓ navigate ^k actions
|
|
77
|
+
|
|
78
|
+
"
|
|
79
|
+
`)
|
|
80
|
+
}, 60000)
|
|
81
|
+
|
|
82
|
+
test('compiled executable can run command', async () => {
|
|
83
|
+
ensureCompiled()
|
|
84
|
+
|
|
85
|
+
session = await launchTerminal({
|
|
86
|
+
command: executablePath,
|
|
87
|
+
args: [],
|
|
88
|
+
cols: 60,
|
|
89
|
+
rows: 16,
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
await session.text({
|
|
93
|
+
waitFor: (text) => /Simple Test Extension/i.test(text),
|
|
94
|
+
timeout: 10000,
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// Select first command (List Items) - enter opens action panel, enter again runs
|
|
98
|
+
await session.press('enter')
|
|
99
|
+
await session.press('enter')
|
|
100
|
+
|
|
101
|
+
await session.text({
|
|
102
|
+
waitFor: (text) => /First Item/i.test(text),
|
|
103
|
+
timeout: 10000,
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const listSnapshot = await session.text()
|
|
107
|
+
expect(listSnapshot).toMatchInlineSnapshot(`
|
|
108
|
+
"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
List Items ───────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
> Search...
|
|
114
|
+
|
|
115
|
+
Items ▲
|
|
116
|
+
›▲ First Item This is the first item █
|
|
117
|
+
▲ Second Item This is the second item
|
|
118
|
+
▲ Third Item This is the third item
|
|
119
|
+
▲ Fourth Item This is the fourth item ▼
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
✓ Copied to Clipboard First Item
|
|
123
|
+
|
|
124
|
+
"
|
|
125
|
+
`)
|
|
126
|
+
}, 60000)
|
|
127
|
+
|
|
128
|
+
test('compiled executable can navigate back', async () => {
|
|
129
|
+
ensureCompiled()
|
|
130
|
+
|
|
131
|
+
session = await launchTerminal({
|
|
132
|
+
command: executablePath,
|
|
133
|
+
args: [],
|
|
134
|
+
cols: 60,
|
|
135
|
+
rows: 16,
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
await session.text({
|
|
139
|
+
waitFor: (text) => /Simple Test Extension/i.test(text),
|
|
140
|
+
timeout: 10000,
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// Run first command
|
|
144
|
+
await session.press('enter')
|
|
145
|
+
await session.press('enter')
|
|
146
|
+
|
|
147
|
+
await session.text({
|
|
148
|
+
waitFor: (text) => /First Item/i.test(text),
|
|
149
|
+
timeout: 10000,
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
// Navigate back with escape - this should either:
|
|
153
|
+
// 1. Go back to command list (if push was used)
|
|
154
|
+
// 2. Exit (if replace was used because only one command is visible)
|
|
155
|
+
await session.press('escape')
|
|
156
|
+
|
|
157
|
+
// Wait a bit for navigation to complete
|
|
158
|
+
await session.waitIdle()
|
|
159
|
+
|
|
160
|
+
const backSnapshot = await session.text()
|
|
161
|
+
// If we're back at the command list, we'll see "Simple Test Extension"
|
|
162
|
+
// If the command exited, the terminal might be empty
|
|
163
|
+
expect(backSnapshot).toMatchInlineSnapshot(`
|
|
164
|
+
"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
List Items ───────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
> Search...
|
|
170
|
+
|
|
171
|
+
Items ▲
|
|
172
|
+
›▲ First Item This is the first item █
|
|
173
|
+
▲ Second Item This is the second item
|
|
174
|
+
▲ Third Item This is the third item
|
|
175
|
+
▲ Fourth Item This is the fourth item ▼
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
↵ copy item title ↑↓ navigate ^k actions
|
|
179
|
+
|
|
180
|
+
"
|
|
181
|
+
`)
|
|
182
|
+
}, 60000)
|
|
183
|
+
|
|
184
|
+
test('compiled executable shows error when command throws at root scope', async () => {
|
|
185
|
+
ensureCompiled()
|
|
186
|
+
|
|
187
|
+
session = await launchTerminal({
|
|
188
|
+
command: executablePath,
|
|
189
|
+
args: [],
|
|
190
|
+
cols: 60,
|
|
191
|
+
rows: 20,
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
// With lazy loading, the command list should appear first
|
|
195
|
+
await session.text({
|
|
196
|
+
waitFor: (text) => /Simple Test Extension/i.test(text),
|
|
197
|
+
timeout: 10000,
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
// Filter to the Throw Error command
|
|
201
|
+
await session.type('throw error')
|
|
202
|
+
await session.waitIdle()
|
|
203
|
+
|
|
204
|
+
// Select and run the command
|
|
205
|
+
await session.press('enter')
|
|
206
|
+
await session.press('enter')
|
|
207
|
+
|
|
208
|
+
// Wait for error to be displayed
|
|
209
|
+
await session.text({
|
|
210
|
+
waitFor: (text) => /error/i.test(text),
|
|
211
|
+
timeout: 10000,
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
const errorSnapshot = await session.text()
|
|
215
|
+
expect(errorSnapshot).toMatchInlineSnapshot(`
|
|
216
|
+
"
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
Error: This is a test error thrown at root scope
|
|
222
|
+
at <anonymous> (fixtures/simple-extension/src/th
|
|
223
|
+
at processTicksAndRejections (native:7:39)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
"
|
|
237
|
+
`)
|
|
238
|
+
}, 60000)
|
|
239
|
+
|
|
240
|
+
// Test for single-command extension with root-level error
|
|
241
|
+
const singleErrorFixtureDir = path.resolve(__dirname, '../fixtures/single-error-extension')
|
|
242
|
+
const singleErrorDistDir = path.join(singleErrorFixtureDir, 'dist')
|
|
243
|
+
const singleErrorExecutablePath = path.join(singleErrorDistDir, 'single-error-extension')
|
|
244
|
+
|
|
245
|
+
function ensureSingleErrorCompiled() {
|
|
246
|
+
if (!fs.existsSync(singleErrorExecutablePath)) {
|
|
247
|
+
if (fs.existsSync(singleErrorDistDir)) {
|
|
248
|
+
fs.rmSync(singleErrorDistDir, { recursive: true, force: true })
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
execSync(`bun src/cli.tsx compile ${singleErrorFixtureDir}`, {
|
|
252
|
+
cwd: path.resolve(__dirname, '..'),
|
|
253
|
+
stdio: 'pipe',
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!fs.existsSync(singleErrorExecutablePath)) {
|
|
258
|
+
throw new Error(`Compiled executable not found at ${singleErrorExecutablePath}`)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
test('single command extension shows error when command throws at root scope', async () => {
|
|
263
|
+
ensureSingleErrorCompiled()
|
|
264
|
+
|
|
265
|
+
session = await launchTerminal({
|
|
266
|
+
command: singleErrorExecutablePath,
|
|
267
|
+
args: [],
|
|
268
|
+
cols: 60,
|
|
269
|
+
rows: 20,
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
// Wait for something to appear
|
|
273
|
+
await session.waitIdle()
|
|
274
|
+
|
|
275
|
+
const errorSnapshot = await session.text()
|
|
276
|
+
expect(errorSnapshot).toMatchInlineSnapshot(`
|
|
277
|
+
"
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
Error
|
|
283
|
+
at <anonymous> (fixtures/single-error-extension/
|
|
284
|
+
at processTicksAndRejections (unknown:7:39)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
"
|
|
298
|
+
`)
|
|
299
|
+
}, 60000)
|
|
300
|
+
|
|
@@ -20,6 +20,7 @@ import { useDialog } from 'termcast/src/internal/dialog'
|
|
|
20
20
|
import { useNavigation } from 'termcast/src/internal/navigation'
|
|
21
21
|
import { Dropdown } from 'termcast/src/components/dropdown'
|
|
22
22
|
import { ExtensionPreferences } from 'termcast/src/components/extension-preferences'
|
|
23
|
+
import { ThemePicker } from 'termcast/src/components/theme-picker'
|
|
23
24
|
import { useStore } from 'termcast/src/state'
|
|
24
25
|
import { useIsInFocus } from 'termcast/src/internal/focus-context'
|
|
25
26
|
import { useIsOffscreen } from 'termcast/src/internal/offscreen'
|
|
@@ -209,7 +210,7 @@ Action.Push = (props) => {
|
|
|
209
210
|
props.onPush?.()
|
|
210
211
|
// Push the target to dialog if needed
|
|
211
212
|
if (props.target) {
|
|
212
|
-
dialog.push(props.target, 'center')
|
|
213
|
+
dialog.push({ element: props.target, position: 'center' })
|
|
213
214
|
}
|
|
214
215
|
},
|
|
215
216
|
})
|
|
@@ -530,17 +531,29 @@ Action.ToggleQuickLook = (props) => {
|
|
|
530
531
|
useActionDescendant({
|
|
531
532
|
title: props.title || 'Quick Look',
|
|
532
533
|
shortcut: props.shortcut || { key: 'space' },
|
|
533
|
-
execute: () => {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
534
|
+
execute: async () => {
|
|
535
|
+
if (!props.path) {
|
|
536
|
+
props.onToggle?.()
|
|
537
|
+
return
|
|
537
538
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
539
|
+
|
|
540
|
+
if (process.platform !== 'darwin') {
|
|
541
|
+
showToast({
|
|
542
|
+
title: 'Quick Look',
|
|
543
|
+
message: 'Quick Look is only supported on macOS',
|
|
544
|
+
style: Toast.Style.Failure,
|
|
545
|
+
})
|
|
546
|
+
return
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const { spawn } = await import('node:child_process')
|
|
550
|
+
// qlmanage -p opens Quick Look preview
|
|
551
|
+
const child = spawn('qlmanage', ['-p', props.path], {
|
|
552
|
+
stdio: 'ignore',
|
|
553
|
+
detached: true,
|
|
543
554
|
})
|
|
555
|
+
child.unref()
|
|
556
|
+
props.onToggle?.()
|
|
544
557
|
},
|
|
545
558
|
})
|
|
546
559
|
|
|
@@ -680,15 +693,16 @@ const ActionPanel: ActionPanelType = (props) => {
|
|
|
680
693
|
}
|
|
681
694
|
}, [descendantsContext.map, dialog, isOffscreen])
|
|
682
695
|
|
|
683
|
-
// prevent showing actions if
|
|
684
|
-
|
|
696
|
+
// prevent showing actions if we're not inside an actions dialog (must be after hooks)
|
|
697
|
+
const lastStackItem = dialog.stack[dialog.stack.length - 1]
|
|
698
|
+
if (lastStackItem?.type !== 'actions' && !isOffscreen) return null
|
|
685
699
|
|
|
686
700
|
// ActionPanel renders as Dropdown with children
|
|
687
701
|
return (
|
|
688
702
|
<ActionDescendantsProvider value={descendantsContext}>
|
|
689
703
|
<ActionPanelContext.Provider value={contextValue}>
|
|
690
704
|
<Dropdown
|
|
691
|
-
tooltip={title}
|
|
705
|
+
tooltip={title || 'Actions'}
|
|
692
706
|
placeholder='Search actions...'
|
|
693
707
|
filtering
|
|
694
708
|
onChange={(value) => {
|
|
@@ -706,38 +720,43 @@ const ActionPanel: ActionPanelType = (props) => {
|
|
|
706
720
|
}}
|
|
707
721
|
>
|
|
708
722
|
{children}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
723
|
+
<ActionPanel.Section title="Settings">
|
|
724
|
+
{hasExtensionPrefs && (
|
|
725
|
+
<Action
|
|
726
|
+
title={`Configure ${extensionPackageJson!.title}...`}
|
|
727
|
+
shortcut={{ modifiers: ['cmd', 'shift'], key: ',' }}
|
|
728
|
+
onAction={() => {
|
|
729
|
+
dialog.clear()
|
|
730
|
+
push(
|
|
731
|
+
<ExtensionPreferences
|
|
732
|
+
extensionName={extensionPackageJson!.name}
|
|
733
|
+
/>,
|
|
734
|
+
)
|
|
735
|
+
}}
|
|
736
|
+
/>
|
|
737
|
+
)}
|
|
738
|
+
{hasCommandPrefs && (
|
|
739
|
+
<Action
|
|
740
|
+
title="Configure Command..."
|
|
741
|
+
onAction={() => {
|
|
742
|
+
dialog.clear()
|
|
743
|
+
push(
|
|
744
|
+
<ExtensionPreferences
|
|
745
|
+
extensionName={extensionPackageJson!.name}
|
|
746
|
+
commandName={currentCommandName!}
|
|
747
|
+
/>,
|
|
748
|
+
)
|
|
749
|
+
}}
|
|
750
|
+
/>
|
|
751
|
+
)}
|
|
752
|
+
<Action
|
|
753
|
+
title="Change Theme..."
|
|
754
|
+
onAction={() => {
|
|
755
|
+
dialog.clear()
|
|
756
|
+
dialog.push({ element: <ThemePicker /> })
|
|
757
|
+
}}
|
|
758
|
+
/>
|
|
759
|
+
</ActionPanel.Section>
|
|
741
760
|
</Dropdown>
|
|
742
761
|
</ActionPanelContext.Provider>
|
|
743
762
|
</ActionDescendantsProvider>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared animation tick system for synchronized loading animations.
|
|
5
|
+
*
|
|
6
|
+
* Components subscribe to a global tick counter that increments every 20ms.
|
|
7
|
+
* Each component can compute its animation state based on the tick value.
|
|
8
|
+
*
|
|
9
|
+
* Intervals (coordinated so animations look synchronized):
|
|
10
|
+
* - LoadingBar: 40ms (2 ticks) - wave animation
|
|
11
|
+
* - LoadingText: 40ms (2 ticks) - wave animation, same speed as bar
|
|
12
|
+
* - Spinner: 200ms (10 ticks) - pulses every 5 wave steps
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
type TickListener = (tick: number) => void
|
|
16
|
+
|
|
17
|
+
let globalTick = 0
|
|
18
|
+
let intervalId: NodeJS.Timeout | null = null
|
|
19
|
+
const listeners = new Set<TickListener>()
|
|
20
|
+
|
|
21
|
+
const BASE_INTERVAL_MS = 20
|
|
22
|
+
|
|
23
|
+
function startGlobalTick() {
|
|
24
|
+
if (intervalId) return
|
|
25
|
+
intervalId = setInterval(() => {
|
|
26
|
+
globalTick++
|
|
27
|
+
listeners.forEach((listener) => {
|
|
28
|
+
listener(globalTick)
|
|
29
|
+
})
|
|
30
|
+
}, BASE_INTERVAL_MS)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function stopGlobalTick() {
|
|
34
|
+
if (intervalId) {
|
|
35
|
+
clearInterval(intervalId)
|
|
36
|
+
intervalId = null
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function subscribe(listener: TickListener) {
|
|
41
|
+
listeners.add(listener)
|
|
42
|
+
if (listeners.size === 1) {
|
|
43
|
+
startGlobalTick()
|
|
44
|
+
}
|
|
45
|
+
return () => {
|
|
46
|
+
listeners.delete(listener)
|
|
47
|
+
if (listeners.size === 0) {
|
|
48
|
+
stopGlobalTick()
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Hook to subscribe to animation ticks.
|
|
55
|
+
* @param divisor - Only triggers re-render when tick is divisible by this value. Pass 0 to disable (no subscription).
|
|
56
|
+
* @returns The current tick value (divided by divisor)
|
|
57
|
+
*/
|
|
58
|
+
export function useAnimationTick(divisor: number = 1): number {
|
|
59
|
+
const [tick, setTick] = React.useState(0)
|
|
60
|
+
|
|
61
|
+
React.useEffect(() => {
|
|
62
|
+
// Don't subscribe if divisor is 0 (disabled)
|
|
63
|
+
if (divisor <= 0) {
|
|
64
|
+
setTick(0) // Reset when disabled
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const unsubscribe = subscribe((currentTick) => {
|
|
69
|
+
if (currentTick % divisor === 0) {
|
|
70
|
+
setTick(Math.floor(currentTick / divisor))
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
return unsubscribe
|
|
74
|
+
}, [divisor])
|
|
75
|
+
|
|
76
|
+
return tick
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Tick divisors for each component type (base interval is 20ms)
|
|
80
|
+
// Waves share the same speed so they animate in sync
|
|
81
|
+
export const TICK_DIVISORS = {
|
|
82
|
+
LOADING_BAR: 2, // 40ms - wave animation
|
|
83
|
+
LOADING_TEXT: 2, // 40ms - wave animation (same as bar)
|
|
84
|
+
SPINNER: 10, // 200ms - pulses every 5 wave steps
|
|
85
|
+
} as const
|