Skip to content

Implement conditional deployment pipeline for monorepo using turbo and pnpm#218

Merged
nnoce14 merged 29 commits intomainfrom
nnoce14/issue215
Oct 20, 2025
Merged

Implement conditional deployment pipeline for monorepo using turbo and pnpm#218
nnoce14 merged 29 commits intomainfrom
nnoce14/issue215

Conversation

@nnoce14
Copy link
Member

@nnoce14 nnoce14 commented Oct 14, 2025

Fixes #215

Summary by Sourcery

Migrate monorepo to pnpm and add a turbo‐driven build pipeline with conditional artifact packaging

New Features:

  • Switch CI build scripts from npm to pnpm (install via corepack, cache pnpm store)
  • Detect changed packages and conditionally package and publish API, UI Community, and Docs artifacts
  • Introduce buildEnvSettings parameter for dynamic environment variable injection in the pipeline
  • Add @cellix/ui-core library with RequireAuth and ComponentQueryLoader components, Storybook stories, and Vitest tests

Enhancements:

  • Update all workspaces to use pnpm@10, workspace:* references, add files/exports for packaged artifacts
  • Refine Turbo cache keys to include pnpm-lock.yaml and use .turbo/cache directory
  • Switch detect-changes script to CommonJS entry and update test/coverage commands to pnpm

Build:

  • Introduce pnpm-workspace.yaml to define workspace packages

Documentation:

  • Add comprehensive README and API documentation for @cellix/ui-core molecules and Storybook setup

Tests:

  • Add Storybook play tests and Vitest configs for new UI core components

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Oct 14, 2025

Reviewer's Guide

This PR migrates the monorepo from npm to pnpm, refactors the Azure Pipelines build stage to use Turbo for affected builds and conditional deployment, standardizes all workspace packages with consistent manifests and TypeScript references, and introduces a new shared UI core library with Storybook and Vitest integration.

Entity relationship diagram for pnpm workspace and catalog dependencies

erDiagram
    PACKAGE ||--o{ WORKSPACE : contains
    WORKSPACE {
        string name
        string version
        string type
        string[] files
        string[] dependencies
        string[] devDependencies
        string[] peerDependencies
    }
    PACKAGE {
        string name
        string version
    }
    CATALOG ||--o{ PACKAGE : provides
    CATALOG {
        string name
        string version
    }
    WORKSPACE ||--o{ CATALOG : uses
Loading

Class diagram for the new @cellix/ui-core library structure

classDiagram
    class ComponentQueryLoader {
        +error: Error | undefined
        +errorComponent?: React.JSX.Element
        +loading: boolean
        +hasData: object | null | undefined
        +hasDataComponent: React.JSX.Element
        +noDataComponent?: React.JSX.Element
        +loadingRows?: number
        +loadingComponent?: React.JSX.Element
    }
    class RequireAuth {
        +children: React.JSX.Element
        +forceLogin?: boolean
    }
    class Molecules {
        +ComponentQueryLoader
        +RequireAuth
    }
    class Components {
        +Molecules
    }
    class UI_Core {
        +Components
    }
    UI_Core --> Components
    Components --> Molecules
    Molecules --> ComponentQueryLoader
    Molecules --> RequireAuth
Loading

Class diagram for standardized workspace package manifests

classDiagram
    class PackageJson {
        +name: string
        +version: string
        +type: string
        +files: string[]
        +exports: object
        +scripts: object
        +dependencies: object
        +devDependencies: object
        +peerDependencies?: object
    }
    class TsConfig {
        +extends: string
        +compilerOptions: object
        +include: string[]
        +exclude: string[]
        +references?: object[]
    }
    PackageJson --> TsConfig : "tsconfig reference"
Loading

File-Level Changes

Change Details Files
Overhauled CI pipeline to use pnpm and support conditional artifact deployment
  • Renamed cache variables and Cache@2 tasks to pnpm equivalents
  • Added inline Bash step to install and configure pnpm via corepack
  • Replaced npm install/ci steps with pnpm install and frozen-lockfile
  • Reconfigured Turbo cache keys, restore logic, and cacheHitVar
  • Updated detect-changes script extension and Turbo run commands to pnpm
  • Injected buildEnvSettings parameters and added separate API, UI, and Docs artifact packaging/publishing stages
build-pipeline/core/monorepo-build-stage.yml
Migrated monorepo from npm to pnpm and updated workspace configuration
  • Switched root package.json packageManager to pnpm and removed package-lock.json
  • Added pnpm-workspace.yaml and committed pnpm-lock.yaml
  • Rewrote root scripts from npm to pnpm invocations
  • Converted versioned locks to workspace:* dependencies where appropriate
package.json
pnpm-workspace.yaml
pnpm-lock.yaml
Standardized workspace package manifests and enhanced TypeScript project references
  • Added files and exports fields to each package.json
  • Updated dependencies/devDependencies to workspace:* and added peerDependencies
  • Updated scripts in each package to use pnpm commands
  • Inserted project references in tsconfig.json for composite builds and cross-package linking
apps/docs/package.json
apps/ui-sharethrift/package.json
apps/api/package.json
packages/sthrift/ui-components/package.json
packages/cellix/mongoose-seedwork/tsconfig.json
packages/sthrift/mock-mongodb-memory-server/tsconfig.json
Added @cellix/ui-core component library with UI molecules, stories, and testing configuration
  • Scaffolded new @cellix/ui-core package with package.json and README
  • Implemented ComponentQueryLoader and RequireAuth components under src/components/molecules
  • Created Storybook stories, preview.tsx, main.ts and Docusaurus-style README
  • Configured Vitest via vitest.config.ts and tsconfig.json for composite builds
packages/cellix/ui-core/package.json
packages/cellix/ui-core/README.md
packages/cellix/ui-core/src/components/molecules/require-auth/index.tsx
packages/cellix/ui-core/src/components/molecules/component-query-loader/index.tsx
packages/cellix/ui-core/.storybook/main.ts
packages/cellix/ui-core/.storybook/preview.tsx
packages/cellix/ui-core/vitest.config.ts
packages/cellix/ui-core/tsconfig.json

Assessment against linked issues

Issue Objective Addressed Explanation
#215 Extend the deployment pipeline to support conditional deployment for all app packages (UI static web app, backend function app, Docusaurus docs) using turbo for change detection.
#215 Update deployment templates and build scripts to use pnpm for install, build, and deploy steps, especially for the function app.
#215 Publish and download build artifacts for each application, ensuring artifacts are shared properly between build and deploy stages, and validate/update Bicep modules/templates in iac/ for app service plans, function app, and static website storage.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • Consider extracting the large inline Bash scripts for PNPM setup and artifact packaging into separate version-controlled script files to improve readability and maintainability of the pipeline YAML.
  • Instead of manually installing corepack via npm, evaluate using the official PNPM Azure DevOps task or a corepack-enabled pipeline step to simplify your PNPM setup and reduce custom scripting.
  • You have multiple hard-coded references to the Turbo cache directory—parameterizing and centralizing that path would help keep cache configuration consistent and easier to update.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider extracting the large inline Bash scripts for PNPM setup and artifact packaging into separate version-controlled script files to improve readability and maintainability of the pipeline YAML.
- Instead of manually installing corepack via npm, evaluate using the official PNPM Azure DevOps task or a corepack-enabled pipeline step to simplify your PNPM setup and reduce custom scripting.
- You have multiple hard-coded references to the Turbo cache directory—parameterizing and centralizing that path would help keep cache configuration consistent and easier to update.

## Individual Comments

### Comment 1
<location> `packages/cellix/ui-core/src/components/molecules/require-auth/index.tsx:58-59` </location>
<code_context>
+      </Row>
+    );
+  }
+  if (auth.isAuthenticated) {
+    return props.children;
+  } else if (auth.error) {
+    return <Navigate to="/" />;
+  } else {
</code_context>

<issue_to_address>
**suggestion:** The logic for error handling may mask underlying issues.

Logging the error or displaying an error message before redirecting would help with troubleshooting and improve user experience.

```suggestion
  } else if (auth.error) {
    // Log the error for troubleshooting
    console.error('Authentication error:', auth.error);

    // Display an error message to the user before redirecting
    setTimeout(() => {
      // Redirect after a short delay
      window.location.href = '/';
    }, 2500);

    return (
      <Row justify={'center'} style={{ height: '100vh', alignItems: 'center' }}>
        <Space size={'large'} direction="vertical" style={{ textAlign: 'center' }}>
          <Typography.Title level={2} type="danger">
            Authentication Error
          </Typography.Title>
          <Typography.Text type="danger">
            {auth.error.message || 'An unexpected authentication error occurred. Redirecting to home...'}
          </Typography.Text>
        </Space>
      </Row>
    );
```
</issue_to_address>

### Comment 2
<location> `packages/cellix/ui-core/src/components/molecules/require-auth/index.tsx:61-62` </location>
<code_context>
+  } else if (auth.error) {
+    return <Navigate to="/" />;
+  } else {
+    redirectUser();
+    return;
+  }
+};
</code_context>

<issue_to_address>
**issue (bug_risk):** Returning undefined from a React component may cause rendering issues.

Return null instead of undefined to avoid runtime warnings and clarify that no UI should be rendered.
</issue_to_address>

### Comment 3
<location> `packages/cellix/ui-core/src/components/molecules/component-query-loader/index.tsx:15-23` </location>
<code_context>
+    if (props.errorComponent) {
+      return props.errorComponent;
+    }
+    message.error(props.error.message);
+    return <Skeleton/>;
+  }
+  if (props.loading) {
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Calling message.error on every render may cause repeated notifications.

To prevent duplicate error notifications, use a ref or effect to display the error only once per occurrence.

```suggestion
import React, { useEffect, useRef } from "react";

export const ComponentQueryLoader: FC<ComponentQueryLoaderProps> = (props) => {
  const lastErrorMessageRef = useRef<string | undefined>(undefined);

  useEffect(() => {
    if (props.error && props.error.message !== lastErrorMessageRef.current) {
      message.error(props.error.message);
      lastErrorMessageRef.current = props.error.message;
    }
  }, [props.error]);

  if (props.error) {
    if (props.errorComponent) {
      return props.errorComponent;
    }
    return <Skeleton/>;
  }
  if (props.loading) {
```
</issue_to_address>

### Comment 4
<location> `packages/cellix/ui-core/README.md:39-42` </location>
<code_context>
+## Folder structure
+
+```
+packages/cellix-ui-core/
+├── src/
+│   ├── components/                        # UI components organized by atomic design principles
</code_context>

<issue_to_address>
**issue (typo):** Typo in folder path: should be 'packages/cellix/ui-core/'

The documentation currently uses 'packages/cellix-ui-core/', but it should be 'packages/cellix/ui-core/'.

```suggestion
```
packages/cellix/ui-core/
├── src/
│   ├── components/                        # UI components organized by atomic design principles
```
</issue_to_address>

### Comment 5
<location> `packages/cellix/ui-core/src/components/molecules/require-auth/require-auth.stories.tsx:10` </location>
<code_context>
+
+type AwaitedReturn<T> = T extends (...args: unknown[]) => Promise<infer R> ? R : never;
+
+// Wrapper that injects a mocked AuthContext matching react-oidc-context shape
+const Wrapper: React.FC<{ auth: Partial<AuthContextProps>; children: React.ReactNode }> = ({ auth, children }) => {
+  // Provide minimal defaults and stub functions to avoid runtime errors
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring the stories to use a global Wrapper decorator and a single Template to eliminate repetitive provider setup and reduce boilerplate.

You’ve already centralized most of your “AuthContext” wiring into that top-level `Wrapper`, but each story is still re-defining its own provider inline – which is why the file is ballooning again. You can collapse all of your “simple” stories down to a single template + a decorator, and only keep per-story `render` blocks for truly custom setups (e.g. your `<Routes>` one).

1. Move `Wrapper` into a global decorator that reads `context.args.auth`:
```tsx
// at the bottom of your file, replace your `meta` with:
const meta = {
  title: 'UI/Core/Molecules/RequireAuth',
  component: RequireAuth,
  parameters: { layout: 'padded' },
  decorators: [
    (Story, context) => (
      <Wrapper auth={context.args.auth ?? {}}>
        <Story />
      </Wrapper>
    )
  ],
} satisfies Meta<typeof RequireAuth>;

export default meta;
```

2. Create a single “Template” that renders your component + `<Child />`:
```tsx
const Template: StoryFn<typeof RequireAuth> = (args) => (
  <RequireAuth {...args}>
    <Child />
  </RequireAuth>
);
```

3. Now your simple stories reduce to <10 lines each:
```tsx
export const Loading = Template.bind({});
Loading.args = {
  forceLogin: false,
  auth: { isLoading: true },
};
Loading.play = async ({ canvasElement }) => {
  const canvas = within(canvasElement);
  expect(canvas.queryByText('Private Content')).toBeNull();
  await expect(canvas.findByText(/Please wait.../i)).resolves.toBeTruthy();
};

export const Authenticated = Template.bind({});
Authenticated.args = {
  forceLogin: false,
  auth: { isAuthenticated: true },
};
Authenticated.play = async ({ canvasElement }) => {
  const canvas = within(canvasElement);
  await expect(canvas.findByText('Private Content')).resolves.toBeTruthy();
};
```

4. For stories that need extra routing or bespoke instrumentation (ErrorState, NotAuthenticated, etc.), keep using a custom `render:` block – they’ll still pick up your global decorator, so you can delete the local `<Wrapper>` definitions inside them.

This consolidates ~200 lines of repeating boilerplate into one decorator + one template, while preserving all your existing functionality.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@nnoce14 nnoce14 merged commit 1bea6d9 into main Oct 20, 2025
7 checks passed
@nnoce14 nnoce14 deleted the nnoce14/issue215 branch October 20, 2025 15:08
arif-u-ahmed pushed a commit that referenced this pull request Nov 4, 2025
Implement conditional deployment pipeline for monorepo using turbo and pnpm
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement conditional deployment pipeline for monorepo using turbo and pnpm

1 participant