Skip to content

Commit ffc5f69

Browse files
CopilotAgentEnderbarbados-clemensgraphite-app[bot]nx-cloud[bot]
authored
docs(core): document argv in task execution hook contexts (#33322)
The `argv` field was already implemented in `PreTasksExecutionContext` and `PostTasksExecutionContext` but lacked tests and documentation for plugin authors to discover and use it. ## Changes - **Added unit tests** (`packages/nx/src/daemon/server/handle-tasks-execution-hooks.spec.ts`) validating that `argv` flows correctly through hook handlers for different command patterns (direct, affected, run-many) - **Enhanced existing documentation** in `astro-docs/src/content/docs/extending-nx/task-running-lifecycle.mdoc` with a new section covering: - Context property definitions showing the `argv` field - Examples showing how to detect command types (direct execution, affected, run-many) - Example demonstrating conditional analytics based on the original command - Common command patterns reference - Best practices for defensive argv parsing ## Usage ```typescript import type { NxPlugin, PostTasksExecutionContext } from '@nx/devkit'; export const myPlugin: NxPlugin = { name: 'my-plugin', postTasksExecution: async (options, context: PostTasksExecutionContext) => { // Distinguish between nx build my-app vs nx affected -t build if (context.argv.includes('affected')) { console.log('Running in affected mode'); } } }; ``` Fixes https://linear.app/nxdev/issue/NXC-3382/add-contextargv-to-task-execution-hook-contexts <!-- START COPILOT CODING AGENT SUFFIX --> <details> <summary>Original prompt</summary> > Issue Title: Add context.argv to task execution hook contexts > Issue Description: Expose the original CLI arguments on the plugin worker so hooks can distinguish how execution was started (e.g., `nx build nx-api` vs `nx affected -t build`). Proposal: include the invoking argv on the hook context (e.g., `context.argv`). > Fixes https://linear.app/nxdev/issue/NXC-3382/add-contextargv-to-task-execution-hook-contexts > > > Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6: > [https://github.com/nrwl/nx](https://github.com/nrwl/nx) > > Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6: > Aha! that worked - so you can tell it to assign to copilot instead of "me and copilot" > > Comment by User d484ef82-7f7d-4a95-be09-9d82ca3905dc: > 📋 I wasn't able to determine which GitHub repository to work in. > > I think it's one of these, but can you tell me which one is right? > > Comment by User : > Created issue [NXC-3382](https://linear.app/nxdev/issue/NXC-3382/add-contextargv-to-task-execution-hook-contexts) > > Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6: > This comment thread is synced to a corresponding [thread in Slack](https://nrwl.slack.com/archives/C070BJ2JYLW/p1761928859857989?thread_ts=1761928859.857989&cid=C070BJ2JYLW). All replies are displayed in both locations. > > Comment by User : > This thread is for an agent session with githubcopilot. > > Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6: > @linear make a ticket and assign it to copilot > > Comment by User f5ae6d50-28e9-4ee7-ad51-3da8208d5914: > Makes sense to me > > Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6: > Sure, context.argv? > > Comment by User f5ae6d50-28e9-4ee7-ad51-3da8208d5914: > We can add them as `argv`? > > Comment by User f5ae6d50-28e9-4ee7-ad51-3da8208d5914: > Yeah they would run on the plugin worker so it's not there > > Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6: > @jason we could add `originalArgv` to the contexts? > > Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6: > Eh, probably not... they run on the plugin worker > > Comment by User 4215f3ef-50bd-4f09-85a0-b489c88057b6: > Yeah, I don't think the hooks know.... I'd be curious if process.argv would just have that info though > > Comment by User 439b15a6-827b-4258-971a-d86133ad59de: > payfit does > > Comment by User 74901385-a023-4825-8470-fe68b1b55664: > I can’t see anything about that in the docs - so I would assume the hooks are agnostic to how the tasks were triggered? > > Comment by User 74901385-a023-4825-8470-fe68b1b55664: > so they’re asking is there’s a way to tell the difference between `nx build nx-api` or `nx affected -t build` in the task hook? > > Comment by User 74901385-a023-4825-8470-fe68b1b55664: > > I’ve been playing around with the Task Execution Hooks, specifically the postTasksExecution hook, and I think it will be really useful for me to grab some detailed metrics for our specific use cases. > > What I feel like it’s missing is a way to see what command actually started the task execution, whether it was a specific target or an affected command. As long as it was a specific target, I think the tasks are sorted in order so the last taskResult will probably be the actual target of the command but for affected it seems a bit more random what the last result will be. > > Is there a way to know exactly which command kicked off the ‘task execution’? > > </details> <!-- START COPILOT CODING AGENT TIPS --> --- 💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey). --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: AgentEnder <[email protected]> Co-authored-by: Craigory Coppola <[email protected]> Co-authored-by: Caleb Ukle <[email protected]> Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com> Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com> Co-authored-by: Copilot <[email protected]>
1 parent 035e0fd commit ffc5f69

2 files changed

Lines changed: 210 additions & 0 deletions

File tree

astro-docs/src/content/docs/extending-nx/task-running-lifecycle.mdoc

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,103 @@ export async function postTasksExecution(options, context) {
117117
}
118118
```
119119

120+
## Using `context.argv` to Determine the Command
121+
122+
The context object includes an `argv` property that contains the original CLI arguments used to invoke Nx. This allows you to distinguish between different execution modes and apply conditional logic.
123+
124+
### Context Properties
125+
126+
Both `preTasksExecution` and `postTasksExecution` hooks receive a context object with the following properties:
127+
128+
```typescript
129+
type PreTaskExecutionContext = {
130+
id: string;
131+
workspaceRoot: string;
132+
nxJsonConfiguration: NxJsonConfiguration;
133+
argv: string[]; // Original CLI arguments
134+
};
135+
136+
type PostTasksExecutionContext = {
137+
id: string;
138+
workspaceRoot: string;
139+
nxJsonConfiguration: NxJsonConfiguration;
140+
taskResults: TaskResults;
141+
argv: string[]; // Original CLI arguments
142+
startTime: number;
143+
endTime: number;
144+
};
145+
```
146+
147+
### Example: Detecting Command Types
148+
149+
```typescript
150+
export async function postTasksExecution(options, context) {
151+
// Check if running affected command
152+
if (context.argv.includes('affected')) {
153+
console.log('✅ Ran affected tasks');
154+
} else if (context.argv.includes('run-many')) {
155+
console.log('✅ Ran tasks for multiple projects');
156+
} else {
157+
console.log('✅ Ran task for specific project');
158+
}
159+
}
160+
```
161+
162+
### Example: Conditional Analytics Based on Command
163+
164+
```typescript
165+
function isAffectedCommand(argv) {
166+
return argv.includes('affected');
167+
}
168+
169+
function getTargetName(argv) {
170+
const targetIndex = argv.findIndex(
171+
(arg) => arg === '-t' || arg === '--target'
172+
);
173+
return targetIndex !== -1 ? argv[targetIndex + 1] : undefined;
174+
}
175+
176+
export async function postTasksExecution(options, context) {
177+
const isAffected = isAffectedCommand(context.argv);
178+
const target = getTargetName(context.argv);
179+
180+
// Send analytics with command context
181+
await sendAnalytics({
182+
executionId: context.id,
183+
commandType: isAffected ? 'affected' : 'direct',
184+
target: target,
185+
taskCount: Object.keys(context.taskResults).length,
186+
duration: context.endTime - context.startTime,
187+
});
188+
}
189+
```
190+
191+
### Common Command Patterns
192+
193+
- Direct execution: `nx build my-app` → `argv: ['node', '/path/to/nx', 'build', 'my-app']`
194+
- Affected: `nx affected -t build test` → `argv: ['node', '/path/to/nx', 'affected', '-t', 'build', 'test']`
195+
- Run many: `nx run-many -t build -p app1 app2` → `argv: ['node', '/path/to/nx', 'run-many', '-t', 'build', '-p', 'app1', 'app2']`
196+
197+
### Best Practices for Parsing argv
198+
199+
When working with `context.argv`, parse it defensively:
200+
201+
```typescript
202+
// Bad - assumes structure
203+
const target = context.argv[2];
204+
205+
// Good - searches for the flag
206+
const targetIndex = context.argv.findIndex(
207+
(arg) => arg === '-t' || arg === '--target'
208+
);
209+
const target = targetIndex !== -1 ? context.argv[targetIndex + 1] : undefined;
210+
```
211+
120212
## Best Practices
121213

122214
1. **Keep hooks fast**: Hooks should execute quickly to avoid slowing down the task execution process
123215
2. **Handle errors gracefully**: Ensure your hooks don't crash the entire execution pipeline
124216
3. **Use environment variables** for configuration that needs to persist across tasks
125217
4. **Leverage context data**: Use the context object to access relevant information about the workspace and task results
126218
5. **Provide clear errors**: If throwing errors, make sure they are descriptive and actionable
219+
6. **Parse argv defensively**: Don't assume the position of arguments; search for specific flags instead
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import {
2+
handleRunPreTasksExecution,
3+
handleRunPostTasksExecution,
4+
} from './handle-tasks-execution-hooks';
5+
import type {
6+
PreTasksExecutionContext,
7+
PostTasksExecutionContext,
8+
} from '../../project-graph/plugins/public-api';
9+
10+
// Mock the tasks-execution-hooks module
11+
jest.mock('../../project-graph/plugins/tasks-execution-hooks', () => ({
12+
runPreTasksExecution: jest.fn(),
13+
runPostTasksExecution: jest.fn(),
14+
}));
15+
16+
import {
17+
runPreTasksExecution,
18+
runPostTasksExecution,
19+
} from '../../project-graph/plugins/tasks-execution-hooks';
20+
21+
describe('Task Execution Hooks', () => {
22+
beforeEach(() => {
23+
jest.clearAllMocks();
24+
});
25+
26+
describe('handleRunPreTasksExecution', () => {
27+
it('should pass argv from context to runPreTasksExecution', async () => {
28+
const testArgv = ['node', 'nx', 'build', 'my-app'];
29+
const context: PreTasksExecutionContext = {
30+
id: 'test-id',
31+
workspaceRoot: '/test/workspace',
32+
nxJsonConfiguration: {},
33+
argv: testArgv,
34+
};
35+
36+
(runPreTasksExecution as jest.Mock).mockResolvedValue([]);
37+
38+
await handleRunPreTasksExecution(context);
39+
40+
expect(runPreTasksExecution).toHaveBeenCalledWith(context);
41+
expect(runPreTasksExecution).toHaveBeenCalledWith(
42+
expect.objectContaining({
43+
argv: testArgv,
44+
})
45+
);
46+
});
47+
48+
it('should handle different argv patterns (affected command)', async () => {
49+
const testArgv = ['node', 'nx', 'affected', '-t', 'build'];
50+
const context: PreTasksExecutionContext = {
51+
id: 'test-id-2',
52+
workspaceRoot: '/test/workspace',
53+
nxJsonConfiguration: {},
54+
argv: testArgv,
55+
};
56+
57+
(runPreTasksExecution as jest.Mock).mockResolvedValue([]);
58+
59+
await handleRunPreTasksExecution(context);
60+
61+
expect(runPreTasksExecution).toHaveBeenCalledWith(
62+
expect.objectContaining({
63+
argv: testArgv,
64+
})
65+
);
66+
});
67+
});
68+
69+
describe('handleRunPostTasksExecution', () => {
70+
it('should pass argv from context to runPostTasksExecution', async () => {
71+
const testArgv = ['node', 'nx', 'build', 'my-app'];
72+
const context: PostTasksExecutionContext = {
73+
id: 'test-id',
74+
workspaceRoot: '/test/workspace',
75+
nxJsonConfiguration: {},
76+
argv: testArgv,
77+
taskResults: {},
78+
startTime: Date.now(),
79+
endTime: Date.now() + 1000,
80+
};
81+
82+
(runPostTasksExecution as jest.Mock).mockResolvedValue(undefined);
83+
84+
await handleRunPostTasksExecution(context);
85+
86+
expect(runPostTasksExecution).toHaveBeenCalledWith(context);
87+
expect(runPostTasksExecution).toHaveBeenCalledWith(
88+
expect.objectContaining({
89+
argv: testArgv,
90+
})
91+
);
92+
});
93+
94+
it('should handle different argv patterns (affected command)', async () => {
95+
const testArgv = ['node', 'nx', 'affected', '-t', 'test'];
96+
const context: PostTasksExecutionContext = {
97+
id: 'test-id-2',
98+
workspaceRoot: '/test/workspace',
99+
nxJsonConfiguration: {},
100+
argv: testArgv,
101+
taskResults: {},
102+
startTime: Date.now(),
103+
endTime: Date.now() + 1000,
104+
};
105+
106+
(runPostTasksExecution as jest.Mock).mockResolvedValue(undefined);
107+
108+
await handleRunPostTasksExecution(context);
109+
110+
expect(runPostTasksExecution).toHaveBeenCalledWith(
111+
expect.objectContaining({
112+
argv: testArgv,
113+
})
114+
);
115+
});
116+
});
117+
});

0 commit comments

Comments
 (0)