@uipath/project-executor 1.200.0-preview.118

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/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # @uipath/project-executor
2
+
3
+ The common run/debug contract that every UiPath project type implements, so a
4
+ caller can run a project without knowing what kind of project it is.
5
+
6
+ Today each project type ships its own run/debug command with its own arguments,
7
+ its own result shape and its own status vocabulary:
8
+
9
+ | project type | command | model | status words |
10
+ |---|---|---|---|
11
+ | ProcessOrchestration (BPMN) | `uip maestro bpmn debug` | Studio Web upload → Orchestrator debug session → PIMS instance | `Completed`, `Faulted`, `Cancelled` |
12
+ | Flow | `uip maestro flow debug` | same | same |
13
+ | CaseManagement | `uip maestro case debug` | same | same |
14
+ | Agent | `uip agent debug` | serverless job, polled via Orchestrator Jobs | `Successful`, `Faulted`, `Stopped`, `Suspended` |
15
+ | API Workflow | `uip api-workflow run` | local, in-process | `Successful`, `Faulted` |
16
+ | RPA | external Studio backend | local or remote robot | its own set |
17
+
18
+ This package holds the types and interfaces those implementations share, so a
19
+ caller (for example a solution-level run command) can treat them uniformly.
20
+ It contains **no execution logic of its own** — only the contract and the
21
+ lifecycle around a run. Implementations are expected to be thin adapters over
22
+ the run/debug service each tool already owns, so a unified entry point and the
23
+ per-tool command cannot drift.
24
+
25
+ ## What is in here
26
+
27
+ **The contract**
28
+
29
+ - `ProjectExecutor` — abstract base each project type extends. Operations
30
+ default to "not supported", so a type opts into exactly what it can do.
31
+ `runAsync` is the only operation today; interactive debugging (breakpoints,
32
+ stepping) is deliberately out of scope. Its `protected
33
+ resolveDefaultEntryPoint` is the single hook for "what runs when the caller
34
+ named nothing": the base reads the first entry point declared in
35
+ `entry-points.json`, and an implementation overrides it to put its own
36
+ convention first (RPA prefers `project.json`'s `main`). Implementations call it
37
+ from `runAsync`, so a caller that passed an entry point pays for no file read.
38
+ - `IProjectExecutorFactory` — declares `supportedTypes` and creates an executor.
39
+ - `ExecutorsFactoryRepository` (+ the shared `executorsFactoryRepository`)
40
+ — maps project types to factories. Same shape and registration semantics as
41
+ `ToolsFactoryRepository` in `@uipath/solutionpackager-tool-core`: the first
42
+ factory to claim a type keeps it, re-registering the same factory *class* is a
43
+ silent no-op (two tools each bundling a copy is normal), and only a different
44
+ class claiming a taken type warns.
45
+
46
+ The shared instance hangs off a `Symbol.for` slot on `globalThis`, for the
47
+ same reason `toolsFactoryRepository` does: tool bundles **inline** this
48
+ package (builds externalize only `applicationinsights` plus sibling entry
49
+ points), so every tool — including one built in a different repo, such as
50
+ `@uipath/rpa-tool` — ships its own copy of the module. With plain module
51
+ state each copy would get an independent map and cross-tool registration
52
+ would silently do nothing.
53
+ - `ExecutorFactoryModule` / `isExecutorFactoryModule` / `EXECUTOR_FACTORY_EXPORT`
54
+ — the shape of a tool's entry point. See below.
55
+ - `ProjectRunner` — the project-level layer. It owns the lifecycle around one
56
+ run (resolve the factory for the type, create the executor, invoke it, dispose
57
+ it) and turns an unregistered type or a throwing implementation into a
58
+ `RunResult`. It passes options through untouched — resolving a default entry
59
+ point needs I/O against a per-type layout, so that is the executor's job, not
60
+ the runner's. Callers compose this instead of
61
+ touching the registry themselves, the same way `@uipath/solution-packager`
62
+ builds on `@uipath/project-packager`.
63
+
64
+ **Normalized results**
65
+
66
+ - `RunStatus` / `mapRunStatus` — folds the three status vocabularies above onto
67
+ one enum, so callers switch on one thing and an unrecognized backend status
68
+ degrades to `Unknown` rather than throwing.
69
+ - `RunResult` / `RunErrorCode` / `RunStep` — a superset of what any one type
70
+ produces. Identifiers differ per backend (`jobKey` for Orchestrator,
71
+ `instanceId`/`runId` for PIMS, none for the local executor), so all are
72
+ optional and only `status` is guaranteed.
73
+
74
+ **Options**
75
+
76
+ - `ProjectRunOptions` / `SolutionRunOptions`, `RunConnection`, `RunCallbacks`,
77
+ `SolutionProjectRef`, `RunTarget`.
78
+ - `RunTarget.Online` is the only implemented target. `Local` exists so the
79
+ choice is explicit at the call site rather than implied.
80
+ - `IExecutorLogger` — a minimal logging surface, structurally satisfied by
81
+ `logger` from `@uipath/common`, declared here so this package does not force a
82
+ logging implementation on consumers.
83
+
84
+ ## Implementing it
85
+
86
+ A tool exposes its factory from `src/executor-tool.ts`, which the host discovers
87
+ by file path. **A tool supplies a factory; it does not register anything.** The
88
+ module's only export is `createExecutorFactory`, and the host decides when, and
89
+ into which registry, to register what it hands back:
90
+
91
+ ```ts
92
+ // src/executor-tool.ts
93
+ import type { ExecutorFactoryModule } from "@uipath/project-executor";
94
+ import { MyExecutorFactory } from "./executor/my-executor-factory.js";
95
+
96
+ export const createExecutorFactory: ExecutorFactoryModule["createExecutorFactory"] =
97
+ () => new MyExecutorFactory();
98
+ ```
99
+
100
+ The host side imports it, checks the shape, and registers:
101
+
102
+ ```ts
103
+ const module = await import(executorToolPath);
104
+ if (!isExecutorFactoryModule(module)) {
105
+ throw new Error(
106
+ `${executorToolPath} does not export ${EXECUTOR_FACTORY_EXPORT}()`,
107
+ );
108
+ }
109
+ executorsFactoryRepository.registerProjectExecutorFactory(
110
+ module.createExecutorFactory(),
111
+ );
112
+ ```
113
+
114
+ This is the same split Studio Web uses for the packager tools, and it exists
115
+ because the alternative — a module that registers during import, as the older
116
+ `src/packager-tool.ts` convention does — is invisible at the call site and
117
+ order-dependent: a stray `import` anywhere silently changes which project types
118
+ look runnable, and the module cannot be imported without mutating global state,
119
+ which makes it awkward to test. With a factory supplier, a test calls
120
+ `createExecutorFactory()` and registers into its own
121
+ `new ExecutorsFactoryRepository()` instead of the process-wide one.
122
+
123
+ Two more rules worth knowing before adding a factory:
124
+
125
+ - **One factory per service, not per CLI verb.** A factory's `supportedTypes`
126
+ must list only the types its wrapped service actually handles. The `maestro`
127
+ verb, for instance, owns three project types but has three distinct debug
128
+ services that validate different project files — claiming all three from one
129
+ factory would route a project into the wrong validator.
130
+ - **`runAsync` should resolve, not reject, for expected failures.** A faulted
131
+ run is a `RunResult`, not an exception; reject only for programming errors.
package/dist/index.js ADDED
@@ -0,0 +1,252 @@
1
+ // src/models/run-options.ts
2
+ var RunTarget;
3
+ ((RunTarget2) => {
4
+ RunTarget2["Online"] = "Online";
5
+ RunTarget2["Local"] = "Local";
6
+ })(RunTarget ||= {});
7
+ // src/models/run-status.ts
8
+ var RunStatus;
9
+ ((RunStatus2) => {
10
+ RunStatus2["Pending"] = "Pending";
11
+ RunStatus2["Running"] = "Running";
12
+ RunStatus2["Succeeded"] = "Succeeded";
13
+ RunStatus2["Faulted"] = "Faulted";
14
+ RunStatus2["Cancelled"] = "Cancelled";
15
+ RunStatus2["Suspended"] = "Suspended";
16
+ RunStatus2["Unknown"] = "Unknown";
17
+ })(RunStatus ||= {});
18
+ var TERMINAL_RUN_STATUSES = [
19
+ "Succeeded" /* Succeeded */,
20
+ "Faulted" /* Faulted */,
21
+ "Cancelled" /* Cancelled */
22
+ ];
23
+ function isTerminalRunStatus(status) {
24
+ return TERMINAL_RUN_STATUSES.includes(status);
25
+ }
26
+ var RAW_STATUS_MAP = {
27
+ completed: "Succeeded" /* Succeeded */,
28
+ faulted: "Faulted" /* Faulted */,
29
+ cancelled: "Cancelled" /* Cancelled */,
30
+ canceled: "Cancelled" /* Cancelled */,
31
+ failed: "Faulted" /* Faulted */,
32
+ running: "Running" /* Running */,
33
+ inprogress: "Running" /* Running */,
34
+ pending: "Pending" /* Pending */,
35
+ successful: "Succeeded" /* Succeeded */,
36
+ stopped: "Cancelled" /* Cancelled */,
37
+ stopping: "Running" /* Running */,
38
+ suspended: "Suspended" /* Suspended */,
39
+ terminating: "Running" /* Running */,
40
+ new: "Pending" /* Pending */,
41
+ waitingforresource: "Pending" /* Pending */
42
+ };
43
+ function mapRunStatus(rawStatus) {
44
+ if (!rawStatus)
45
+ return "Unknown" /* Unknown */;
46
+ return RAW_STATUS_MAP[rawStatus.toLowerCase()] ?? "Unknown" /* Unknown */;
47
+ }
48
+
49
+ // src/models/run-result.ts
50
+ var RunErrorCode;
51
+ ((RunErrorCode2) => {
52
+ RunErrorCode2["None"] = "None";
53
+ RunErrorCode2["InvalidInput"] = "InvalidInput";
54
+ RunErrorCode2["UnsupportedProjectType"] = "UnsupportedProjectType";
55
+ RunErrorCode2["Unauthorized"] = "Unauthorized";
56
+ RunErrorCode2["PrepareFailed"] = "PrepareFailed";
57
+ RunErrorCode2["StartFailed"] = "StartFailed";
58
+ RunErrorCode2["ExecutionFailed"] = "ExecutionFailed";
59
+ RunErrorCode2["Timeout"] = "Timeout";
60
+ RunErrorCode2["Cancelled"] = "Cancelled";
61
+ RunErrorCode2["Unknown"] = "Unknown";
62
+ })(RunErrorCode ||= {});
63
+
64
+ class RunResult {
65
+ status;
66
+ errorCode;
67
+ rawStatus;
68
+ message;
69
+ jobKey;
70
+ instanceId;
71
+ runId;
72
+ traceId;
73
+ steps;
74
+ output;
75
+ url;
76
+ details;
77
+ constructor(status, errorCode = "None" /* None */, message) {
78
+ this.status = status;
79
+ this.errorCode = errorCode;
80
+ this.message = message;
81
+ }
82
+ get isSuccess() {
83
+ return this.status === "Succeeded" /* Succeeded */ && this.errorCode === "None" /* None */;
84
+ }
85
+ get isTerminal() {
86
+ return isTerminalRunStatus(this.status);
87
+ }
88
+ static succeeded(init = {}) {
89
+ return Object.assign(new RunResult("Succeeded" /* Succeeded */, "None" /* None */), init);
90
+ }
91
+ static failed(errorCode, message, init = {}) {
92
+ return Object.assign(new RunResult("Faulted" /* Faulted */, errorCode, message), init);
93
+ }
94
+ }
95
+ // src/services/executor-logger.ts
96
+ var noopExecutorLogger = {
97
+ info: () => {},
98
+ warn: () => {},
99
+ error: () => {}
100
+ };
101
+ // src/services/executor-tool-module.ts
102
+ var EXECUTOR_FACTORY_EXPORT = "createExecutorFactory";
103
+ function isExecutorFactoryModule(module) {
104
+ return typeof module === "object" && module !== null && typeof module[EXECUTOR_FACTORY_EXPORT] === "function";
105
+ }
106
+ // src/services/executors-factory-repository.ts
107
+ class ExecutorsFactoryRepository {
108
+ factoryMap = new Map;
109
+ onConflict;
110
+ constructor(onConflict) {
111
+ this.onConflict = onConflict ?? ((message) => console.warn(message));
112
+ }
113
+ registerProjectExecutorFactory(factory) {
114
+ for (const projectType of factory.supportedTypes) {
115
+ const existing = this.factoryMap.get(projectType);
116
+ if (existing) {
117
+ if (existing.constructor?.name !== factory.constructor?.name) {
118
+ this.onConflict(`Executor factory conflict for project type '${projectType}': ` + `'${existing.constructor?.name}' already registered, ` + `ignoring '${factory.constructor?.name}'.`);
119
+ }
120
+ continue;
121
+ }
122
+ this.factoryMap.set(projectType, factory);
123
+ }
124
+ }
125
+ canHandleProject(projectType) {
126
+ return this.factoryMap.has(projectType);
127
+ }
128
+ getProjectExecutorFactory(projectType) {
129
+ const factory = this.factoryMap.get(projectType);
130
+ if (!factory) {
131
+ throw new Error(`No executor is registered for project type '${projectType}'. ` + `Registered types: ${this.registeredTypes.join(", ") || "none"}.`);
132
+ }
133
+ return factory;
134
+ }
135
+ get registeredTypes() {
136
+ return [...this.factoryMap.keys()];
137
+ }
138
+ reset() {
139
+ this.factoryMap.clear();
140
+ }
141
+ }
142
+ var REGISTRY_KEY = Symbol.for("@uipath/project-executor/executorsFactoryRepository");
143
+ var _global = globalThis;
144
+ if (!_global[REGISTRY_KEY]) {
145
+ _global[REGISTRY_KEY] = new ExecutorsFactoryRepository;
146
+ }
147
+ var executorsFactoryRepository = _global[REGISTRY_KEY];
148
+ // src/services/project-executor.ts
149
+ class ProjectExecutor {
150
+ fileSystem;
151
+ logger;
152
+ constructor(fileSystem, logger) {
153
+ this.fileSystem = fileSystem;
154
+ this.logger = logger;
155
+ }
156
+ runAsync(_options, _cancellationToken) {
157
+ return Promise.resolve(RunResult.failed("UnsupportedProjectType" /* UnsupportedProjectType */, `${this.constructor.name} does not support run.`));
158
+ }
159
+ async resolveDefaultEntryPoint(projectPath) {
160
+ const entryPointsFile = this.fileSystem.path.join(projectPath, ENTRY_POINTS_FILE_NAME);
161
+ try {
162
+ const raw = await this.fileSystem.readFile(entryPointsFile, "utf-8");
163
+ if (!raw) {
164
+ return;
165
+ }
166
+ const declared = JSON.parse(raw).entryPoints;
167
+ return this.firstDeclaredEntryPoint(declared);
168
+ } catch (error) {
169
+ this.logger.warn(`Could not read the default entry point from ${entryPointsFile}: ${error instanceof Error ? error.message : String(error)}`);
170
+ return;
171
+ }
172
+ }
173
+ asEntryPoint(value) {
174
+ return typeof value === "string" && value.length > 0 ? value : undefined;
175
+ }
176
+ firstDeclaredEntryPoint(entryPoints) {
177
+ if (!Array.isArray(entryPoints)) {
178
+ return;
179
+ }
180
+ for (const entry of entryPoints) {
181
+ const filePath = this.asEntryPoint(entry?.filePath);
182
+ if (filePath) {
183
+ return filePath;
184
+ }
185
+ }
186
+ return;
187
+ }
188
+ dispose() {
189
+ return Promise.resolve();
190
+ }
191
+ }
192
+ var ENTRY_POINTS_FILE_NAME = "entry-points.json";
193
+ // src/services/project-runner.ts
194
+ class ProjectRunner {
195
+ fileSystem;
196
+ logger;
197
+ registry;
198
+ constructor(deps) {
199
+ this.fileSystem = deps.fileSystem;
200
+ this.logger = deps.logger ?? noopExecutorLogger;
201
+ this.registry = deps.registry ?? executorsFactoryRepository;
202
+ }
203
+ canRun(projectType) {
204
+ return this.registry.canHandleProject(projectType);
205
+ }
206
+ get runnableTypes() {
207
+ return this.registry.registeredTypes;
208
+ }
209
+ async runAsync(options, cancellationToken) {
210
+ const { projectType, name } = options.project;
211
+ if (!this.canRun(projectType)) {
212
+ const known = this.runnableTypes.join(", ") || "none";
213
+ return RunResult.failed("UnsupportedProjectType" /* UnsupportedProjectType */, `No executor is registered for project type '${projectType}' ` + `(project '${name}'). Runnable types: ${known}.`);
214
+ }
215
+ let executor;
216
+ try {
217
+ const factory = this.registry.getProjectExecutorFactory(projectType);
218
+ executor = await factory.createAsync(this.logger, this.fileSystem);
219
+ this.logger.info(`Running ${name} (${projectType}) on ${options.target}.`);
220
+ return await executor.runAsync(options, cancellationToken);
221
+ } catch (error) {
222
+ return RunResult.failed("Unknown" /* Unknown */, messageOf(error));
223
+ } finally {
224
+ try {
225
+ await executor?.dispose();
226
+ } catch (error) {
227
+ this.logger.warn(`Failed to dispose the ${projectType} executor: ${messageOf(error)}`);
228
+ }
229
+ }
230
+ }
231
+ }
232
+ function messageOf(error) {
233
+ return error instanceof Error ? error.message : String(error);
234
+ }
235
+ export {
236
+ noopExecutorLogger,
237
+ mapRunStatus,
238
+ isTerminalRunStatus,
239
+ isExecutorFactoryModule,
240
+ executorsFactoryRepository,
241
+ TERMINAL_RUN_STATUSES,
242
+ RunTarget,
243
+ RunStatus,
244
+ RunResult,
245
+ RunErrorCode,
246
+ ProjectRunner,
247
+ ProjectExecutor,
248
+ ExecutorsFactoryRepository,
249
+ EXECUTOR_FACTORY_EXPORT
250
+ };
251
+
252
+ //# debugId=F5B1472AD9D0537464756E2164756E21
@@ -0,0 +1,16 @@
1
+ export type { ProjectType } from "./models/project-type";
2
+ export type { ProjectRunOptions, RunCallbacks, RunConnection, SolutionProjectRef, SolutionRunOptions, } from "./models/run-options";
3
+ export { RunTarget } from "./models/run-options";
4
+ export type { RunStep } from "./models/run-result";
5
+ export { RunErrorCode, RunResult } from "./models/run-result";
6
+ export { isTerminalRunStatus, mapRunStatus, RunStatus, TERMINAL_RUN_STATUSES, } from "./models/run-status";
7
+ export type { IProjectExecutorFactory } from "./services/executor-factory";
8
+ export type { IExecutorLogger } from "./services/executor-logger";
9
+ export { noopExecutorLogger } from "./services/executor-logger";
10
+ export type { ExecutorFactoryModule } from "./services/executor-tool-module";
11
+ export { EXECUTOR_FACTORY_EXPORT, isExecutorFactoryModule, } from "./services/executor-tool-module";
12
+ export type { IExecutorsFactoryRepository, IExecutorsFactoryRepositoryConfigurator, } from "./services/executors-factory-repository";
13
+ export { ExecutorsFactoryRepository, executorsFactoryRepository, } from "./services/executors-factory-repository";
14
+ export { ProjectExecutor } from "./services/project-executor";
15
+ export type { ProjectRunnerDeps } from "./services/project-runner";
16
+ export { ProjectRunner } from "./services/project-runner";
@@ -0,0 +1,19 @@
1
+ /**
2
+ * A project type as it appears in a solution manifest — `Process`, `Agent`,
3
+ * `processOrchestration`, and whatever a future tool introduces.
4
+ *
5
+ * Deliberately a plain string rather than a union with an enum. Manifests are
6
+ * written by more than one producer with inconsistent casing (solution-sdk
7
+ * stores `ProcessOrchestration` lower-cased because that is what Studio Web
8
+ * expects), and a solution may legitimately name a type this package has never
9
+ * heard of. The alias this replaced — `ProjectTypes | string` from
10
+ * `@uipath/solutionpackager-tool-core` — collapsed to `string` regardless, so it
11
+ * validated nothing while making the *run* contract depend on the *packaging*
12
+ * contract and drag its dependencies along behind it.
13
+ *
14
+ * The real contract is elsewhere: an executor declares the exact strings it
15
+ * claims via `IProjectExecutorFactory.supportedTypes`, and the registry matches
16
+ * them verbatim. A caller reading a manifest is responsible for handing over the
17
+ * spelling those executors registered.
18
+ */
19
+ export type ProjectType = string;
@@ -0,0 +1,149 @@
1
+ import type { ProjectType } from "./project-type";
2
+ import type { RunStep } from "./run-result";
3
+ import type { RunStatus } from "./run-status";
4
+ /**
5
+ * Where the run executes. Only `Online` is implemented today; the meeting
6
+ * decision was online (serverless) first, with local/hybrid later, so the
7
+ * enum exists to keep that choice explicit at the call site rather than
8
+ * implied.
9
+ */
10
+ export declare enum RunTarget {
11
+ /** Serverless execution in the cloud. */
12
+ Online = "Online",
13
+ /** Local execution on the developer machine. Not implemented yet. */
14
+ Local = "Local"
15
+ }
16
+ /** Auth/tenant coordinates a project type needs to reach the backends. */
17
+ export interface RunConnection {
18
+ baseUrl: string;
19
+ accessToken: string;
20
+ organizationId: string;
21
+ organizationName: string;
22
+ tenantId?: string;
23
+ tenantName?: string;
24
+ /** Orchestrator folder (OrganizationUnitId) the job runs in. */
25
+ organizationUnitId?: number;
26
+ folderKey?: string;
27
+ /**
28
+ * Fully-qualified Orchestrator folder path, e.g. `Shared/Finance`.
29
+ *
30
+ * Some runtimes want the path rather than the id or key — the RPA backend
31
+ * takes it verbatim — and it cannot be derived from either without a lookup.
32
+ */
33
+ folderPath?: string;
34
+ }
35
+ /** Progress callbacks. All optional — a caller may just await the result. */
36
+ export interface RunCallbacks {
37
+ /** Fired when the normalized status changes. */
38
+ onStatusChange?: (status: RunStatus, steps?: RunStep[]) => void;
39
+ /** Fired for log lines emitted by the running project. */
40
+ onLog?: (message: string) => void;
41
+ }
42
+ /**
43
+ * A project inside the solution, as seen by the executor layer.
44
+ *
45
+ * `designId` is the id from the solution file (`.uipx` `Projects[].Id`);
46
+ * `cloudProjectId` is the Studio Web project id, which only exists once the
47
+ * solution has been synced to the cloud.
48
+ */
49
+ export interface SolutionProjectRef {
50
+ /** Project name as it appears in the solution. */
51
+ name: string;
52
+ projectType: ProjectType;
53
+ /** Absolute path to the project directory. */
54
+ projectPath: string;
55
+ /** Design-time id from the solution file. */
56
+ designId?: string;
57
+ /** Studio Web project id, once synced. */
58
+ cloudProjectId?: string;
59
+ }
60
+ /** Options for running one project. */
61
+ export interface ProjectRunOptions {
62
+ /** The project to run. */
63
+ project: SolutionProjectRef;
64
+ /**
65
+ * Entry point within the project (e.g. `Main.xaml`, `agent.json`, a
66
+ * workflow id). When omitted the implementation's default is used.
67
+ */
68
+ entryPoint?: string;
69
+ /** Input arguments passed to the run. */
70
+ inputArguments?: Record<string, unknown>;
71
+ target: RunTarget;
72
+ connection: RunConnection;
73
+ /** Studio Web solution id, once the solution has been synced. */
74
+ solutionId?: string;
75
+ /**
76
+ * The other projects in the same solution. Implementations that support JIT
77
+ * resolution use these to let the runtime resolve cross-project calls
78
+ * without publishing.
79
+ *
80
+ * Note for whoever builds the FPS `debug.master` map: Studio Web puts
81
+ * *every* JIT-enabled project into `processes`, including the lead one
82
+ * (the lead is identified separately by `debug.master.projectId`). So the
83
+ * map is built from `[project, ...siblingProjects]`, not from
84
+ * `siblingProjects` alone.
85
+ */
86
+ siblingProjects?: SolutionProjectRef[];
87
+ /** Milliseconds between status polls. */
88
+ pollIntervalMs?: number;
89
+ /** Overall budget for waiting on the run, in milliseconds. */
90
+ timeoutMs?: number;
91
+ callbacks?: RunCallbacks;
92
+ /**
93
+ * Whether to wait for the run to finish. Defaults to `true` when omitted.
94
+ *
95
+ * With `false` the implementation should return as soon as the run has
96
+ * started, leaving `status` non-terminal (`Pending`/`Running`). It is a real
97
+ * field rather than a `providerOptions` key because "start it and don't
98
+ * block" is meaningful for every project type, not one of them.
99
+ *
100
+ * Honouring it is best-effort: some backends only expose a blocking call
101
+ * (a local RPA run through Studio/Helm, for one), so an implementation that
102
+ * cannot start-without-waiting still returns a normal terminal `RunResult`
103
+ * rather than failing. Callers must therefore read {@link RunResult.isTerminal}
104
+ * instead of inferring the outcome from having passed `wait: false`.
105
+ */
106
+ wait?: boolean;
107
+ /**
108
+ * Run knobs that belong to one project type rather than to every type.
109
+ * Keys are owned and interpreted by the implementing executor.
110
+ *
111
+ * The escape hatch exists so type-specific switches do not have to be
112
+ * promoted into this shared shape one at a time. RPA, for example, has
113
+ * `logLevel`, `skipBuild`, `profiling` and `profilingMode`; none of those
114
+ * mean anything to a serverless agent run. It mirrors how
115
+ * `RunResult.details` already carries type-specific data on the way out.
116
+ *
117
+ * A knob that turns out to be genuinely cross-cutting should graduate to a
118
+ * real field instead of living here forever.
119
+ */
120
+ providerOptions?: Record<string, unknown>;
121
+ }
122
+ /** Options for running a whole solution. */
123
+ export interface SolutionRunOptions {
124
+ /** Path to the solution directory or `.uis`/`.uipx` file. */
125
+ solutionPath: string;
126
+ /**
127
+ * Name or design id of the lead project — the one whose entry point is
128
+ * executed. Required when the solution has more than one runnable project.
129
+ */
130
+ leadProject?: string;
131
+ entryPoint?: string;
132
+ inputArguments?: Record<string, unknown>;
133
+ target: RunTarget;
134
+ connection: RunConnection;
135
+ pollIntervalMs?: number;
136
+ timeoutMs?: number;
137
+ callbacks?: RunCallbacks;
138
+ /**
139
+ * When false, return as soon as the run starts instead of awaiting it.
140
+ * Forwarded to the lead project's executor — see
141
+ * {@link ProjectRunOptions.wait} for what implementations guarantee.
142
+ */
143
+ wait?: boolean;
144
+ /**
145
+ * Passed through to the lead project's executor. See
146
+ * {@link ProjectRunOptions.providerOptions}.
147
+ */
148
+ providerOptions?: Record<string, unknown>;
149
+ }
@@ -0,0 +1,67 @@
1
+ import { RunStatus } from "./run-status";
2
+ /** Why a run failed, for callers that need to branch without parsing messages. */
3
+ export declare enum RunErrorCode {
4
+ None = "None",
5
+ /** The project/solution path or entry point could not be resolved. */
6
+ InvalidInput = "InvalidInput",
7
+ /** No executor is registered for the project type. */
8
+ UnsupportedProjectType = "UnsupportedProjectType",
9
+ /** Authentication or authorization failed. */
10
+ Unauthorized = "Unauthorized",
11
+ /** The pre-run (JIT) preparation step failed. */
12
+ PrepareFailed = "PrepareFailed",
13
+ /** The job could not be started. */
14
+ StartFailed = "StartFailed",
15
+ /** The run started but ended in a non-success terminal state. */
16
+ ExecutionFailed = "ExecutionFailed",
17
+ /** Polling/waiting exceeded the allotted budget. */
18
+ Timeout = "Timeout",
19
+ /** Cancelled via the cancellation token. */
20
+ Cancelled = "Cancelled",
21
+ /** Anything not covered above. */
22
+ Unknown = "Unknown"
23
+ }
24
+ /** One execution step reported by a project type, when it exposes them. */
25
+ export interface RunStep {
26
+ id: string;
27
+ name?: string;
28
+ type?: string;
29
+ status: RunStatus;
30
+ startedAt?: string;
31
+ completedAt?: string;
32
+ }
33
+ /**
34
+ * Result of running a single project.
35
+ *
36
+ * Deliberately a superset of what any one project type produces: identifiers
37
+ * differ per backend (`jobKey` for Orchestrator, `instanceId`/`runId` for
38
+ * PIMS), so all are optional and only `status` is guaranteed.
39
+ */
40
+ export declare class RunResult {
41
+ status: RunStatus;
42
+ errorCode: RunErrorCode;
43
+ /** The project type's own status string, preserved for diagnostics. */
44
+ rawStatus?: string;
45
+ message?: string;
46
+ /** Orchestrator job key, when the run went through a job. */
47
+ jobKey?: string;
48
+ /** PIMS debug instance id, when the run went through debug-instances. */
49
+ instanceId?: string;
50
+ runId?: string;
51
+ /** Distributed-trace id, when the runtime supplied one. */
52
+ traceId?: string;
53
+ /** Per-step detail, when the project type reports it. */
54
+ steps?: RunStep[];
55
+ /** Output arguments, when the run produced any. */
56
+ output?: Record<string, unknown>;
57
+ /** Deep link to the run in Studio Web / Orchestrator, when known. */
58
+ url?: string;
59
+ /** Anything project-type-specific that does not fit above. */
60
+ details?: Record<string, unknown>;
61
+ constructor(status: RunStatus, errorCode?: RunErrorCode, message?: string);
62
+ /** True only for a successfully completed run. */
63
+ get isSuccess(): boolean;
64
+ get isTerminal(): boolean;
65
+ static succeeded(init?: Partial<RunResult>): RunResult;
66
+ static failed(errorCode: RunErrorCode, message?: string, init?: Partial<RunResult>): RunResult;
67
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Normalized terminal/intermediate status for a run across all project types.
3
+ *
4
+ * Each project type reports its own vocabulary — PIMS uses
5
+ * `Completed/Faulted/Cancelled`, Orchestrator uses
6
+ * `Successful/Faulted/Stopped/Suspended`, the local serverless executor uses
7
+ * its own — so implementations map onto this set and callers switch on one
8
+ * thing. `mapRunStatus` covers the values seen across the current tools.
9
+ */
10
+ export declare enum RunStatus {
11
+ /** The job/instance has not started executing yet. */
12
+ Pending = "Pending",
13
+ /** Executing. */
14
+ Running = "Running",
15
+ /** Finished successfully. */
16
+ Succeeded = "Succeeded",
17
+ /** Finished with an error. */
18
+ Faulted = "Faulted",
19
+ /** Stopped or cancelled before completing. */
20
+ Cancelled = "Cancelled",
21
+ /** Parked waiting on external input (e.g. an Action Center task). */
22
+ Suspended = "Suspended",
23
+ /** Reported a status this layer does not recognize — see `rawStatus`. */
24
+ Unknown = "Unknown"
25
+ }
26
+ /** Statuses from which no further transition is expected. */
27
+ export declare const TERMINAL_RUN_STATUSES: readonly RunStatus[];
28
+ export declare function isTerminalRunStatus(status: RunStatus): boolean;
29
+ /**
30
+ * Map a project-type-specific status string onto the normalized `RunStatus`.
31
+ * Unrecognized values become `Unknown` rather than throwing — a new backend
32
+ * status must not fail a run that otherwise succeeded.
33
+ */
34
+ export declare function mapRunStatus(rawStatus: string | undefined): RunStatus;
@@ -0,0 +1,21 @@
1
+ import type { IFileSystem } from "@uipath/filesystem";
2
+ import type { ProjectType } from "../models/project-type";
3
+ import type { IExecutorLogger } from "./executor-logger";
4
+ import type { ProjectExecutor } from "./project-executor";
5
+ /**
6
+ * Factory for creating a `ProjectExecutor`.
7
+ *
8
+ * Mirrors `IProjectToolFactory` from the packager family: a tool package
9
+ * exports one of these declaring which project types it handles, and the
10
+ * registry maps each type to it.
11
+ */
12
+ export interface IProjectExecutorFactory {
13
+ /** The project types this factory can run. */
14
+ readonly supportedTypes: readonly ProjectType[];
15
+ /**
16
+ * Create a executor instance.
17
+ * @param logger - logger for the operation
18
+ * @param fileSystem - file system instance
19
+ */
20
+ createAsync(logger: IExecutorLogger, fileSystem: IFileSystem): Promise<ProjectExecutor>;
21
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Minimal logging surface the executor layer needs.
3
+ *
4
+ * Structurally satisfied by `logger` from `@uipath/common`, so callers can pass
5
+ * that directly; declared here so `project-executor` does not force a logging
6
+ * implementation on consumers and so tests can substitute a spy.
7
+ */
8
+ export interface IExecutorLogger {
9
+ info(message: string, ...args: unknown[]): void;
10
+ warn(message: string, ...args: unknown[]): void;
11
+ error(message: string, ...args: unknown[]): void;
12
+ }
13
+ /** Discards everything. Useful as a default and in tests. */
14
+ export declare const noopExecutorLogger: IExecutorLogger;
@@ -0,0 +1,39 @@
1
+ import type { IProjectExecutorFactory } from "./executor-factory";
2
+ /** The export a tool's `executor-tool.ts` entry point must provide. */
3
+ export declare const EXECUTOR_FACTORY_EXPORT = "createExecutorFactory";
4
+ /**
5
+ * The shape of a tool's `src/executor-tool.ts` entry point.
6
+ *
7
+ * A tool **supplies a factory; it does not register anything.** The host imports
8
+ * the module, calls this, and registers the result into the one shared registry
9
+ * itself — the same division StudioWeb's `_registerPackagerTools` uses for the
10
+ * packager factories ("the packager packages no longer self-register on
11
+ * import").
12
+ *
13
+ * Registering on import, or handing the tool the registry to register into,
14
+ * both put the decision in the wrong place: the host ends up with entries it
15
+ * never asked for, import order starts to matter, and a stray `import` silently
16
+ * changes which project types look runnable. Exporting a factory keeps the
17
+ * tool's job "describe what I can run" and the host's job "decide what is
18
+ * registered".
19
+ *
20
+ * ```ts
21
+ * // src/executor-tool.ts
22
+ * import type { ExecutorFactoryModule } from "@uipath/project-executor";
23
+ * import { MyExecutorFactory } from "./executor/my-executor-factory.js";
24
+ *
25
+ * export const createExecutorFactory: ExecutorFactoryModule["createExecutorFactory"] =
26
+ * () => new MyExecutorFactory();
27
+ * ```
28
+ */
29
+ export interface ExecutorFactoryModule {
30
+ createExecutorFactory: () => IProjectExecutorFactory;
31
+ }
32
+ /**
33
+ * Narrow a dynamically imported module to {@link ExecutorFactoryModule}.
34
+ *
35
+ * Entry points are resolved by file path and imported dynamically, so the host
36
+ * has no compile-time guarantee about what came back. Checking here lets it fail
37
+ * with a message naming the missing export rather than a bare "not a function".
38
+ */
39
+ export declare function isExecutorFactoryModule(module: unknown): module is ExecutorFactoryModule;
@@ -0,0 +1,39 @@
1
+ import type { ProjectType } from "../models/project-type";
2
+ import type { IProjectExecutorFactory } from "./executor-factory";
3
+ /** Registration side of the repository. */
4
+ export interface IExecutorsFactoryRepositoryConfigurator {
5
+ registerProjectExecutorFactory(factory: IProjectExecutorFactory): void;
6
+ }
7
+ /** Lookup side of the repository. */
8
+ export interface IExecutorsFactoryRepository {
9
+ /** True when some factory claims this project type. */
10
+ canHandleProject(projectType: ProjectType): boolean;
11
+ /**
12
+ * Get the factory for a project type.
13
+ * @throws when no factory is registered for the type.
14
+ */
15
+ getProjectExecutorFactory(projectType: ProjectType): IProjectExecutorFactory;
16
+ /** Project types with a registered factory. */
17
+ readonly registeredTypes: readonly ProjectType[];
18
+ }
19
+ /**
20
+ * Maps project types to the factory that can run them.
21
+ *
22
+ * Deliberately the same shape as `ToolsFactoryRepository` in the packager
23
+ * family, including the re-registration semantics: a tool package that bundles
24
+ * its own copy of a factory may register the same type twice, which is a no-op,
25
+ * while a *different* factory claiming a taken type is a real conflict and
26
+ * warns rather than silently winning.
27
+ */
28
+ export declare class ExecutorsFactoryRepository implements IExecutorsFactoryRepository, IExecutorsFactoryRepositoryConfigurator {
29
+ private readonly factoryMap;
30
+ private readonly onConflict;
31
+ constructor(onConflict?: (message: string) => void);
32
+ registerProjectExecutorFactory(factory: IProjectExecutorFactory): void;
33
+ canHandleProject(projectType: ProjectType): boolean;
34
+ getProjectExecutorFactory(projectType: ProjectType): IProjectExecutorFactory;
35
+ get registeredTypes(): readonly ProjectType[];
36
+ /** Clear all registrations. Tests only. */
37
+ reset(): void;
38
+ }
39
+ export declare const executorsFactoryRepository: ExecutorsFactoryRepository;
@@ -0,0 +1,77 @@
1
+ import type { IFileSystem } from "@uipath/filesystem";
2
+ import type { ProjectRunOptions } from "../models/run-options";
3
+ import { RunResult } from "../models/run-result";
4
+ import type { IExecutorLogger } from "./executor-logger";
5
+ /**
6
+ * The contract every project type implements so `uip solution run` behaves the
7
+ * same regardless of what is inside the solution.
8
+ *
9
+ * Mirrors `ProjectTool` from the packager family: an abstract base whose
10
+ * operations default to "not supported", so a project type opts in to exactly
11
+ * the operations it can do. Today that is `runAsync` only — debug (breakpoints,
12
+ * stepping) is deliberately out of scope, see docs/solution-run-debug-design.md.
13
+ *
14
+ * Implementations are thin adapters over each tool's existing run/debug
15
+ * service; they should not re-implement execution.
16
+ */
17
+ export declare abstract class ProjectExecutor {
18
+ protected readonly fileSystem: IFileSystem;
19
+ protected readonly logger: IExecutorLogger;
20
+ constructor(fileSystem: IFileSystem, logger: IExecutorLogger);
21
+ /**
22
+ * Run the project and, unless the caller opts out, wait for it to finish.
23
+ *
24
+ * Implementations must resolve with a `RunResult` for expected failures
25
+ * (a faulted run is a result, not an exception) and reject only for
26
+ * programming errors.
27
+ */
28
+ runAsync(_options: ProjectRunOptions, _cancellationToken?: AbortSignal): Promise<RunResult>;
29
+ /**
30
+ * The entry point to run when the caller named none.
31
+ *
32
+ * The one hook for "what runs by default". The shared implementation returns
33
+ * the first entry point the project declares in `entry-points.json` — the
34
+ * same file, shape and "first one wins" rule the packager uses to report a
35
+ * packaged project's entry point, so a project runs the workflow it publishes
36
+ * as its own.
37
+ *
38
+ * `protected`, and called by an implementation's own `runAsync` rather than
39
+ * by `ProjectRunner`. Two reasons: resolving a real default needs I/O against
40
+ * a layout only the project type understands, and only `runAsync` knows
41
+ * whether it needs one at all — a caller that passed an entry point should
42
+ * not pay for a file read. It also keeps one method where there were nearly
43
+ * two: a synchronous `defaultEntryPoint()` could only ever answer for a fixed
44
+ * name, so every project type whose default lives in a file had to leave it
45
+ * returning `undefined` and do the real work elsewhere.
46
+ *
47
+ * Override to put a project type's own convention first and delegate here for
48
+ * the rest — the RPA executor prefers `project.json`'s `main` and falls back
49
+ * to this. Best-effort by contract: a missing or malformed file resolves to
50
+ * `undefined` rather than failing a run the caller may have given an explicit
51
+ * entry point for.
52
+ *
53
+ * @param projectPath Absolute path to the project directory.
54
+ */
55
+ protected resolveDefaultEntryPoint(projectPath: string): Promise<string | undefined>;
56
+ /**
57
+ * A declared entry-point path, when it is a usable one.
58
+ *
59
+ * `private`: it belongs to the default discovery below, not to the contract
60
+ * and not to overrides. A project type reading a field of its own vets it
61
+ * against its own rules.
62
+ */
63
+ private asEntryPoint;
64
+ /**
65
+ * The first usable `filePath` in `entry-points.json`'s list.
66
+ *
67
+ * Scans rather than indexing `[0]` so one malformed element does not hide the
68
+ * rest of a list that does name a workflow.
69
+ *
70
+ * `private` for the same reason as {@link asEntryPoint}: this is how the
71
+ * default discovery reads *this* file. A project type with a list of its own
72
+ * elsewhere reads that itself.
73
+ */
74
+ private firstDeclaredEntryPoint;
75
+ /** Release resources. Override when the implementation holds any. */
76
+ dispose(): Promise<void>;
77
+ }
@@ -0,0 +1,42 @@
1
+ import type { IFileSystem } from "@uipath/filesystem";
2
+ import type { ProjectType } from "../models/project-type";
3
+ import type { ProjectRunOptions } from "../models/run-options";
4
+ import { RunResult } from "../models/run-result";
5
+ import type { IExecutorLogger } from "./executor-logger";
6
+ import type { IExecutorsFactoryRepository } from "./executors-factory-repository";
7
+ export interface ProjectRunnerDeps {
8
+ fileSystem: IFileSystem;
9
+ logger?: IExecutorLogger;
10
+ /** Defaults to the shared registry. Injectable for tests. */
11
+ registry?: IExecutorsFactoryRepository;
12
+ }
13
+ /**
14
+ * Runs a single project by resolving the executor registered for its type.
15
+ *
16
+ * This is the project-level layer: it owns the lifecycle around a run — look up
17
+ * the executor, create it, invoke it, dispose it, and turn an implementation
18
+ * that throws into a failed `RunResult` — so no caller has to repeat that.
19
+ *
20
+ * Callers compose this rather than touching the registry themselves, the same
21
+ * way `@uipath/solution-packager` builds on `@uipath/project-packager` instead of
22
+ * reaching for tool factories directly.
23
+ */
24
+ export declare class ProjectRunner {
25
+ private readonly fileSystem;
26
+ private readonly logger;
27
+ private readonly registry;
28
+ constructor(deps: ProjectRunnerDeps);
29
+ /** True when some executor claims this project type. */
30
+ canRun(projectType: ProjectType): boolean;
31
+ /** Project types that can be run in this process. */
32
+ get runnableTypes(): readonly ProjectType[];
33
+ /**
34
+ * Run one project to completion.
35
+ *
36
+ * Resolves as a failed `RunResult` rather than rejecting for anything the
37
+ * caller can act on — an unregistered project type, or an executor that
38
+ * threw instead of returning a result. A caller reporting a run outcome
39
+ * should not have to also handle exceptions for ordinary failures.
40
+ */
41
+ runAsync(options: ProjectRunOptions, cancellationToken?: AbortSignal): Promise<RunResult>;
42
+ }
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@uipath/project-executor",
3
+ "license": "MIT",
4
+ "version": "1.200.0-preview.118",
5
+ "description": "Common run contract implemented by every UiPath project type - the shared ProjectExecutor interface behind 'uip solution run'.",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/UiPath/cli.git",
9
+ "directory": "packages/executor/project-executor"
10
+ },
11
+ "publishConfig": {
12
+ "registry": "https://registry.npmjs.org/"
13
+ },
14
+ "keywords": [
15
+ "uipath",
16
+ "executor",
17
+ "run",
18
+ "solution"
19
+ ],
20
+ "type": "module",
21
+ "main": "./dist/index.js",
22
+ "types": "./dist/src/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/src/index.d.ts",
26
+ "default": "./dist/index.js"
27
+ }
28
+ },
29
+ "files": [
30
+ "dist"
31
+ ],
32
+ "dependencies": {
33
+ "@uipath/filesystem": "1.200.0"
34
+ },
35
+ "gitHead": "bc87399d98869787498783b3b9383df4426fd896"
36
+ }