alfy 2.1.0 → 3.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.
package/index.d.ts CHANGED
@@ -20,9 +20,32 @@ export type OutputOptions = {
20
20
  The script will only be re-run if the script filter is still active and the user hasn't changed the state of the filter by typing and triggering a re-run. For example, it could be used to update the progress of a particular task:
21
21
  */
22
22
  readonly rerunInterval?: number;
23
+
24
+ /**
25
+ Variables passed out of the script filter and accessible throughout the current session as environment variables.
26
+
27
+ These are session-level variables that persist for the duration of the user action. Individual items can also have their own `variables` which override these when selected.
28
+
29
+ @example
30
+ ```
31
+ import alfy from 'alfy';
32
+
33
+ alfy.output(
34
+ [
35
+ {
36
+ title: 'Unicorn',
37
+ arg: 'unicorn',
38
+ }
39
+ ],
40
+ {
41
+ variables: {animal: 'unicorn'}
42
+ }
43
+ );
44
+ ```
45
+ */
46
+ readonly variables?: Record<string, string>;
23
47
  };
24
48
 
25
- // eslint-disable-next-line unicorn/prevent-abbreviations
26
49
  export type CacheConfGetOptions = {
27
50
  /**
28
51
  Get the item for the key provided without taking the `maxAge` of the item into account.
@@ -30,7 +53,6 @@ export type CacheConfGetOptions = {
30
53
  readonly ignoreMaxAge?: boolean;
31
54
  };
32
55
 
33
- // eslint-disable-next-line unicorn/prevent-abbreviations
34
56
  export type CacheConfSetOptions = {
35
57
  /**
36
58
  Number of milliseconds the cached value is valid.
@@ -39,7 +61,7 @@ export type CacheConfSetOptions = {
39
61
  };
40
62
 
41
63
  // TODO: Rename this in the next major version.
42
- // eslint-disable-next-line unicorn/prevent-abbreviations
64
+
43
65
  export type CacheConf<T extends Record<string, any> = Record<string, unknown>> = {
44
66
  isExpired: (key: keyof T) => boolean;
45
67
 
@@ -367,9 +389,9 @@ export type Alfy = {
367
389
  /**
368
390
  Log value to the Alfred workflow debugger.
369
391
 
370
- @param text
392
+ @param value
371
393
  */
372
- log: (text: string) => void;
394
+ log: (value: unknown) => void;
373
395
 
374
396
  /**
375
397
  Display an error or error message in Alfred.
package/index.js CHANGED
@@ -3,18 +3,20 @@ import process from 'node:process';
3
3
  import {createRequire} from 'node:module';
4
4
  import Conf from 'conf';
5
5
  import got from 'got';
6
- import {hookStderr} from 'hook-std';
7
6
  import loudRejection from 'loud-rejection';
8
7
  import cleanStack from 'clean-stack';
9
8
  import {getProperty} from 'dot-prop';
10
9
  import AlfredConfig from 'alfred-config';
11
- import updateNotification from './lib/update-notification.js'; // eslint-disable-line import/order
10
+ import updateNotification from './lib/update-notification.js'; // eslint-disable-line import-x/order
12
11
 
13
12
  const require = createRequire(import.meta.url);
14
13
  const CacheConf = require('cache-conf');
15
14
 
16
15
  const alfy = {};
17
16
 
17
+ // Track if output has been generated to avoid empty JSON
18
+ let hasOutput = false;
19
+
18
20
  updateNotification();
19
21
 
20
22
  const getIcon = name => `/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/${name}.icns`;
@@ -41,8 +43,9 @@ alfy.alfred = {
41
43
 
42
44
  alfy.input = process.argv[2];
43
45
 
44
- alfy.output = (items, {rerunInterval} = {}) => {
45
- console.log(JSON.stringify({items, rerun: rerunInterval}, null, '\t'));
46
+ alfy.output = (items, {rerunInterval, variables} = {}) => {
47
+ hasOutput = true;
48
+ console.log(JSON.stringify({items, rerun: rerunInterval, variables}, null, '\t'));
46
49
  };
47
50
 
48
51
  alfy.matches = (input, list, item) => {
@@ -73,6 +76,7 @@ alfy.log = text => {
73
76
 
74
77
  alfy.error = error => {
75
78
  const stack = cleanStack(error.stack || error);
79
+ const title = error.stack ? `${error.name}: ${error.message}` : String(error);
76
80
 
77
81
  const copy = `
78
82
  \`\`\`
@@ -86,7 +90,7 @@ ${process.platform} ${os.release()}
86
90
  `.trim();
87
91
 
88
92
  alfy.output([{
89
- title: error.stack ? `${error.name}: ${error.message}` : error,
93
+ title,
90
94
  subtitle: 'Press ⌘L to see the full error and ⌘C to copy it.',
91
95
  valid: false,
92
96
  text: {
@@ -97,6 +101,9 @@ ${process.platform} ${os.release()}
97
101
  path: alfy.icon.error,
98
102
  },
99
103
  }]);
104
+
105
+ // Also output to stderr for the debugger (as requested in issue #86)
106
+ console.error(stack);
100
107
  };
101
108
 
102
109
  alfy.config = new Conf({
@@ -133,7 +140,7 @@ alfy.fetch = async (url, options) => {
133
140
  delete options.transform;
134
141
  delete options.maxAge;
135
142
 
136
- const key = rawKey.replaceAll('.', '\\.');
143
+ const key = rawKey.replaceAll('.', String.raw`\.`);
137
144
  const cachedResponse = alfy.cache.get(key, {ignoreMaxAge: true});
138
145
 
139
146
  if (cachedResponse && !alfy.cache.isExpired(key)) {
@@ -181,6 +188,12 @@ alfy.icon = {
181
188
 
182
189
  loudRejection(alfy.error);
183
190
  process.on('uncaughtException', alfy.error);
184
- hookStderr(alfy.error);
191
+
192
+ // Ensure valid JSON output even if user forgets to call alfy.output() (fixes #82)
193
+ process.on('beforeExit', () => {
194
+ if (!hasOutput) {
195
+ alfy.output([]);
196
+ }
197
+ });
185
198
 
186
199
  export default alfy;
@@ -2,7 +2,7 @@ import {readPackageUp} from 'read-package-up';
2
2
  import alfredNotifier from 'alfred-notifier';
3
3
 
4
4
  export default async function updateNotification() {
5
- const {package: package_} = await readPackageUp();
5
+ const {package: package_} = (await readPackageUp()) ?? {};
6
6
  const alfy = package_?.alfy ?? {};
7
7
 
8
8
  if (alfy.updateNotification !== false) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alfy",
3
- "version": "2.1.0",
3
+ "version": "3.1.0",
4
4
  "description": "Create Alfred workflows with ease",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/alfy",
@@ -17,7 +17,7 @@
17
17
  },
18
18
  "sideEffects": false,
19
19
  "engines": {
20
- "node": ">=18"
20
+ "node": ">=20"
21
21
  },
22
22
  "scripts": {
23
23
  "test": "xo && ava && tsd"
@@ -47,21 +47,21 @@
47
47
  "alfred-notifier": "^0.2.3",
48
48
  "cache-conf": "^0.6.0",
49
49
  "clean-stack": "^5.2.0",
50
- "conf": "^13.0.1",
51
- "dot-prop": "^9.0.0",
52
- "execa": "^9.3.0",
53
- "got": "^14.4.2",
54
- "hook-std": "^3.0.0",
50
+ "conf": "^14.0.0",
51
+ "dot-prop": "^10.0.0",
52
+ "execa": "^9.6.0",
53
+ "got": "^14.4.8",
54
+ "hook-std": "^4.0.0",
55
55
  "loud-rejection": "^2.2.0",
56
56
  "read-package-up": "^11.0.0"
57
57
  },
58
58
  "devDependencies": {
59
- "ava": "^6.1.3",
59
+ "ava": "^6.4.1",
60
60
  "delay": "^6.0.0",
61
- "nock": "^13.5.4",
61
+ "nock": "^14.0.10",
62
62
  "tempfile": "^5.0.0",
63
- "tsd": "^0.31.1",
64
- "xo": "^0.59.2"
63
+ "tsd": "^0.33.0",
64
+ "xo": "^1.2.2"
65
65
  },
66
66
  "tsd": {
67
67
  "compilerOptions": {
package/readme.md CHANGED
@@ -151,13 +151,16 @@ test('main', async t => {
151
151
 
152
152
  ## Debugging
153
153
 
154
- When developing your workflow it can be useful to be able to debug it when something is not working. This is when the [workflow debugger](https://www.alfredapp.com/help/workflows/advanced/debugger/) comes in handy. You can find it in your workflow view in Alfred. Press the insect icon to open it. It will show you the plain text output of `alfy.output()` and anything you log with `alfy.log()`:
154
+ When developing your workflow it can be useful to be able to debug it when something is not working. This is when the [workflow debugger](https://www.alfredapp.com/help/workflows/advanced/debugger/) comes in handy. You can find it in your workflow view in Alfred. Press the insect icon to open it. It will show you the plain text output of `alfy.output()` and anything you log with `alfy.log()` or `console.error()`:
155
155
 
156
156
  ```js
157
157
  import alfy from 'alfy';
158
158
 
159
159
  const unicorn = getUnicorn();
160
160
  alfy.log(unicorn);
161
+
162
+ // You can also use console.error() for debugging
163
+ console.error('Debug info:', {data: someData});
161
164
  ```
162
165
 
163
166
  ## Environment variables
@@ -231,13 +234,35 @@ alfy.output(
231
234
 
232
235
  <img src="media/screenshot-output.png" width="694">
233
236
 
237
+ ###### variables
238
+
239
+ Type: `object`
240
+
241
+ Variables passed out of the script filter and accessible throughout the current session as environment variables. These are session-level variables that persist for the duration of the user action. Individual items can also have their own `variables` which override these when selected.
242
+
243
+ ```js
244
+ import alfy from 'alfy';
245
+
246
+ alfy.output(
247
+ [
248
+ {
249
+ title: 'Unicorn',
250
+ arg: 'unicorn',
251
+ }
252
+ ],
253
+ {
254
+ variables: {animal: 'unicorn'}
255
+ }
256
+ );
257
+ ```
258
+
234
259
  #### log(value)
235
260
 
236
261
  Log `value` to the [Alfred workflow debugger](https://www.alfredapp.com/help/workflows/advanced/debugger/).
237
262
 
238
263
  #### matches(input, list, item?)
239
264
 
240
- Returns an `string[]` of items in `list` that case-insensitively contains `input`.
265
+ Returns a `string[]` of items in `list` that case-insensitively contains `input`.
241
266
 
242
267
  ```js
243
268
  import alfy from 'alfy';
package/run-node.sh CHANGED
@@ -13,7 +13,25 @@ PATH_CACHE="$alfred_workflow_cache"/node_path
13
13
 
14
14
  get_user_path() {
15
15
  eval $(/usr/libexec/path_helper -s)
16
- echo "$($SHELL -i -l -c 'echo -e "\n"PATH=\"$PATH:\$PATH\""\n"' 2>/dev/null | grep "^PATH=")" > "$PATH_CACHE"
16
+
17
+ # Use delimiters to reliably extract PATH from shell startup noise (inspired by `shell-env`).
18
+ # Disable Oh My Zsh plugins that can block the process.
19
+ local delimiter="_ALFY_ENV_DELIMITER_"
20
+ local raw_env
21
+ raw_env="$(DISABLE_AUTO_UPDATE=true ZSH_TMUX_AUTOSTARTED=true ZSH_TMUX_AUTOSTART=false $SHELL -ilc "echo -n $delimiter; command env; echo -n $delimiter; exit" 2>/dev/null)"
22
+
23
+ # Extract the env output between the delimiters.
24
+ local env_output="${raw_env#*"$delimiter"}"
25
+ env_output="${env_output%%"$delimiter"*}"
26
+
27
+ # Extract PATH from the env output.
28
+ local user_path
29
+ user_path="$(echo "$env_output" | sed -n 's/^PATH=//p')"
30
+
31
+ # Only write cache if we got a non-empty result.
32
+ if [[ -n "$user_path" ]]; then
33
+ echo "PATH=\"${user_path}:\$PATH\"" > "$PATH_CACHE"
34
+ fi
17
35
  }
18
36
 
19
37
  set_path() {