diff --git a/.github/workflows/run-coder-eval.yml b/.github/workflows/run-coder-eval.yml index 67877d8595..35645e637b 100644 --- a/.github/workflows/run-coder-eval.yml +++ b/.github/workflows/run-coder-eval.yml @@ -321,19 +321,29 @@ jobs: docker pull "ghcr.io/uipath/coder-eval-agent:${CE_IMAGE_TAG}" # Build the skills extension image (`@uipath/cli`, its tool plugins, and - # `@uipath/flow-sdk` on top of coder-eval-agent). Matches + # `@uipath/maestro-builder-sdk` on top of coder-eval-agent). Matches # smoke-skills.yml + daily.sh. - name: Build skills Docker image env: DOCKER_BUILDKIT: "1" NPM_AUTH_TOKEN: ${{ secrets.GH_PACKAGES_TOKEN || secrets.GH_PAT }} CE_IMAGE_TAG: ${{ needs.partition.outputs.image_tag }} + FLOW_SDK_PKG_NAME: ${{ vars.FLOW_SDK_PKG_NAME }} run: | + # The image's ARG default is the post-rename package name; override it + # only when a repo var pins a different one. The pre-rename baseline + # needs BOTH this and FLOW_SDK_VERSION (see tests/docker/Dockerfile) — + # pinning the version alone asks for a version the new name never had. + pkg_name_arg=() + if [ -n "${FLOW_SDK_PKG_NAME:-}" ]; then + pkg_name_arg=(--build-arg "FLOW_SDK_PKG_NAME=${FLOW_SDK_PKG_NAME}") + fi docker build \ --build-arg CODER_EVAL_IMAGE="ghcr.io/uipath/coder-eval-agent:${CE_IMAGE_TAG}" \ --secret id=npm_auth_token,env=NPM_AUTH_TOKEN \ --build-arg CLI_VERSION="${{ inputs.cli_version }}" \ --build-arg FLOW_SDK_VERSION="${{ vars.FLOW_SDK_VERSION || 'latest' }}" \ + ${pkg_name_arg[@]+"${pkg_name_arg[@]}"} \ -t skills-image:latest \ -f tests/docker/Dockerfile \ . diff --git a/.github/workflows/smoke-skills.yml b/.github/workflows/smoke-skills.yml index 70027784fd..dba025918e 100644 --- a/.github/workflows/smoke-skills.yml +++ b/.github/workflows/smoke-skills.yml @@ -377,7 +377,7 @@ jobs: # Build custom smoke image extending coder-eval-agent with @uipath/cli, # every whitelisted tool plugin from GitHub Packages @dev, and the latest - # published @uipath/flow-sdk (see tests/docker/Dockerfile). Every push to + # published @uipath/maestro-builder-sdk (see tests/docker/Dockerfile). Every push to # `cli/main` publishes a `-dev.` prerelease under the `dev` dist-tag # (cli PR #2650 renamed the old `alpha` tag → `dev` on 2026-06-26), so # smoke tracks main — not stale `latest` from public npm (which lags by ~2 @@ -386,11 +386,21 @@ jobs: env: DOCKER_BUILDKIT: "1" NPM_AUTH_TOKEN: ${{ secrets.GH_PACKAGES_TOKEN || secrets.GH_PAT }} + FLOW_SDK_PKG_NAME: ${{ vars.FLOW_SDK_PKG_NAME }} run: | + # The image's ARG default is the post-rename package name; override it + # only when a repo var pins a different one. The pre-rename baseline + # needs BOTH this and FLOW_SDK_VERSION (see tests/docker/Dockerfile) — + # pinning the version alone asks for a version the new name never had. + pkg_name_arg=() + if [ -n "${FLOW_SDK_PKG_NAME:-}" ]; then + pkg_name_arg=(--build-arg "FLOW_SDK_PKG_NAME=${FLOW_SDK_PKG_NAME}") + fi docker build \ --build-arg CODER_EVAL_IMAGE=ghcr.io/uipath/coder-eval-agent:${{ steps.ceref.outputs.version }} \ --secret id=npm_auth_token,env=NPM_AUTH_TOKEN \ --build-arg FLOW_SDK_VERSION="${{ vars.FLOW_SDK_VERSION || 'latest' }}" \ + ${pkg_name_arg[@]+"${pkg_name_arg[@]}"} \ -t skills-image:latest \ -f tests/docker/Dockerfile \ . diff --git a/preview/skills/uipath-maestro-bpmn/SKILL.md b/preview/skills/uipath-maestro-bpmn/SKILL.md index c5a78b16b1..d7cb590dde 100644 --- a/preview/skills/uipath-maestro-bpmn/SKILL.md +++ b/preview/skills/uipath-maestro-bpmn/SKILL.md @@ -1,11 +1,11 @@ --- name: uipath-maestro-bpmn -description: "TRIGGER for authoring structural-core UiPath Maestro BPMN as `.bpmn.ts` with the TypeScript builder SDK (`@uipath/flow-sdk/bpmn`) and running the `uip maestro bpmn` check/compile/format/validate loop. Covers events, gateways, tasks, sub-processes, sequence flows, bindings, static rules, and semantic `.bpmn` output. Flow builder authoring → uipath-maestro-flow; case plans → uipath-maestro-case. DO NOT TRIGGER for registry-backed typed BPMN nodes beyond the structural core." +description: "TRIGGER for authoring structural-core UiPath Maestro BPMN as `.bpmn.ts` with the TypeScript builder SDK (`@uipath/maestro-builder-sdk/bpmn`) and running the `uip maestro bpmn` check/compile/format/validate loop. Covers events, gateways, tasks, sub-processes, sequence flows, bindings, static rules, and semantic `.bpmn` output. Flow builder authoring → uipath-maestro-flow; case plans → uipath-maestro-case. DO NOT TRIGGER for registry-backed typed BPMN nodes beyond the structural core." allowed-tools: Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion --- @@ -26,7 +26,7 @@ need, then let TypeScript and `bpmn check` provide the detailed contract. product tooling require. Run it inside a solution to join that solution; run it outside one and a parent `Solution` is scaffolded around it. 2. Keep `.bpmn.ts` at the workspace root, beside `package.json`. -3. Import from `@uipath/flow-sdk/bpmn` and default-export a chain ending in `.build()`. +3. Import from `@uipath/maestro-builder-sdk/bpmn` and default-export a chain ending in `.build()`. 4. Start from the closest staged `examples/*.bpmn.ts`. 5. Run `uip maestro bpmn check .bpmn.ts --source` after structural changes. 6. Compile **into the scaffolded project**, format only when layout is needed, and run @@ -55,7 +55,7 @@ need, then let TypeScript and `bpmn check` provide the detailed contract. ## Minimal shape ```ts -import { bpmn } from '@uipath/flow-sdk/bpmn'; +import { bpmn } from '@uipath/maestro-builder-sdk/bpmn'; export default bpmn('notify') .name('Notify') diff --git a/preview/skills/uipath-maestro-bpmn/examples/InvoiceApproval.bpmn.ts b/preview/skills/uipath-maestro-bpmn/examples/InvoiceApproval.bpmn.ts index 5073f2a473..b78fb6db10 100644 --- a/preview/skills/uipath-maestro-bpmn/examples/InvoiceApproval.bpmn.ts +++ b/preview/skills/uipath-maestro-bpmn/examples/InvoiceApproval.bpmn.ts @@ -38,7 +38,7 @@ * `connectionId` added, which the source export omitted. It compiles, and that is what * it is for — reading, and rendering the canvas diagram on this page. */ -import { bpmn, errorSchema, errorSchemaWithResponse } from "@uipath/flow-sdk/bpmn"; +import { bpmn, errorSchema, errorSchemaWithResponse } from "@uipath/maestro-builder-sdk/bpmn"; export default bpmn("Process_1") .name("Process") diff --git a/preview/skills/uipath-maestro-bpmn/examples/NotifyChannel.bpmn.ts b/preview/skills/uipath-maestro-bpmn/examples/NotifyChannel.bpmn.ts index e72759e634..b9123409c5 100644 --- a/preview/skills/uipath-maestro-bpmn/examples/NotifyChannel.bpmn.ts +++ b/preview/skills/uipath-maestro-bpmn/examples/NotifyChannel.bpmn.ts @@ -14,7 +14,7 @@ * `uip maestro bpmn compile NotifyChannel.bpmn.ts -o NotifyChannel.bpmn * --library "$FLOW_SDK_LIBRARY_JSON"`, then `uip maestro bpmn validate`. */ -import { bpmn } from '@uipath/flow-sdk/bpmn'; +import { bpmn } from '@uipath/maestro-builder-sdk/bpmn'; export default bpmn('notify-channel') .name('NotifyChannel') diff --git a/preview/skills/uipath-maestro-bpmn/references/api.md b/preview/skills/uipath-maestro-bpmn/references/api.md index 5fea48113a..aca144b626 100644 --- a/preview/skills/uipath-maestro-bpmn/references/api.md +++ b/preview/skills/uipath-maestro-bpmn/references/api.md @@ -5,7 +5,7 @@ # BPMN SDK API -Exact `@uipath/flow-sdk/bpmn` authoring signatures and option shapes, from the public +Exact `@uipath/maestro-builder-sdk/bpmn` authoring signatures and option shapes, from the public declarations. Signatures, fields, optionality, and declaration comments are generated from the built types; longer tutorials stay in the node references. @@ -16,8 +16,8 @@ generated from the built types; longer tutorials stay in the node references. > names a type whose members or rules you need, read the declaration: > > ```bash -> grep -rln "declare function err" node_modules/@uipath/flow-sdk/dist --include="*.d.ts" -> # -> node_modules/@uipath/flow-sdk/dist/core/expr.d.ts +> grep -rln "declare function err" node_modules/@uipath/maestro-builder-sdk/dist --include="*.d.ts" +> # -> node_modules/@uipath/maestro-builder-sdk/dist/core/expr.d.ts > ``` > > Grep the **`.d.ts`**, never `dist/*.js` — the compiled JavaScript carries no diff --git a/preview/skills/uipath-maestro-case/SKILL.md b/preview/skills/uipath-maestro-case/SKILL.md index f977fac415..d92bfdf1c6 100644 --- a/preview/skills/uipath-maestro-case/SKILL.md +++ b/preview/skills/uipath-maestro-case/SKILL.md @@ -1,11 +1,11 @@ --- name: uipath-maestro-case -description: "TRIGGER for authoring UiPath Maestro Case plans as `.case.ts` with the reference-mode TypeScript builder SDK (`@uipath/flow-sdk/case`), compiling to `caseplan.json`, and running the `uip maestro case` check/compile/validate loop. Covers stages, tasks, rules, bindings, published-resource references, and brownfield decompile/edit/recompile. Flow builder authoring → uipath-maestro-flow; structural-core BPMN → uipath-maestro-bpmn. DO NOT TRIGGER for C#/XAML automation → uipath-rpa." +description: "TRIGGER for authoring UiPath Maestro Case plans as `.case.ts` with the reference-mode TypeScript builder SDK (`@uipath/maestro-builder-sdk/case`), compiling to `caseplan.json`, and running the `uip maestro case` check/compile/validate loop. Covers stages, tasks, rules, bindings, published-resource references, and brownfield decompile/edit/recompile. Flow builder authoring → uipath-maestro-flow; structural-core BPMN → uipath-maestro-bpmn. DO NOT TRIGGER for C#/XAML automation → uipath-rpa." allowed-tools: Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion --- -# Flow SDK API +# Maestro Builder SDK — Flow API -Exact `@uipath/flow-sdk` authoring signatures and option shapes, from the public +Exact `@uipath/maestro-builder-sdk` authoring signatures and option shapes, from the public declarations. Signatures, fields, optionality, and declaration comments are generated from the built types; longer tutorials stay in the node references. @@ -16,8 +16,8 @@ generated from the built types; longer tutorials stay in the node references. > names a type whose members or rules you need, read the declaration: > > ```bash -> grep -rln "declare function err" node_modules/@uipath/flow-sdk/dist --include="*.d.ts" -> # -> node_modules/@uipath/flow-sdk/dist/core/expr.d.ts +> grep -rln "declare function err" node_modules/@uipath/maestro-builder-sdk/dist --include="*.d.ts" +> # -> node_modules/@uipath/maestro-builder-sdk/dist/core/expr.d.ts > ``` > > Grep the **`.d.ts`**, never `dist/*.js` — the compiled JavaScript carries no @@ -33,7 +33,7 @@ generated from the built types; longer tutorials stay in the node references. **Builders** — [FlowBuilder](#flowbuilder-class) · [StepList](#steplist-class) · [ArmBuilder](#armbuilder-class) -**Option shapes** — [TriggerOptions](#triggeroptions-interface) · [EventSubscription](#eventsubscription-interface) · [ScheduledInputs](#scheduledinputs-interface) · [HttpInputs](#httpinputs-type) · [ScriptInputs](#scriptinputs-interface) · [TransformInputs](#transforminputs-interface) · [HitlInputs](#hitlinputs-interface) · [SummarizeInputs](#summarizeinputs-interface) · [BatchTransformInputs](#batchtransforminputs-interface) · [IxpExtractInputs](#ixpextractinputs-interface) · [DelayInputs](#delayinputs-type) · [RpaWorkflowInputs](#rpaworkflowinputs-interface) · [ApiWorkflowInputs](#apiworkflowinputs-interface) · [PublishedFunctionInputs](#publishedfunctioninputs-interface) · [SendMessageInputs](#sendmessageinputs-interface) · [WaitForMessageInputs](#waitformessageinputs-interface) · [ConversationContextInputs](#conversationcontextinputs-interface) · [CreateOutgoingCallInputs](#createoutgoingcallinputs-interface) · [EndCallInputs](#endcallinputs-interface) · [VoiceAgentInputs](#voiceagentinputs-interface) · [ConversationalAgentInputs](#conversationalagentinputs-interface) · [AgenticProcessInputs](#agenticprocessinputs-type) · [AgentInputs](#agentinputs-interface) · [InlineAgentInputs](#inlineagentinputs-interface) · [DocumentClassifyInputs](#documentclassifyinputs-interface) · [DynamicExtractInputs](#dynamicextractinputs-interface) · [DataFabricReadInputs](#datafabricreadinputs-interface) · [DataFabricUpdateInputs](#datafabricupdateinputs-interface) · [QueueItemInputs](#queueiteminputs-interface) · [ConnectorOpts](#connectoropts-interface) · [NodeOptions](#nodeoptions-interface) · [HttpInputsBase](#httpinputsbase-interface) · [DocValidationInputs](#docvalidationinputs-interface) · [AgenticProcessInputsBase](#agenticprocessinputsbase-interface) · [LoopOptions](#loopoptions-interface) · [DoWhileOptions](#dowhileoptions-interface) +**Option shapes** — [TriggerOptions](#triggeroptions-interface) · [EventSubscription](#eventsubscription-interface) · [ScheduledInputs](#scheduledinputs-interface) · [HttpInputs](#httpinputs-type) · [ScriptInputs](#scriptinputs-interface) · [TransformInputs](#transforminputs-interface) · [HitlInputs](#hitlinputs-interface) · [SummarizeInputs](#summarizeinputs-interface) · [BatchTransformInputs](#batchtransforminputs-interface) · [IxpExtractInputs](#ixpextractinputs-interface) · [DelayInputs](#delayinputs-type) · [RpaWorkflowInputs](#rpaworkflowinputs-interface) · [ApiWorkflowInputs](#apiworkflowinputs-interface) · [PublishedFunctionInputs](#publishedfunctioninputs-interface) · [SendMessageInputs](#sendmessageinputs-interface) · [WaitForMessageInputs](#waitformessageinputs-interface) · [ConversationContextInputs](#conversationcontextinputs-interface) · [CreateOutgoingCallInputs](#createoutgoingcallinputs-interface) · [EndCallInputs](#endcallinputs-interface) · [VoiceAgentInputs](#voiceagentinputs-interface) · [ConversationalAgentInputs](#conversationalagentinputs-interface) · [AgenticProcessInputs](#agenticprocessinputs-type) · [AgentInputs](#agentinputs-interface) · [InlineAgentInputs](#inlineagentinputs-interface) · [DocumentClassifyInputs](#documentclassifyinputs-interface) · [DynamicExtractInputs](#dynamicextractinputs-interface) · [DataFabricReadInputs](#datafabricreadinputs-interface) · [DataFabricUpdateInputs](#datafabricupdateinputs-interface) · [QueueItemInputs](#queueiteminputs-interface) · [ConnectorOpts](#connectoropts-interface) · [NodeOptions](#nodeoptions-interface) · [HttpInputsBase](#httpinputsbase-interface) · [DocValidationInputs](#docvalidationinputs-interface) · [AgenticProcessInputsBase](#agenticprocessinputsbase-interface) · [LoopOptions](#loopoptions-interface) · [DoWhileOptions](#dowhileoptions-interface) · [ReturnOptions](#returnoptions-interface) **Supporting types** — [ContributionDiagnostic](#contributiondiagnostic-interface) · [DefinitionReference](#definitionreference-type) · [ContributionContext](#contributioncontext-interface) · [BindingContribution](#bindingcontribution-interface) · [NodeContribution](#nodecontribution-interface) · [FlowNode](#flownode-class) · [FlowAction](#flowaction-class) · [FlowTrigger](#flowtrigger-class) · [FlowResource](#flowresource-class) · [rawNode](#rawnode-function) · [TriggerSpec](#triggerspec-type) · [ChildFlow](#childflow-type) · [Expr](#expr-class) · [SubflowSpec](#subflowspec-interface) · [ActionSpec](#actionspec-type) · [ErrorEnvelopeField](#errorenvelopefield-type) · [RawReference](#rawreference-type) · [EventFilter](#eventfilter-type) · [ScheduleEvery](#scheduleevery-type) · [FlowLayout](#flowlayout-interface) · [StickyNote](#stickynote-interface) · [TypeDesc](#typedesc-type) · [VarSpec](#varspec-interface) · [BuiltFlow](#builtflow-interface) · [ScriptReturns](#scriptreturns-type) · [TransformVariant](#transformvariant-type) · [TransformOperation](#transformoperation-type) · [HitlVariant](#hitlvariant-type) · [AppRef](#appref-interface) · [FormField](#formfield-type) · [Outcome](#outcome-type) · [HitlRecipient](#hitlrecipient-interface) · [OutputColumn](#outputcolumn-interface) · [ReturnFieldType](#returnfieldtype-type) · [VoiceSettings](#voicesettings-interface) · [ConversationalAgentSettings](#conversationalagentsettings-interface) · [AgentGuardrail](#agentguardrail-type) · [AgenticProcessCompletion](#agenticprocesscompletion-type) · [AgentLocation](#agentlocation-type) · [AgentFlavour](#agentflavour-type) · [InlineAgentFieldType](#inlineagentfieldtype-type) · [AgentMemoryRef](#agentmemoryref-interface) · [ContextIndexRef](#contextindexref-interface) · [ToolRef](#toolref-type) · [EscalationRef](#escalationref-interface) · [DataFabricFilter](#datafabricfilter-interface) · [QueuePriority](#queuepriority-type) · [SchedulePreset](#schedulepreset-type) · [Step](#step-type) · [FlowActionSpec](#flowactionspec-type) · [CaseValue](#casevalue-type) · [NodeLayout](#nodelayout-interface) · [EdgeRoute](#edgeroute-interface) · [StickyNoteColor](#stickynotecolor-type) · [VarDecl](#vardecl-interface) · [BuiltEntryPoint](#builtentrypoint-interface) · [HttpBranch](#httpbranch-interface) · [ScriptReturnType](#scriptreturntype-type) · [FilterRule](#filterrule-interface) · [FilterMatch](#filtermatch-type) · [FieldMapping](#fieldmapping-interface) · [Aggregation](#aggregation-interface) · [ShownField](#shownfield-interface) · [AskedField](#askedfield-interface) · [InOutField](#inoutfield-interface) · [HitlChannel](#hitlchannel-type) · [HitlAssigneeType](#hitlassigneetype-type) · [HitlConnection](#hitlconnection-interface) · [CustomGuardrail](#customguardrail-interface) · [BuiltInValidatorGuardrail](#builtinvalidatorguardrail-interface) · [BuiltinToolRef](#builtintoolref-interface) · [ConnectorToolRef](#connectortoolref-interface) · [ProcessToolRef](#processtoolref-interface) · [IxpToolRef](#ixptoolref-interface) · [McpToolRef](#mcptoolref-interface) · [RemoteA2aToolRef](#remotea2atoolref-interface) · [ClientSideToolRef](#clientsidetoolref-interface) · [HttpRequestToolRef](#httprequesttoolref-interface) · [SwitchArm](#switcharm-interface) · [FilterCondition](#filtercondition-type) · [Transformation](#transformation-type) · [AggregationOperation](#aggregationoperation-type) · [FormFieldType](#formfieldtype-type) · [GuardrailSelector](#guardrailselector-interface) · [GuardrailAction](#guardrailaction-type) · [GuardrailRule](#guardrailrule-type) · [BuiltinToolName](#builtintoolname-type) · [ProcessToolKind](#processtoolkind-type) · [GuardrailScope](#guardrailscope-type) · [GuardrailFieldReference](#guardrailfieldreference-interface) · [GuardrailFieldSelector](#guardrailfieldselector-type) @@ -70,7 +70,7 @@ export declare function manual(): TriggerSpec; * Start the flow when a connector event fires — e.g. an email arriving in a * mailbox folder. */ -export declare function onEvent>(descriptor: TriggerDescriptor, opts?: TriggerOptions): TriggerSpec; +export declare function onEvent(descriptor: TriggerDescriptor, opts?: TriggerOptions): TriggerSpec; /** Stringly form, for an event with no prepared module. */ export declare function onEvent(subscription: EventSubscription): TriggerSpec; @@ -509,7 +509,7 @@ Behavior and worked examples: [connector-params.md](connector-params.md). * the typed form, where a generated trigger descriptor identifies the event: * `waitForEvent(EmailReceived, { where: { … } })`. */ -export declare function waitForEvent>(descriptor: TriggerDescriptor, opts?: TriggerOptions): ActionSpec; +export declare function waitForEvent(descriptor: TriggerDescriptor, opts?: TriggerOptions): ActionSpec; /** Stringly form, for an event with no prepared module. */ export declare function waitForEvent(subscription: EventSubscription): ActionSpec; @@ -828,6 +828,19 @@ export declare function lookup(descriptor: D, field: F): LookupBuilder; + +/** Begin resolving a `where` parameter on a connector-EVENT descriptor. */ +export declare function lookup(descriptor: { + connector: string; + event: string; + nodeType?: undefined; +}, field: string): LookupBuilder; ```` ## LookupResolution (interface) @@ -1014,7 +1027,7 @@ declare class StepList { /** Go to another step from a NAMED PORT — a side exit. */ stepToRef(port: string, target: string): this; /** Terminate this path, binding flow outputs to expressions. */ - return(values?: Record, options?: NodeOptions): this; + return(values?: Record, options?: ReturnOptions): this; } ```` @@ -1052,7 +1065,9 @@ export interface TriggerOptions> { * registers. Loose offline; for Outlook `email-received` that is * `{ parentFolderId: '' }`. */ - where?: W; + where?: { + [K in keyof W]?: W[K] | LookupToken; + }; /** * The OBJECT a GENERIC event watches — the one thing its node type does not * say. `record-created` / `record-updated` on Data Fabric, Salesforce, @@ -1091,7 +1106,7 @@ export interface EventSubscription { * bound connection (`uip is triggers describe * --connection-id `). */ - where?: Record; + where?: Record; /** * The OBJECT a GENERIC event watches (a Data Fabric entity, a Salesforce or * ServiceNow object, a Jira record type). Required for such an event, refused @@ -2184,6 +2199,19 @@ export interface DoWhileOptions extends NodeOptions { } ```` +## ReturnOptions (interface) + +````ts +/** `.return(...)`-specific options. */ +export interface ReturnOptions extends NodeOptions { + /** + * The End node's id — what `layout` keys on and what `nodes[].id` becomes. + * Defaults to `end`, then `end2`, `end3`, … in emission order. + */ + name?: string; +} +```` + ## ContributionDiagnostic (interface) ````ts @@ -2972,7 +3000,7 @@ export type Step = { kind: 'return'; name: string; values: Record; - options?: NodeOptions; + options?: ReturnOptions; }; ```` diff --git a/preview/skills/uipath-maestro-flow/references/connector-params.md b/preview/skills/uipath-maestro-flow/references/connector-params.md index d7461e2fe2..a905085c8f 100644 --- a/preview/skills/uipath-maestro-flow/references/connector-params.md +++ b/preview/skills/uipath-maestro-flow/references/connector-params.md @@ -24,6 +24,26 @@ Signatures: { connection: 'jira', folder: 'shared' })) ``` +## Author first — `check` names every prepare you owe + +Authoring never waits on `prepare`, and no discovery command precedes the +source. The loop is: + +1. **Search** the library and read the operation's page. +2. **Author** the step from the task's own words — the fields you intend, + `lookup()` tokens for ids, `{ object: '' }` for a + generic operation. +3. **Check**: `uip maestro flow check .flow.ts --source`. It names each + gap with the exact command — `LOOKUP_UNRESOLVED`, `OBJECT_UNPREPARED`, + `CUSTOM_FIELDS_UNPREPARED`, `CONNECTOR_INPUT`. +4. **Prepare once** — `--object`, `--resolve` and `-f` compose in a single + invocation, and it finds the connection itself and writes `bindings.json`. + Switch to the generated descriptor import where it printed one. +5. **Check again, then compile.** + +Every prepare command below is the one `check` prints at step 3 — shown here +so its flags are documented, not as a step to run before the source exists. + ## Three contracts to settle before authoring Check these before the first connector call; they are the common places where a @@ -68,7 +88,7 @@ FLOW_SDK_LIBRARY_MD="$(uip maestro registry path --library-md)" The four library verbs are `pull`, `search`, `path` and `prepare`. Like the authoring verbs they need a prerelease `@uipath/cli`, and they hold no logic of -their own — each one runs the `@uipath/flow-sdk` installed in this workspace. In +their own — each one runs the `@uipath/maestro-builder-sdk` installed in this workspace. In a workspace with no `uip`, `npx flow-sdk registry ` is the same command with the same arguments. @@ -88,10 +108,12 @@ jq '.entries[] | select(.label | test("send email"; "i")) | {label,nodeType,path sed -n '1,220p' "$FLOW_SDK_LIBRARY_MD/" ``` -When a connection-specific field is absent, materialize the live schema rather -than guessing or hand-editing the emitted Flow. This reads the schema through -the tenant, so it needs a live Integration Service connection and a logged-in -`uip`; without one, fall back to the curated operation in the markdown library. +When `check` refuses a connection-specific field as unknown — or warns the +schema is unprepared — materialize the live schema rather than guessing or +hand-editing the emitted Flow; its diagnostic names this command. It reads the +schema through the tenant, so it needs a live Integration Service connection +and a logged-in `uip`; without one, fall back to the curated operation in the +markdown library. ```bash npx flow-sdk registry prepare @@ -155,27 +177,29 @@ above unless you specifically want that. ### Finding the object id for a Generic operation -`--object` takes the connection's **API** name (`acr_user`), not its display -name. Filter the catalog server-side — a real tenant has tens of thousands of -objects, and paging them client-side is what makes this expensive: +Do not hunt for it, and do not resolve it before authoring: write the step +with `{ object: '' }` and let `check` name this +command (`OBJECT_UNPREPARED`). `--object` matches, it does not equality-test — +any casing, resolved against the connected catalog (exact, then +case-insensitive on API name and display name, narrowed to objects that +support the action's verb): ```bash -# By API name, when the task names it outright. -uip is resources list --connection-id \ - --output-filter "[?Name=='acr_user']" --output json -# By human label, when the task only gives you that. `lower()` is available. -uip is resources list --connection-id \ - --output-filter "[?contains(lower(DisplayName),'account recovery')]" --output json +npx flow-sdk registry prepare --object Company__C +# object: matched "Company__C" -> Company__c ``` -Each row is `{Name, DisplayName, Path, Type, SubType, Custom, ElementKey}` — -`Name` is what `--object` wants. To see one object's parameters for a single -operation without preparing it: - -```bash -uip is resources describe --connection-id \ - --operation List --output json -``` +When the name matches **several** objects, the command refuses and prints the +candidates (API name, display name, custom, operations). `Custom: yes` marks a +tenant-only object, so a task that says "custom" means one of those rows. Pick +by the task's wording, or present the candidates and let the user choose — +never pick for them — then re-run with the chosen `Name`. When **nothing** +matches, the command has already retried a refreshed catalog: the object is +not on this connection, so surface that rather than inventing a name. Do not +hand-filter the catalog with `uip is resources list` + `--output-filter` — the +matching, the verb narrowing and the staleness retry are prepare's job now, +and the match is recorded in `connectors-local/resolutions.json` (the +`objects` section) beside the lookup resolutions. ## Structured filters (CEQL) @@ -242,6 +266,20 @@ same connection has many different shapes. Measured on one live Jira tenant, Both dimensions matter independently, and `fields.summary` — *required* — appears in none of the snapshot's static inputs. +**The parents are themselves lookups, and the id `--resolve` records is the +very value `-f` wants.** Do not run one prepare to resolve them and retype the +ids into a second command: pass both flags together — + +```bash +npx flow-sdk registry prepare uipath-atlassian-jira create-issue \ + --resolve fields.project.key:key=UIP --resolve fields.issuetype.id:name=Bug \ + -f fields.project.key=UIP -f fields.issuetype.id=10732 +``` + +— or run the resolve-only prepare and paste the re-run it prints: when every +parent has a recorded resolution, its closing note carries the exact +`-f NAME=VALUE` command with the resolved ids filled in. + **Two ways to get this wrong, and only one of them is loud.** *Naming too few parents is refused*, clearly: @@ -391,14 +429,16 @@ debug are the evidence. Fields such as Slack channels and mailbox folders store ids, not display names. **If a field is marked LOOKUP, do not guess it and do not resolve it by hand.** -Use the field's helper and record the value once with `prepare`. This one rule -covers 1,071 fields across 104 connectors, so it is worth knowing before any -particular connector is: +Author the field's helper call and keep going: ```ts channel: lookup(SendMessageToUser, 'channel').byEmail('dustin@example.com') ``` +When the flow is written, `check` reports each token still unresolved +(`LOOKUP_UNRESOLVED`) with exactly this command — one prepare records them all, +and it is the only tenant call the whole loop needs: + ```bash npx flow-sdk registry prepare uipath-salesforce-slack send-message-to-user \ --resolve channel:profile.email=dustin@example.com @@ -429,6 +469,15 @@ value for is resolved with its helper, not filled from a look-alike input. Run `check` before compiling: it finds everything else that is wrong first, so the one expensive call is spent last. +**When the task names nobody for a person-shaped required field** (`reporter`, +`requester`, `owner`), the value is the connection's own user: take the account +from `uip login status` and resolve by **display name**. Search only by the +names the field's "matches on" list declares — do not improvise queries against +raw collections. Jira in particular hides e-mail addresses from user search, so +an `emailAddress=` query returns nothing for a user that plainly exists, while +`displayName=` finds them; the field's spec (`matches on displayName | +accountId`) already told you e-mail was not searchable. + Each operation's markdown page lists its lookup fields, the helper for each, and what it can be searched by. The generated descriptor carries the same facts as a comment above the `export const`, so `grep -B6 'export const SendMessageToUser'` diff --git a/preview/skills/uipath-maestro-flow/references/data-fabric.md b/preview/skills/uipath-maestro-flow/references/data-fabric.md index 939d31a7da..4d2f509d18 100644 --- a/preview/skills/uipath-maestro-flow/references/data-fabric.md +++ b/preview/skills/uipath-maestro-flow/references/data-fabric.md @@ -1,34 +1,49 @@ # Data Fabric -*Exact signatures, fields, and defaults: [`dataFabricRead()`](api.md#datafabricread-function), [`dataFabricUpdate()`](api.md#datafabricupdate-function), and — for every other entity operation — [`connector()`](api.md#connector-function).* +*Exact signatures, fields, and defaults: [`connector()`](api.md#connector-function) for every entity operation, and — for the transitional native family — [`dataFabricRead()`](api.md#datafabricread-function) and [`dataFabricUpdate()`](api.md#datafabricupdate-function).* -## Two surfaces, one product — choose by the verb +## One product, one surface "Data Fabric" and "Data Service" are **the same product under two names**: the tenant lists connector key `uipath-uipath-dataservice` with the display name **UiPath Data Fabric**, and `uip df entities` manages the entities that -connector reads. What differs is the authoring surface, and the surfaces are not -interchangeable, because the native one has exactly two verbs: +connector reads. + +**Reach every entity operation through the connector.** One surface covers all +of them, and the connector is the surface that does: | Operation | Surface | | --- | --- | -| Read ONE record (optionally filtered) | `dataFabricRead()` → `core.datafabric.read` | -| Update ONE record | `dataFabricUpdate()` → `core.datafabric.update` | | Create a record | `connector('uipath-uipath-dataservice', 'create-entity-record', …)` | | Get a record by id | `connector(…, 'get-entity-record-by-id', …)` | -| Query MANY records / a row limit | `connector(…, 'query-entity-records', …)` | +| Query records / filters / a row limit | `connector(…, 'query-entity-records', …)` | +| Update a record | `connector(…, 'update-entity-record', …)` | | Delete a record | `connector(…, 'delete-entity-record', …)` | | Upload / download / delete a file record field | `connector(…, '…-file-…-record-field', …)` | | Record Created / Record Updated events | `onEvent(…)` on the same connector | -**So a task can say "Data Fabric" and still be a connector task.** Route on the -operation you need, never on which of the two names the scenario used. Reaching -for a `dataFabricCreate()` is the predictable dead end — there is no such node in -`core.datafabric.*` and none is coming; the create verb has always lived on the -connector. +**So a task can say "Data Fabric" and still be a connector task** — they all are. +Reaching for a `dataFabricCreate()` is the predictable dead end: there is no such +node in `core.datafabric.*`, and the create verb has always lived on the connector. + +### Why not the native nodes + +`core.datafabric.*` exists, and `dataFabricRead()` / `dataFabricUpdate()` compile. +But the native family is **mid-transition and covers only 2 of the 7 operations +above** — read one record, update one record. That asymmetry is the problem, not +the two node types: + +- **A flow ends up straddling both surfaces.** Create, get and delete have no + native node, so an author who takes the native node for the one verb that has + one ships a flow that is a connector flow everywhere else — two connection + bindings, two payload shapes, two things to debug, for one entity. +- **`core.datafabric.*` declares no output schema**, so a step that must map + declared outputs downstream cannot be a native step anyway. -`core.datafabric.*` also declares **no output schema**, so a step that must map -declared outputs downstream is a connector step for that reason alone. +So during the transition: **do not reach for the native nodes unless the scenario +names them.** Native nodes for all seven verbs are the direction of travel; until +they land, one flow, one surface, and that surface is the connector. `check` does +not reject a native node — this is a routing default, not a rule. ### The connector path, end to end @@ -65,10 +80,12 @@ That page names the connector *UiPath Data Fabric*, gives the HTTP route `prepare-connector … -f entityName=` line to run — before any of it has to be inferred. -## The native two-verb family +## The native two-verb family (transitional) -Read one entity record with filters (`core.datafabric.read`) and write fields -back to one record (`core.datafabric.update`). +For a scenario that names these nodes. Read one entity record with filters +(`core.datafabric.read`) and write fields back to one record +(`core.datafabric.update`). Everything else on the entity is a connector step — +see the routing table above. Data Fabric **events** (Record Created / Record Updated on an entity) are Integration Service connector events on `uipath-uipath-dataservice`, not this diff --git a/preview/skills/uipath-maestro-flow/references/event-trigger.md b/preview/skills/uipath-maestro-flow/references/event-trigger.md index 923d594024..b0f9b24bad 100644 --- a/preview/skills/uipath-maestro-flow/references/event-trigger.md +++ b/preview/skills/uipath-maestro-flow/references/event-trigger.md @@ -18,38 +18,51 @@ Signatures: })) ``` -## Tenant discovery +## Tenant discovery — `check` names it, `prepare` does it -Event parameters and their ids can be connection-specific. Resolve them from -the bound live connection rather than inferring them from a display label or -copying an id from an example, cached descriptor, or earlier session. +Event parameters and their ids are connection-specific. Author the +subscription first — an id-valued parameter as a `lookup()` token, never a +pasted id — and one `prepare` discharges everything `check` names: + +```ts +.trigger(onEvent({ + connector: 'uipath-microsoft-outlook365', event: 'email-received', + where: { + parentFolderId: lookup({ connector: 'uipath-microsoft-outlook365', event: 'email-received' }, + 'parentFolderId').by('displayName', 'Inbox'), + }, + filters: [{ field: 'subject', contains: 'Approval' }], + connection: 'outlook365', folder: 'shared', +})) +``` ```bash -# When a folder path is specified, scope connection discovery to that exact -# folder. Do not choose a same-connector row from an all-folders listing. -uip or folders get --output json -uip is connections list --folder-key --output json - -# Enumerate the event's object types on the exact connection the Flow will bind. -# Use the Integration Service operation (for example EMAIL_RECEIVED), not the -# builder event slug (for example email-received). -uip is triggers objects \ - --connection-id --output json - -# Describe the selected object to confirm its event-parameter contract. -uip is triggers describe \ - --connection-id --output json - -# If the contract names a reference object, list its values on that connection. -uip is resources run list \ - --connection-id --output json +npx flow-sdk registry prepare uipath-microsoft-outlook365 email-received \ + --resolve parentFolderId:displayName=Inbox ``` -Use the selected reference row's live `Id` for an id-valued event parameter. -For Outlook `email-received`, for example, select the `Message` object, confirm -that `parentFolderId` references `MailFolder`, list `MailFolder`, and pass the -Inbox row's `Id` as `where.parentFolderId`; the display name `Inbox` and an id -from another connection are not substitutes. +That one command replaces the old manual sequence end to end: it discovers the +connection and writes both `bindings.json` entries (connection id AND folder +key — the same stage a connector-action prepare runs), fetches the event's +connection-scoped definition, records the object decision for a generic event +(`--object `, the same matching ladder actions use), stores the +where/filter vocabulary in `connectors-local/` so `check` works offline, and +records each `--resolve` in `resolutions.json` for `compile` to substitute. +`check` then validates every `where` key and every `filters[].field` against +the prepared vocabulary — an unknown filter leaf is an ERROR +(`EVENT_FILTER_UNKNOWN_FIELD`), because the platform silently drops it and the +deployed trigger fires on events the filter should exclude. + +Two facts prepare surfaces before they can hurt: an event object that requires +a BYOA (bring-your-own-app) connection is refused with the connections that +would work, instead of faulting at runtime with an unrelated webhook error; +and a `webhooks`-mode event prints the URL-registration step — it cannot be +debugged locally, so deploy to test it. + +Do not run `uip is triggers objects` / `triggers describe` / +`resources run list` by hand and paste ids into `where`: prepare runs the same +calls deterministically, and a pasted id is meaningless in review and wrong +after a connection move. Before finishing, inspect the emitted node's `inputs.detail.eventParameters` and confirm it contains the selected id and the connection/folder bindings refer to the same connection used for discovery. diff --git a/preview/skills/uipath-maestro-flow/references/loops.md b/preview/skills/uipath-maestro-flow/references/loops.md index c216ca1f94..173556ebf8 100644 --- a/preview/skills/uipath-maestro-flow/references/loops.md +++ b/preview/skills/uipath-maestro-flow/references/loops.md @@ -32,6 +32,13 @@ The loop runs sequentially (`parallel: false`). Inside the body, read `$vars..currentItem` and `$vars..currentIndex` — or `v('eachOrder.currentItem')` from the builder. +The item is named after the LOOP, not after the collection and not by a +convention: **there is no `$vars.item`, `$vars.currentItem` or `$vars.`.** +A branch or step that decides on the item has to name the loop, so a loop called +`eachIssue` reads `$vars.eachIssue.currentItem.priority` — never +`$vars.item.priority`, which resolves to nothing and takes the false arm on every +iteration without erroring. + ## Rich loop options (loop 2.4) Any of these — or a `body.break()` in the body — selects the loop's 2.4 diff --git a/preview/skills/uipath-maestro-flow/references/queue.md b/preview/skills/uipath-maestro-flow/references/queue.md index 6f9474c4b2..6824780ec0 100644 --- a/preview/skills/uipath-maestro-flow/references/queue.md +++ b/preview/skills/uipath-maestro-flow/references/queue.md @@ -14,6 +14,24 @@ Signature: reference: input('invoiceId'), wait: false })) ``` +## Reading what the create returned + +The node's output carries the Orchestrator record of the item it created — `Id`, +`Key`, `UniqueKey`, `Reference`, `Status` and the rest of the queue-item schema. +Read a field with `out('', '')`; do NOT add a script step to +re-derive it, and do not invent a variable for the item: + +```ts +.step('enqueue', queueItem({ queue: 'Invoices', folderPath: 'Shared', + key: queueKey, item: { InvoiceId: input('invoiceId') } })) +.return({ itemKey: out('enqueue', 'Key') }) +``` + +`out('enqueue', 'Key')` is the whole read. It serializes to +`$vars.enqueue.response.Key`, which is the spelling the runtime resolves — so a +hand-written `$vars.enqueue.output.Key`, or a `script()` step that reads the +node and republishes the key, is both longer and wrong. + ## Tenant settings Queue-level settings are not present in source. A queue may require unique diff --git a/scripts/sync-maestro-sdk-preview.mjs b/scripts/sync-maestro-sdk-preview.mjs index 10e1fa0d82..092b937f73 100644 --- a/scripts/sync-maestro-sdk-preview.mjs +++ b/scripts/sync-maestro-sdk-preview.mjs @@ -40,27 +40,43 @@ const managedDirectories = [ 'preview/skills/uipath-maestro-bpmn/examples', ]; -const oldSiblingParagraph = [ - 'The sibling authoring surfaces have their own:', - '[`references/case-api.md`](references/case-api.md) for `@uipath/flow-sdk/case`', - 'and [`references/bpmn-api.md`](references/bpmn-api.md) for', - '`@uipath/flow-sdk/bpmn`. Neither is needed to build a Flow.', -].join('\n'); - -const currentSiblingParagraph = [ - 'The sibling authoring surfaces have their own:', - '[`references/case-api.md`](references/case-api.md) for `@uipath/flow-sdk/case`', - 'and [`references/bpmn-api.md`](references/bpmn-api.md) for', - '`@uipath/flow-sdk/bpmn`; their runtime-only decisions are in', - '[`references/case-runtime.md`](references/case-runtime.md) and', - '[`references/bpmn-runtime.md`](references/bpmn-runtime.md). None are needed to', - 'build a Flow.', -].join('\n'); - -const newSiblingParagraph = [ +/** + * The upstream paragraph pointing at sibling `references/*.md`, rewritten to + * point at sibling SKILLS — the catalog splits Flow, Case and BPMN into three, + * so a cross-reference to a file the reader does not have is a dead end. + * + * Matched by SHAPE, not by literal text. Three literal variants used to be + * listed here, all three spelling `@uipath/flow-sdk`, and the rename to + * `@uipath/maestro-builder-sdk` matched none of them — so this script threw + * `expected source text is absent` and the daily re-sync would have failed + * until someone added a fourth. The package specifier is the upstream's to + * choose; this only cares that the paragraph is there and what it says about + * the two siblings. + */ +const SIBLING_PARAGRAPH = new RegExp( + 'The sibling authoring surfaces have their own:\\n' + + '\\[`references/case-api\\.md`\\]\\(references/case-api\\.md\\) for `(?@[^`/]+/[^`/]+)/case`' + // Up to the paragraph's closing "build a Flow.". The paragraph has had a short + // form ("Neither is needed to build a Flow.") and a long one that adds the two + // runtime references and wraps before "build a Flow.", and a three-way merge + // can start from a pin carrying either — so this matches to the sentence end + // rather than to a particular wrapping. + // + // BOUNDED, and that is the point. An unbounded `[\\s\\S]*?` is lazy but has no + // ceiling: reword the closing sentence and any later "build a Flow." becomes + // the match end, so the replacement SWALLOWS everything between — a silent + // deletion inside a large generated diff, which is the opposite of the loud + // failure this matcher is supposed to give. Six intervening lines clears the + // longest real form (seven lines) with room to spare and nothing like the + // distance to the next section. + + '(?:[^\\n]*\\n){0,6}?[^\\n]*build a Flow\\.', +); + +/** What replaces it, carrying whatever specifier the upstream paragraph used. */ +const siblingParagraphFor = (pkg) => [ 'The sibling authoring surfaces have their own skills:', - '`uipath-maestro-case` for `@uipath/flow-sdk/case` and `uipath-maestro-bpmn`', - 'for `@uipath/flow-sdk/bpmn`. Neither is needed to build a Flow.', + `\`uipath-maestro-case\` for \`${pkg}/case\` and \`uipath-maestro-bpmn\``, + `for \`${pkg}/bpmn\`. Neither is needed to build a Flow.`, ].join('\n'); const oldStagingParagraph = [ @@ -347,15 +363,14 @@ function replaceRequired(text, before, after, label) { export function adaptFlowSkill(text) { let adapted = text.replaceAll('`example/', '`examples/'); - if (!adapted.includes(newSiblingParagraph)) { - const siblingParagraph = adapted.includes(currentSiblingParagraph) - ? currentSiblingParagraph - : oldSiblingParagraph; - adapted = replaceRequired( - adapted, - siblingParagraph, - newSiblingParagraph, - 'Flow sibling-skill adaptation', + const sibling = SIBLING_PARAGRAPH.exec(adapted); + if (sibling) { + adapted = adapted.replace(sibling[0], siblingParagraphFor(sibling.groups.pkg)); + } else if (!/sibling authoring surfaces have their own skills:/.test(adapted)) { + fail( + 'Could not apply Flow sibling-skill adaptation: the upstream SKILL.md has ' + + 'neither the sibling `references/*` paragraph this rewrites nor the ' + + 'rewritten form. Upstream changed that section — update SIBLING_PARAGRAPH.', ); } adapted = replaceRequired( diff --git a/tests/docker/Dockerfile b/tests/docker/Dockerfile index 418e634e07..b69c0e91ec 100644 --- a/tests/docker/Dockerfile +++ b/tests/docker/Dockerfile @@ -22,6 +22,15 @@ ARG CLI_VERSION=dev # override this with an exact version for a reproducible comparison. ARG FLOW_SDK_VERSION=latest +# The package NAME is a build arg for the same reason the version is: the rename +# split the published history. `@uipath/maestro-builder-sdk` starts at 5.0.0; +# everything at or below 4.0.0 exists only under `@uipath/flow-sdk`. So a +# baseline comparison against a pre-rename build passes both: +# --build-arg FLOW_SDK_PKG_NAME=@uipath/flow-sdk --build-arg FLOW_SDK_VERSION=4.0.0 +# The CLI resolves either name (UiPath/cli#4009), and stage-preview-sdk-workspace.sh +# reads the same variable so the workspace agrees with what the image installed. +ARG FLOW_SDK_PKG_NAME=@uipath/maestro-builder-sdk + # Floor on the number of tool plugins that must install for the image to be # usable. Discovery returns the CLI's full whitelist; a few may lack a build on # the current train (e.g. model-hub-tool has no `-dev` build), so individual @@ -111,18 +120,18 @@ RUN --mount=type=secret,id=npm_auth_token \ : > "$PREVIEW_FLOW_SDK_ROOT/flow-sdk.version" && \ if npm install --prefix "$PREVIEW_FLOW_SDK_ROOT" \ --save-exact --no-audit --no-fund --userconfig "$npmrc" \ - "@uipath/flow-sdk@${FLOW_SDK_VERSION}"; then \ + "${FLOW_SDK_PKG_NAME}@${FLOW_SDK_VERSION}"; then \ if resolved_sdk_version="$(node -p \ - "require('$PREVIEW_FLOW_SDK_ROOT/node_modules/@uipath/flow-sdk/package.json').version")" \ + "require('$PREVIEW_FLOW_SDK_ROOT/node_modules/${FLOW_SDK_PKG_NAME}/package.json').version")" \ && test -x "$PREVIEW_FLOW_SDK_ROOT/node_modules/.bin/flow-sdk"; then \ printf '%s\n' "$resolved_sdk_version" > "$PREVIEW_FLOW_SDK_ROOT/flow-sdk.version"; \ printf '%s\n' available > "$PREVIEW_FLOW_SDK_ASSETS_ROOT/sdk-install.status"; \ - echo "Installed @uipath/flow-sdk@${resolved_sdk_version}"; \ + echo "Installed ${FLOW_SDK_PKG_NAME}@${resolved_sdk_version}"; \ else \ - echo "WARN: @uipath/flow-sdk@${FLOW_SDK_VERSION} installed incompletely; Flow tasks will fail at runtime"; \ + echo "WARN: ${FLOW_SDK_PKG_NAME}@${FLOW_SDK_VERSION} installed incompletely; Flow tasks will fail at runtime"; \ fi; \ else \ - echo "WARN: @uipath/flow-sdk@${FLOW_SDK_VERSION} unavailable; Flow tasks will fail at runtime"; \ + echo "WARN: ${FLOW_SDK_PKG_NAME}@${FLOW_SDK_VERSION} unavailable; Flow tasks will fail at runtime"; \ fi && \ uip --version && \ uip tools search --output json > /tmp/uip-tools-search.json && \ @@ -163,6 +172,17 @@ ENV DOTNET_ROOT=/usr/share/dotnet \ # Pull the released connector catalog through the product CLI. Flow-specific # setup is best-effort so an SDK/feed outage does not block every non-Flow eval; +# +# The smoke fixture's import is rewritten to whatever THIS image installed. A +# build arg cannot reach an import statement, so a hard-coded specifier breaks +# one of the two builds: the new name fails an old-name baseline +# (`--build-arg FLOW_SDK_PKG_NAME=@uipath/flow-sdk`) and the old name fails the +# default build on `Cannot find package`. Here that is only a WARN, so it would +# be a silently skipped smoke rather than a build failure — which is why the +# `grep` follows the `sed`: a specifier shape the pattern misses fails on the +# grep, in the same breath, instead of looking like an SDK defect at +# `flow check`. +# # Flow tasks still fail at runtime when their required surface is unavailable. # The status file records exactly which rolling dependencies resolved. # @@ -235,6 +255,8 @@ RUN set -euo pipefail && \ source_check_status=failed; \ if (cd /opt/maestro-flow-sdk-smoke \ && ln -s "$PREVIEW_FLOW_SDK_ROOT/node_modules" node_modules \ + && sed -i "s#from '@uipath/[a-z0-9-]*'#from '${FLOW_SDK_PKG_NAME}'#" Smoke.flow.ts \ + && grep -q "from '${FLOW_SDK_PKG_NAME}'" Smoke.flow.ts \ && uip maestro flow check Smoke.flow.ts --source); then \ source_check_status=passed; \ else \ @@ -254,6 +276,12 @@ RUN set -euo pipefail && \ > "$FLOW_SDK_SETUP_STATUS" && \ cat "$FLOW_SDK_SETUP_STATUS" +# Persist the built-with name into the image. `ARG` is build-time only, and +# `stage-preview-sdk-workspace.sh` reads this at TASK time — without it a +# non-default build (an old-name baseline) would install one name and have the +# stager look for the other. +ENV FLOW_SDK_PKG_NAME=${FLOW_SDK_PKG_NAME} + ENV FLOW_SDK_LIBRARY_JSON=/opt/preview-flow-sdk-assets/library-json \ FLOW_SDK_LIBRARY_MD=/opt/preview-flow-sdk-assets/library-md \ FLOW_SDK_CONNECTORS_DIR=/opt/preview-flow-sdk-assets/connectors diff --git a/tests/experiments/flow-v2-preview.yaml b/tests/experiments/flow-v2-preview.yaml index a10480421d..f54e5bb56a 100644 --- a/tests/experiments/flow-v2-preview.yaml +++ b/tests/experiments/flow-v2-preview.yaml @@ -50,7 +50,7 @@ defaults: ignore_patterns: [] pre_run: - # Link the image's @uipath/flow-sdk and connector registry into the workspace, + # Link the image's @uipath/maestro-builder-sdk and connector registry into the workspace, # the same setup nightly.yaml runs. Unconditional here: a preview run always # authors through the SDK. Replaces the old in-sandbox `npm install`, which the # shared image made unnecessary when it started baking the package. diff --git a/tests/fixtures/maestro-flow-sdk-smoke/Smoke.flow.ts b/tests/fixtures/maestro-flow-sdk-smoke/Smoke.flow.ts index 1448d4c1e7..c089fe0f0a 100644 --- a/tests/fixtures/maestro-flow-sdk-smoke/Smoke.flow.ts +++ b/tests/fixtures/maestro-flow-sdk-smoke/Smoke.flow.ts @@ -1,4 +1,4 @@ -import { flow, out, script, types } from '@uipath/flow-sdk'; +import { flow, out, script, types } from '@uipath/maestro-builder-sdk'; export default flow('sdk-smoke') .name('SdkSmoke') diff --git a/tests/scripts/stage-preview-sdk-workspace.sh b/tests/scripts/stage-preview-sdk-workspace.sh index d3c9da0b03..039d76f0d8 100755 --- a/tests/scripts/stage-preview-sdk-workspace.sh +++ b/tests/scripts/stage-preview-sdk-workspace.sh @@ -3,7 +3,14 @@ set -euo pipefail sdk_root=${PREVIEW_FLOW_SDK_ROOT:-/opt/preview-flow-sdk} assets_root=${PREVIEW_FLOW_SDK_ASSETS_ROOT:-/opt/preview-flow-sdk-assets} -package_dir=$sdk_root/node_modules/@uipath/flow-sdk +# The package name is a knob, because the rename split the published history: +# `@uipath/maestro-builder-sdk` starts at 5.0.0 and everything at or below 4.0.0 +# exists only under `@uipath/flow-sdk`. Measuring a pre-rename published build — +# which a baseline comparison legitimately wants — needs the old name, and the +# image's own default must match whatever it installed. The CLI resolves either +# (UiPath/cli#4009). +sdk_package=${FLOW_SDK_PKG_NAME:-@uipath/maestro-builder-sdk} +package_dir=$sdk_root/node_modules/$sdk_package library_json=${FLOW_SDK_LIBRARY_JSON:-$assets_root/library-json} registry_root=${UIP_MAESTRO_REGISTRY_HOME:-$assets_root/registry} registry_current=$registry_root/current.json @@ -55,11 +62,12 @@ NODE ) ln -s "$sdk_root/node_modules" node_modules -node - "$sdk_version" "$sdk_gitref" "$registry_hash" <<'NODE' +node - "$sdk_version" "$sdk_gitref" "$registry_hash" "$sdk_package" <<'NODE' const fs = require('node:fs'); const version = process.argv[2]; const gitref = process.argv[3]; const connectorLibraryHash = process.argv[4]; +const sdkPackage = process.argv[5]; let packageJson = {}; if (fs.existsSync('package.json')) { packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8')); @@ -68,13 +76,13 @@ packageJson.private = true; packageJson.type = 'module'; packageJson.devDependencies = { ...(packageJson.devDependencies || {}), - '@uipath/flow-sdk': version, + [sdkPackage]: version, }; fs.writeFileSync('package.json', `${JSON.stringify(packageJson, null, 2)}\n`); fs.writeFileSync( 'preview-sdk-provenance.json', `${JSON.stringify({ - package: '@uipath/flow-sdk', + package: sdkPackage, version, gitref: gitref || null, connector_library: true, @@ -83,7 +91,8 @@ fs.writeFileSync( ); NODE -node -e "import('@uipath/flow-sdk')" +node -e "import('$sdk_package')" uip maestro flow compile --help >/dev/null -printf 'stage-preview-sdk-workspace: @uipath/flow-sdk@%s, connector library %s\n' \ +printf 'stage-preview-sdk-workspace: %s@%s, connector library %s\n' \ + "$sdk_package" \ "$sdk_version" "$registry_hash" diff --git a/tests/scripts/sync-maestro-sdk-preview.test.mjs b/tests/scripts/sync-maestro-sdk-preview.test.mjs index b3577b4f9e..9433a62427 100644 --- a/tests/scripts/sync-maestro-sdk-preview.test.mjs +++ b/tests/scripts/sync-maestro-sdk-preview.test.mjs @@ -362,6 +362,74 @@ test('syncSnapshots three-way merges drift and reapplies only snapshot adaptatio } }); +test('the sibling paragraph is matched by shape, not by package name', () => { + // The point of SIBLING_PARAGRAPH. The fixture above is byte-identical to the + // literal this replaced, so without this test the suite is green against both + // implementations — revert the matcher to three literals and nothing fails, + // while the real upstream body (renamed) throws on the old code and passes on + // the new one. That gap was the whole change and nothing recorded it. + const renamed = flowSiblingParagraph.replaceAll( + '@uipath/flow-sdk', + '@uipath/maestro-builder-sdk', + ); + assert.match( + adaptFlowSkill(`${renamed}\n\n${flowStagingParagraph}`), + /own skills:\n`uipath-maestro-case` for `@uipath\/maestro-builder-sdk\/case`/, + ); + // And the specifier is CARRIED, not assumed: the old name still round-trips. + assert.match( + adaptFlowSkill(`${flowSiblingParagraph}\n\n${flowStagingParagraph}`), + /own skills:\n`uipath-maestro-case` for `@uipath\/flow-sdk\/case`/, + ); +}); + +test('both wordings of the sibling paragraph match', () => { + // A three-way merge can start from a pin carrying either form, so the matcher + // spans both. The long one is what upstream ships today. + const long = [ + 'The sibling authoring surfaces have their own:', + '[`references/case-api.md`](references/case-api.md) for `@uipath/maestro-builder-sdk/case`', + 'and [`references/bpmn-api.md`](references/bpmn-api.md) for', + '`@uipath/maestro-builder-sdk/bpmn`; their runtime-only decisions are in', + '[`references/case-runtime.md`](references/case-runtime.md) and', + '[`references/bpmn-runtime.md`](references/bpmn-runtime.md). None are needed to', + 'build a Flow.', + ].join('\n'); + assert.match( + adaptFlowSkill(`${long}\n\n${flowStagingParagraph}`), + /own skills:\n`uipath-maestro-case` for `@uipath\/maestro-builder-sdk\/case`/, + ); +}); + +test('a reworded closing sentence fails loudly instead of swallowing the file', () => { + // The matcher's gap is BOUNDED for this reason. Unbounded, a reworded closing + // sentence made any later "build a Flow." the match end, and the replacement + // deleted everything between — silently, inside a large generated diff. + const reworded = [ + 'The sibling authoring surfaces have their own:', + '[`references/case-api.md`](references/case-api.md) for `@uipath/maestro-builder-sdk/case`', + 'and [`references/bpmn-api.md`](references/bpmn-api.md) for', + '`@uipath/maestro-builder-sdk/bpmn`. Neither is required here.', + '', + '## Quick start', + '', + 'IMPORTANT SECTION THAT MUST SURVIVE', + '', + 'Run `uip maestro flow init` to build a Flow.', + ].join('\n'); + assert.throws( + () => adaptFlowSkill(`${reworded}\n\n${flowStagingParagraph}`), + /update SIBLING_PARAGRAPH/, + ); +}); + +test('a dropped sibling section fails loudly', () => { + assert.throws( + () => adaptFlowSkill(`# Flow\n\n${flowStagingParagraph}`), + /update SIBLING_PARAGRAPH/, + ); +}); + test('Flow API adaptation waits for the upstream staged-path fix', () => { assert.equal( adaptFlowApi('Worked example: `example-eval/Foo.flow.ts`'), diff --git a/tests/scripts/test_stage_preview_sdk_workspace.py b/tests/scripts/test_stage_preview_sdk_workspace.py index 9625c10f5e..06bd415835 100644 --- a/tests/scripts/test_stage_preview_sdk_workspace.py +++ b/tests/scripts/test_stage_preview_sdk_workspace.py @@ -3,10 +3,13 @@ import subprocess from pathlib import Path +import pytest + REPO_ROOT = Path(__file__).resolve().parents[2] STAGE_SCRIPT = REPO_ROOT / "tests/scripts/stage-preview-sdk-workspace.sh" REQUIRED_CONNECTOR = "uipath.connector.uipath-uipath-dataservice.query-entity-records" +DEFAULT_PACKAGE = "@uipath/maestro-builder-sdk" def _write_executable(path: Path, body: str) -> None: @@ -14,14 +17,29 @@ def _write_executable(path: Path, body: str) -> None: path.chmod(0o755) -def test_stages_credential_free_sdk_workspace(tmp_path: Path) -> None: +# The stager reads the package name from FLOW_SDK_PKG_NAME and falls back to the +# post-rename default. Both halves of that knob are covered: the unset case pins +# the default, and the old name proves the value is actually threaded through +# every place the name appears (install probe, devDependency, provenance) rather +# than hard-coded. +@pytest.mark.parametrize( + ("sdk_package", "set_env"), + [ + pytest.param(DEFAULT_PACKAGE, False, id="default-when-unset"), + pytest.param(DEFAULT_PACKAGE, True, id="new-name-explicit"), + pytest.param("@uipath/flow-sdk", True, id="old-name-via-env"), + ], +) +def test_stages_credential_free_sdk_workspace( + tmp_path: Path, sdk_package: str, set_env: bool +) -> None: sdk_root = tmp_path / "sdk" - package_dir = sdk_root / "node_modules/@uipath/flow-sdk" + package_dir = sdk_root / "node_modules" / sdk_package package_dir.mkdir(parents=True) (package_dir / "package.json").write_text( json.dumps( { - "name": "@uipath/flow-sdk", + "name": sdk_package, "version": "3.20.0", "gitref": "efd27ce4c90ad76fc7f3c9b67f4920997b2cf0a8", "type": "module", @@ -63,6 +81,9 @@ def test_stages_credential_free_sdk_workspace(tmp_path: Path) -> None: "UIP_MAESTRO_REGISTRY_HOME": str(registry_root), "FLOW_SDK_LIBRARY_JSON": str(library_json), } + env.pop("FLOW_SDK_PKG_NAME", None) + if set_env: + env["FLOW_SDK_PKG_NAME"] = sdk_package completed = subprocess.run( ["bash", str(STAGE_SCRIPT)], @@ -73,15 +94,15 @@ def test_stages_credential_free_sdk_workspace(tmp_path: Path) -> None: text=True, ) - assert "@uipath/flow-sdk@3.20.0" in completed.stdout + assert f"{sdk_package}@3.20.0" in completed.stdout assert (workspace / "node_modules").is_symlink() assert (workspace / "node_modules").resolve() == sdk_root / "node_modules" package_json = json.loads((workspace / "package.json").read_text()) assert package_json["scripts"] == {"keep": "true"} - assert package_json["devDependencies"] == {"@uipath/flow-sdk": "3.20.0"} + assert package_json["devDependencies"] == {sdk_package: "3.20.0"} assert "flowSdk" not in package_json assert json.loads((workspace / "preview-sdk-provenance.json").read_text()) == { - "package": "@uipath/flow-sdk", + "package": sdk_package, "version": "3.20.0", "gitref": "efd27ce4c90ad76fc7f3c9b67f4920997b2cf0a8", "connector_library": True, diff --git a/tests/tasks/uipath-maestro-flow/_shared/test_flow_check.py b/tests/tasks/uipath-maestro-flow/_shared/test_flow_check.py index ef6b2488d4..b72f05de37 100644 --- a/tests/tasks/uipath-maestro-flow/_shared/test_flow_check.py +++ b/tests/tasks/uipath-maestro-flow/_shared/test_flow_check.py @@ -604,7 +604,7 @@ def test_find_project_ignores_the_staged_node_modules_symlink(tmp_path, monkeypa solution = tmp_path / "Real" solution.mkdir() _make_proj(solution, "Real", "Flow") - sdk = tmp_path.parent / f"{tmp_path.name}-sdk" / "node_modules" / "@uipath" / "flow-sdk" / "fixtures" + sdk = tmp_path.parent / f"{tmp_path.name}-sdk" / "node_modules" / "@uipath" / "maestro-builder-sdk" / "fixtures" sdk.mkdir(parents=True) (sdk / "project.uiproj").write_text('{"ProjectType": "Flow"}') (sdk / "Fixture.flow").write_text("{}")