Skip to content

Commit 92d8cb4

Browse files
authored
feat: custom workspace verb alias mappings (#269)
1 parent 325b465 commit 92d8cb4

File tree

12 files changed

+391
-9
lines changed

12 files changed

+391
-9
lines changed

desktop/src-tauri/src/types/generated/workspace.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,64 @@ impl ExecutableFilter {
122122
Default::default()
123123
}
124124
}
125+
#[doc = "A map of executable verbs to valid aliases. This allows you to use custom aliases for exec commands in the workspace.\nSetting this will override all of the default flow command aliases. The verbs and its mapped aliases must be valid flow verbs.\n\nIf set to an empty object, verb aliases will be disabled.\n"]
126+
#[doc = r""]
127+
#[doc = r" <details><summary>JSON schema</summary>"]
128+
#[doc = r""]
129+
#[doc = r" ```json"]
130+
#[doc = "{"]
131+
#[doc = " \"description\": \"A map of executable verbs to valid aliases. This allows you to use custom aliases for exec commands in the workspace.\\nSetting this will override all of the default flow command aliases. The verbs and its mapped aliases must be valid flow verbs.\\n\\nIf set to an empty object, verb aliases will be disabled.\\n\","]
132+
#[doc = " \"type\": \"object\","]
133+
#[doc = " \"additionalProperties\": {"]
134+
#[doc = " \"type\": \"array\","]
135+
#[doc = " \"items\": {"]
136+
#[doc = " \"type\": \"string\""]
137+
#[doc = " }"]
138+
#[doc = " }"]
139+
#[doc = "}"]
140+
#[doc = r" ```"]
141+
#[doc = r" </details>"]
142+
#[derive(:: serde :: Deserialize, :: serde :: Serialize, Clone, Debug)]
143+
#[serde(transparent)]
144+
pub struct VerbAliases(
145+
pub ::std::collections::HashMap<::std::string::String, ::std::vec::Vec<::std::string::String>>,
146+
);
147+
impl ::std::ops::Deref for VerbAliases {
148+
type Target =
149+
::std::collections::HashMap<::std::string::String, ::std::vec::Vec<::std::string::String>>;
150+
fn deref(
151+
&self,
152+
) -> &::std::collections::HashMap<::std::string::String, ::std::vec::Vec<::std::string::String>>
153+
{
154+
&self.0
155+
}
156+
}
157+
impl ::std::convert::From<VerbAliases>
158+
for ::std::collections::HashMap<::std::string::String, ::std::vec::Vec<::std::string::String>>
159+
{
160+
fn from(value: VerbAliases) -> Self {
161+
value.0
162+
}
163+
}
164+
impl ::std::convert::From<&VerbAliases> for VerbAliases {
165+
fn from(value: &VerbAliases) -> Self {
166+
value.clone()
167+
}
168+
}
169+
impl
170+
::std::convert::From<
171+
::std::collections::HashMap<::std::string::String, ::std::vec::Vec<::std::string::String>>,
172+
> for VerbAliases
173+
{
174+
fn from(
175+
value: ::std::collections::HashMap<
176+
::std::string::String,
177+
::std::vec::Vec<::std::string::String>,
178+
>,
179+
) -> Self {
180+
Self(value)
181+
}
182+
}
125183
#[doc = "Configuration for a workspace in the Flow CLI.\nThis configuration is used to define the settings for a workspace.\nEvery workspace has a workspace config file named `flow.yaml` in the root of the workspace directory.\n"]
126184
#[doc = r""]
127185
#[doc = r" <details><summary>JSON schema</summary>"]
@@ -154,6 +212,9 @@ impl ExecutableFilter {
154212
#[doc = " \"tags\": {"]
155213
#[doc = " \"default\": [],"]
156214
#[doc = " \"$ref\": \"#/definitions/CommonTags\""]
215+
#[doc = " },"]
216+
#[doc = " \"verbAliases\": {"]
217+
#[doc = " \"$ref\": \"#/definitions/VerbAliases\""]
157218
#[doc = " }"]
158219
#[doc = " }"]
159220
#[doc = "}"]
@@ -174,6 +235,12 @@ pub struct Workspace {
174235
pub executables: ::std::option::Option<ExecutableFilter>,
175236
#[serde(default = "defaults::workspace_tags")]
176237
pub tags: CommonTags,
238+
#[serde(
239+
rename = "verbAliases",
240+
default,
241+
skip_serializing_if = "::std::option::Option::is_none"
242+
)]
243+
pub verb_aliases: ::std::option::Option<VerbAliases>,
177244
}
178245
impl ::std::convert::From<&Workspace> for Workspace {
179246
fn from(value: &Workspace) -> Self {
@@ -188,6 +255,7 @@ impl ::std::default::Default for Workspace {
188255
display_name: Default::default(),
189256
executables: Default::default(),
190257
tags: defaults::workspace_tags(),
258+
verb_aliases: Default::default(),
191259
}
192260
}
193261
}
@@ -264,6 +332,8 @@ pub mod builder {
264332
::std::string::String,
265333
>,
266334
tags: ::std::result::Result<super::CommonTags, ::std::string::String>,
335+
verb_aliases:
336+
::std::result::Result<::std::option::Option<super::VerbAliases>, ::std::string::String>,
267337
}
268338
impl ::std::default::Default for Workspace {
269339
fn default() -> Self {
@@ -273,6 +343,7 @@ pub mod builder {
273343
display_name: Ok(Default::default()),
274344
executables: Ok(Default::default()),
275345
tags: Ok(super::defaults::workspace_tags()),
346+
verb_aliases: Ok(Default::default()),
276347
}
277348
}
278349
}
@@ -330,6 +401,16 @@ pub mod builder {
330401
.map_err(|e| format!("error converting supplied value for tags: {}", e));
331402
self
332403
}
404+
pub fn verb_aliases<T>(mut self, value: T) -> Self
405+
where
406+
T: ::std::convert::TryInto<::std::option::Option<super::VerbAliases>>,
407+
T::Error: ::std::fmt::Display,
408+
{
409+
self.verb_aliases = value
410+
.try_into()
411+
.map_err(|e| format!("error converting supplied value for verb_aliases: {}", e));
412+
self
413+
}
333414
}
334415
impl ::std::convert::TryFrom<Workspace> for super::Workspace {
335416
type Error = super::error::ConversionError;
@@ -342,6 +423,7 @@ pub mod builder {
342423
display_name: value.display_name?,
343424
executables: value.executables?,
344425
tags: value.tags?,
426+
verb_aliases: value.verb_aliases?,
345427
})
346428
}
347429
}
@@ -353,6 +435,7 @@ pub mod builder {
353435
display_name: Ok(value.display_name),
354436
executables: Ok(value.executables),
355437
tags: Ok(value.tags),
438+
verb_aliases: Ok(value.verb_aliases),
356439
}
357440
}
358441
}

desktop/src/types/generated/workspace.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export interface Workspace {
3131
displayName?: string;
3232
executables?: ExecutableFilter;
3333
tags?: CommonTags;
34+
verbAliases?: VerbAliases;
3435
[k: string]: unknown;
3536
}
3637
export interface ExecutableFilter {
@@ -44,3 +45,13 @@ export interface ExecutableFilter {
4445
included?: string[];
4546
[k: string]: unknown;
4647
}
48+
/**
49+
* A map of executable verbs to valid aliases. This allows you to use custom aliases for exec commands in the workspace.
50+
* Setting this will override all of the default flow command aliases. The verbs and its mapped aliases must be valid flow verbs.
51+
*
52+
* If set to an empty object, verb aliases will be disabled.
53+
*
54+
*/
55+
export interface VerbAliases {
56+
[k: string]: string[];
57+
}

docs/guide/executable.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,27 @@ For instance, `flow test my-app` is equivalent to `flow validate my-app`. This a
4141
interaction with the CLI, making it easier to remember and use.
4242
*See the [verb reference](../types/flowfile.md#verb-groups) for a list all verbs and their synonyms.*
4343

44+
**Custom Verb Aliases**
45+
46+
You can customize which verb aliases are available in your workspace by configuring `verbAliases` in your [workspace configuration](workspace.md#workspace-configuration). This allows you to:
47+
48+
- **Use custom aliases**: Define your own preferred aliases for verbs
49+
- **Disable default aliases**: Set an empty map `{}` to disable all verb aliases
50+
- **Selective aliases**: Only enable specific aliases for certain verbs
51+
52+
```yaml
53+
# In your workspace flow.yaml
54+
verbAliases:
55+
run: ["exec", "start"] # `run` executables can be called with `exec` or `start`
56+
build: ["compile"] # `build` executables can be called with `compile`
57+
# No entry for `test` means no aliases for test executables
58+
59+
# To disable all verb aliases:
60+
verbAliases: {}
61+
```
62+
63+
With the above configuration, `flow start my-app` would run a `run` executable, but `flow execute my-app` would fail because `execute` is no longer a valid alias.
64+
4465
> [!TIP]
4566
> Create shell aliases for commonly used verbs to make running executables easier. For example:
4667
> ```shell

docs/guide/workspace.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,27 @@ When a workspace is created, a [configuration](#workspace-configuration) file is
1818

1919
The workspace configuration file is a YAML file that contains the configuration options for the workspace. This file is located in the root directory of the workspace and is named `flow.yaml`.
2020

21+
```yaml
22+
# Example workspace configuration
23+
displayName: "My Project"
24+
description: "A sample Flow workspace"
25+
descriptionFile: README.md
26+
tags: ["development", "web"]
27+
verbAliases:
28+
run: ["exec", "start"]
29+
build: ["compile"]
30+
executables:
31+
included: ["scripts/", "tools/"]
32+
excluded: ["node_modules/", ".git/"]
33+
```
34+
35+
**Key Configuration Options:**
36+
37+
- **verbAliases**: Customize which verb aliases are available when running executables. Set to `{}` to disable all aliases. See [custom verb aliases](executable.md#custom-verb-aliases) for more details.
38+
- **executables**: Configure which directories to include or exclude when discovering executables.
39+
- **displayName**, **description**, and **descriptionFile**: Used in the interactive UI and library views.
40+
- **tags**: Used for filtering workspaces.
41+
2142
For more details about workspace configuration options, see [Workspace](../types/workspace.md).
2243

2344

docs/schemas/workspace_schema.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@
3232
}
3333
}
3434
}
35+
},
36+
"VerbAliases": {
37+
"description": "A map of executable verbs to valid aliases. This allows you to use custom aliases for exec commands in the workspace.\nSetting this will override all of the default flow command aliases. The verbs and its mapped aliases must be valid flow verbs.\n\nIf set to an empty object, verb aliases will be disabled.\n",
38+
"type": "object",
39+
"additionalProperties": {
40+
"type": "array",
41+
"items": {
42+
"type": "string"
43+
}
44+
}
3545
}
3646
},
3747
"properties": {
@@ -56,6 +66,9 @@
5666
"tags": {
5767
"$ref": "#/definitions/CommonTags",
5868
"default": []
69+
},
70+
"verbAliases": {
71+
"$ref": "#/definitions/VerbAliases"
5972
}
6073
}
6174
}

docs/types/workspace.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Every workspace has a workspace config file named `flow.yaml` in the root of the
1919
| `displayName` | The display name of the workspace. This is used in the interactive UI. | `string` | | |
2020
| `executables` | | [ExecutableFilter](#ExecutableFilter) | <no value> | |
2121
| `tags` | | [CommonTags](#CommonTags) | [] | |
22+
| `verbAliases` | | [VerbAliases](#VerbAliases) | <no value> | |
2223

2324

2425
## Definitions
@@ -49,4 +50,17 @@ Tags can be used with list commands to filter returned data.
4950
| `excluded` | A list of directories to exclude from the executable search. | `array` (`string`) | [] | |
5051
| `included` | A list of directories to include in the executable search. | `array` (`string`) | [] | |
5152

53+
### VerbAliases
54+
55+
A map of executable verbs to valid aliases. This allows you to use custom aliases for exec commands in the workspace.
56+
Setting this will override all of the default flow command aliases. The verbs and its mapped aliases must be valid flow verbs.
57+
58+
If set to an empty object, verb aliases will be disabled.
59+
60+
61+
**Type:** `map` (`string` -> `array` (`string`))
62+
63+
64+
65+
5266

flow.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ description: |
44
Source code for flow
55
GitHub Repo: github.com/flowexec/flow
66
descriptionFile: docs/development.md
7+
verbAliases: {}
78
executables:
89
excluded:
910
- node_modules

internal/cache/executables_cache.go

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/flowexec/flow/internal/filesystem"
1111
"github.com/flowexec/flow/types/common"
1212
"github.com/flowexec/flow/types/executable"
13+
"github.com/flowexec/flow/types/workspace"
1314
)
1415

1516
const execCacheKey = "executables"
@@ -90,7 +91,7 @@ func (c *ExecutableCacheImpl) Update(logger io.Logger) error { //nolint:gocognit
9091
continue
9192
}
9293
cacheData.ExecutableMap[e.Ref()] = flowFile.ConfigPath()
93-
for _, ref := range enumerateExecutableAliasRefs(e) {
94+
for _, ref := range enumerateExecutableAliasRefs(e, wsCfg.VerbAliases) {
9495
cacheData.AliasMap[ref] = e.Ref()
9596
}
9697
cacheData.ConfigMap[flowFile.ConfigPath()] = WorkspaceInfo{
@@ -129,17 +130,23 @@ func (c *ExecutableCacheImpl) GetExecutableByRef(logger io.Logger, ref executabl
129130
return exec, nil
130131
}
131132

133+
var primaryRef executable.Ref
132134
cfgPath, found := c.Data.ExecutableMap[ref]
135+
//nolint:nestif
133136
if !found {
134-
if primaryRef, aliasFound := c.Data.AliasMap[ref]; aliasFound {
137+
if aliasedPrimaryRef, aliasFound := c.Data.AliasMap[ref]; aliasFound {
138+
primaryRef = aliasedPrimaryRef
135139
cfgPath, found = c.Data.ExecutableMap[primaryRef]
136140
if !found {
137141
return nil, NewExecutableNotFoundError(ref.String())
138142
}
139143
} else {
140144
return nil, NewExecutableNotFoundError(ref.String())
141145
}
146+
} else {
147+
primaryRef = ref
142148
}
149+
143150
cfg, err := filesystem.LoadFlowFile(cfgPath)
144151
if err != nil {
145152
return nil, errors.Wrap(err, "unable to load executable config")
@@ -164,7 +171,7 @@ func (c *ExecutableCacheImpl) GetExecutableByRef(logger io.Logger, ref executabl
164171
cfg.Executables = append(cfg.Executables, generated...)
165172

166173
execs := cfg.Executables
167-
exec, err := execs.FindByVerbAndID(ref.Verb(), ref.ID())
174+
exec, err := execs.FindByVerbAndID(primaryRef.Verb(), primaryRef.ID())
168175
if err != nil {
169176
return nil, err
170177
} else if exec == nil {
@@ -231,13 +238,39 @@ func (c *ExecutableCacheImpl) initExecutableCacheData(logger io.Logger) error {
231238
return nil
232239
}
233240

234-
func enumerateExecutableAliasRefs(exec *executable.Executable) executable.RefList {
241+
func enumerateExecutableAliasRefs(
242+
exec *executable.Executable,
243+
override *workspace.WorkspaceVerbAliases,
244+
) executable.RefList {
235245
refs := make(executable.RefList, 0)
236246

237-
for _, verb := range executable.RelatedVerbs(exec.Verb) {
238-
refs = append(refs, executable.NewRef(exec.ID(), verb))
239-
for _, id := range exec.AliasesIDs() {
240-
refs = append(refs, executable.NewRef(id, verb))
247+
switch {
248+
case override == nil:
249+
// use default aliases
250+
for _, verb := range executable.RelatedVerbs(exec.Verb) {
251+
refs = append(refs, executable.NewRef(exec.ID(), verb))
252+
for _, id := range exec.AliasesIDs() {
253+
refs = append(refs, executable.NewRef(id, verb))
254+
}
255+
}
256+
case len(*override) == 0:
257+
// disable all aliases if override is set but empty
258+
return refs
259+
default:
260+
// use overrides if provided
261+
o := *override
262+
if verbs, found := o[exec.Verb.String()]; found {
263+
for _, v := range verbs {
264+
vv := executable.Verb(v)
265+
if err := vv.Validate(); err != nil {
266+
// If the verb is not valid, skip it
267+
continue
268+
}
269+
refs = append(refs, executable.NewRef(exec.ID(), vv))
270+
for _, id := range exec.AliasesIDs() {
271+
refs = append(refs, executable.NewRef(id, vv))
272+
}
273+
}
241274
}
242275
}
243276

0 commit comments

Comments
 (0)