@servicetitan/docs-uikit 31.5.1 → 32.0.0

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.
@@ -2,6 +2,24 @@
2
2
  title: BREAKING CHANGES
3
3
  ---
4
4
 
5
+ ## v32.0.0
6
+
7
+ ### [@servicetitan/startup](./startup)
8
+
9
+ :::caution
10
+ This breaking change affects local development, even if you do not update the `@servicetitan/startup` dependency directly. On a clean install, the standard bootstrap script runs the latest version of `startup`, which automatically removes hard-coded tokens from `.npmrc` files.
11
+ :::
12
+
13
+ Removed support for hard-coded npm tokens in `.npmrc` files. The new `startup install` removes hard-coded authentication tokens from project `.npmrc` files and, in development environments, automatically adds a token to npm's per-user configuration that grants access to ServiceTitan's private packages.
14
+
15
+ Projects that previously relied on hard-coded tokens must update their Github and Teamcity workflows to instead use the organization secret as described in the [startup install documentation](./startup/install#configuring-npm-token-in-ci):
16
+
17
+ - Option 1: Inject the organization secret into `.npmrc`.
18
+ - Option 2: Configure `.npmrc` to get the organization secret from an environment variable.
19
+
20
+ **Why this change?**
21
+ Two recent incidents mandated that we revoke the shared token that grants access to ServiceTitan's private packages. By removing hard-coded tokens, ServiceTitan can more easily and seamlessly rotate or revoke tokens, improving security and reducing friction for developers.
22
+
5
23
  ## v31.0.0
6
24
 
7
25
  ### [@servicetitan/startup](./startup)
@@ -63,14 +81,13 @@ title: BREAKING CHANGES
63
81
 
64
82
  ### [@servicetitan/ajax-handlers](./ajax-handlers)
65
83
 
66
- - Changed [**withMicroservice**](./ajax-handlers#parameters) arguments to a single options object.
84
+ - Changed [**withMicroservice**](./ajax-handlers/with-microservice#parameters) arguments to a single options object.
67
85
 
68
86
  ## v24.0.0
69
87
 
70
88
  ### [@servicetitan/startup](./startup)
71
89
 
72
90
  - Upgraded **Typescript** from v4 to v5, which adds correctness improvements that will reveal previously undetected ambiguities. In particular,
73
-
74
91
  - Type checks for enums are stricter because all **enum** values get their own type
75
92
  - **string** and **boolean** values aren't implicitly converted to numbers. Use the `+` operator to explicitly convert values to numbers in mathematical expressions. (e.g., `return +str > 42;`)
76
93
 
@@ -0,0 +1,12 @@
1
+ ---
2
+ title: AJAX Handlers
3
+ ---
4
+
5
+ #### [CHANGELOG (@servicetitan/ajax-handlers)](https://github.com/servicetitan/uikit/blob/master/packages/ajax-handlers/CHANGELOG.md)
6
+
7
+ `@servicetitan/ajax-handlers` provides utilities for handling AJAX requests, authentication, and response processing in ServiceTitan applications. It includes automatic date parsing, authentication wrappers for protected resources, and comprehensive error handling.
8
+
9
+ ## API
10
+
11
+ - [withMicroservice](./with-microservice.mdx) - Use `withMicroservice` to wrap components and authenticate requests to protected resources with support for Bearer and Token Server authentication.
12
+ - [initAjaxHandlersParseDates](./init-ajax-handlers-parse-dates.mdx) - Use `initAjaxHandlersParseDates` to automatically transform date strings in axios responses into JavaScript Date objects.
@@ -0,0 +1,74 @@
1
+ ---
2
+ title: initAjaxHandlersParseDates
3
+ ---
4
+
5
+ `initAjaxHandlersParseDates` is a function that automatically transforms date strings in axios responses into JavaScript Date objects. This eliminates the need for manual date parsing throughout your application.
6
+
7
+ ## Usage
8
+
9
+ Call this function once in your application's initialization code, typically before making any axios requests:
10
+
11
+ ```tsx
12
+ import { initAjaxHandlersParseDates } from '@servicetitan/ajax-handlers';
13
+
14
+ // Initialize date parsing for all axios responses
15
+ initAjaxHandlersParseDates();
16
+ ```
17
+
18
+ ## How It Works
19
+
20
+ The function adds a response transformer to the global axios defaults that:
21
+
22
+ 1. Automatically detects and parses date strings in response data
23
+ 2. Recursively traverses objects and arrays to find date strings
24
+ 3. Preserves other data types unchanged (including Blobs)
25
+ 4. Handles parsing errors gracefully by returning the original data
26
+
27
+ ## Supported Date Formats
28
+
29
+ The parser recognizes two date formats commonly used in APIs:
30
+
31
+ ### ISO 8601 Format
32
+
33
+ Supports standard ISO date strings with optional time zones and milliseconds:
34
+
35
+ - `2024-03-15T10:30:45Z`
36
+ - `2024-03-15T10:30:45.123Z`
37
+ - `2024-03-15T10:30:45+05:00`
38
+ - `2024-03-15T10:30:45.789-08:00`
39
+
40
+ ### .NET JSON Date Format
41
+
42
+ Supports the legacy .NET JSON date format:
43
+
44
+ - `/Date(1710504645000)/`
45
+ - `/Date(1710504645000-0800)/`
46
+ - `/Date(-62135596800000)/` (negative timestamps for dates before 1970)
47
+
48
+ ## Example
49
+
50
+ ```tsx
51
+ import axios from 'axios';
52
+ import { initAjaxHandlersParseDates } from '@servicetitan/ajax-handlers';
53
+
54
+ // Initialize date parsing once
55
+ initAjaxHandlersParseDates();
56
+
57
+ // Now all axios responses will have dates automatically parsed
58
+ const response = await axios.get('/api/users/123');
59
+
60
+ // response.data might look like:
61
+ // {
62
+ // id: 123,
63
+ // name: 'John Doe',
64
+ // createdAt: Date object (not a string!),
65
+ // lastLogin: Date object (not a string!),
66
+ // metadata: {
67
+ // updatedAt: Date object (not a string!)
68
+ // }
69
+ // }
70
+
71
+ // You can immediately use Date methods
72
+ console.log(response.data.createdAt.getFullYear());
73
+ console.log(response.data.lastLogin.toLocaleDateString());
74
+ ```
@@ -1,15 +1,11 @@
1
1
  ---
2
- title: AJAX Handlers
2
+ title: withMicroservice
3
3
  ---
4
4
 
5
- #### [CHANGELOG (@servicetitan/ajax-handlers)](https://github.com/servicetitan/uikit/blob/master/packages/ajax-handlers/CHANGELOG.md)
6
-
7
- ## withMicroservice
8
-
9
5
  `withMicroservice` is a higher-order function that wraps a component and authenticates
10
6
  its requests to protected resources.
11
7
 
12
- ### Usage
8
+ ## Usage
13
9
 
14
10
  The best way to use `withMicroservice` is to wrap the component in the ES module body. E.g.,
15
11
 
@@ -54,7 +50,7 @@ const App = () => {
54
50
  }
55
51
  ```
56
52
 
57
- ### Parameters
53
+ ## Parameters
58
54
 
59
55
  `withMicroservice` accepts an object with the following properties:
60
56
 
@@ -70,27 +66,27 @@ const App = () => {
70
66
  | `preAuthenticateCallback` | `(error?: any) => void` | (optional) callback to call after pre-authentication succeeds or fails. If pre-authentication fails, the error is passed to the first parameter of the callback. |
71
67
  | `tokens` | `Symbol[]` | (optional) symbols to use to provide `baseURL` to autogenerated API services and child components |
72
68
 
73
- #### authAdapter{#auth-adapter}
69
+ ### authAdapter{#auth-adapter}
74
70
 
75
71
  Use `authAdapter` to select the authentication scheme. One of,
76
72
 
77
- | Value | Description |
78
- | :----------- | :------------------------------------------------------------------------------------------------------------------------------------------------ |
79
- | "bearer" | **Bearer** authentication sends the bearer token returned by `authURL` in the **Authorization** header of requests. Uses BearerTokenAuth adapter. |
80
- | "token" | **Token** authentication uses the Token Server's "silent login" protocol to add secure cookies to requests. Uses TokenServerAuth adapter. |
73
+ | Value | Description |
74
+ | :-------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ |
75
+ | "bearer" | **Bearer** authentication sends the bearer token returned by `authURL` in the **Authorization** header of requests. Uses BearerTokenAuth adapter. |
76
+ | "token" | **Token** authentication uses the Token Server's "silent login" protocol to add secure cookies to requests. Uses TokenServerAuth adapter. |
81
77
  | `(context: WithMicroserviceContext) => AuthAdapter` | Custom authentication uses the adapter returned by a function to decorate requests. |
82
78
 
83
79
  See [Authentication Adapters](#authentication-adapters) for more information.
84
80
 
85
- #### authUrl
81
+ ### authUrl
86
82
 
87
83
  The authentication endpoint for protected resources.
88
84
 
89
- - For **Bearer** authentication, this is the endpoint that returns the bearer token to send in **Authorization** headers. Defaults to `${baseURL}/auth`.
85
+ - For **Bearer** authentication, this is the endpoint that returns the bearer token to send in **Authorization** headers. Defaults to `${baseURL}/auth`.
90
86
 
91
- - For **Token server** authentication, this is the "silent login" endpoint. Defaults to `${baseURL}/bff/silent-login`.
87
+ - For **Token server** authentication, this is the "silent login" endpoint. Defaults to `${baseURL}/bff/silent-login`.
92
88
 
93
- #### preAuthenticate{#pre-authenticate}
89
+ ### preAuthenticate{#pre-authenticate}
94
90
 
95
91
  Controls when authentication happens:
96
92
 
@@ -101,9 +97,9 @@ Controls when authentication happens:
101
97
 
102
98
  When using Token Server authentication, authentication is always performed after the component is loaded and this value is ignored.
103
99
 
104
- #### tokens
100
+ ### tokens
105
101
 
106
- The main purpose of `tokens` is to [provide](./react-ioc#provide) the `baseURL` to autogenerated API services.
102
+ The main purpose of `tokens` is to [provide](../react-ioc#provide) the `baseURL` to autogenerated API services.
107
103
  For example,
108
104
 
109
105
  ```tsx
@@ -138,11 +134,11 @@ const Component = () => {
138
134
  }
139
135
  ```
140
136
 
141
- ### Authentication Adapters{#authentication-adapters}
137
+ ## Authentication Adapters{#authentication-adapters}
142
138
 
143
139
  `withMicroservice` uses authentication adapters in order to perform authentication in different ways depending on your needs.
144
140
 
145
- #### BearerTokenAuth
141
+ ### BearerTokenAuth
146
142
 
147
143
  The default adapter is the `BearerTokenAuth` adapter. When authentication occurs, a request is sent to the `authURL` in order to retrieve a token. Then any requests made using the `baseURL` will add the `Authorization: Bearer ...` header with the stored token.
148
144
 
@@ -156,7 +152,7 @@ const App = withMicroservice({
156
152
  });
157
153
  ```
158
154
 
159
- #### TokenServerAuth{#token-server-auth}
155
+ ### TokenServerAuth{#token-server-auth}
160
156
 
161
157
  The `TokenServerAuth` adapter allows you to authenticate with a backend that is using the `ServiceTitan.Ium.Bff` NuGet package ([see TokenServer documentation for details](/docs/projects/ium/tokenserver-new-app-integration/)).
162
158
 
@@ -177,8 +173,8 @@ const App = withMicroservice({
177
173
  ```tsx title="Custom adapter"
178
174
  const options: TokenServerAuthOptions = {
179
175
  authInfoURL: '/custom/authinfo',
180
- onError: (defaultErrorHandler) => {
181
- console.error('There was an error');
176
+ onError: (defaultErrorHandler, error) => {
177
+ console.error(error, error.data);
182
178
  defaultErrorHandler();
183
179
  },
184
180
  };
@@ -189,12 +185,20 @@ const App = withMicroservice({
189
185
  });
190
186
  ```
191
187
 
192
- ##### TokenServerAuthOptions
193
- | Name | Type | Description |
194
- | :------------ | :------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
195
- | `authInfoURL` | `string` | (optional) URL to fetch authentication parameters from. Defaults to `'/bff/authinfo'`. |
196
- | `onError` | `(defaultErrorHandler: () => void) => void;` | (optional) Callback to call when an error occurs during authentication. Defaults to reloading the page a maximum of 1 time. First parameter is a function to call the default error handler. |
188
+ #### TokenServerAuthOptions
189
+
190
+ | Name | Type | Description |
191
+ | :------------ | :--------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- |
192
+ | `authInfoURL` | `string` | (optional) URL to fetch authentication parameters from. Defaults to `'/bff/authinfo'`. |
193
+ | `onError` | `(defaultErrorHandler: () => void, error: Error) => void;` | (optional) Callback to call when an error occurs during authentication. Defaults to reloading the page a maximum of 1 time. |
194
+
195
+ The `onError` callback is passed the following arguments:
196
+
197
+ | Name | Type | Description |
198
+ | :-------------------- | :--------- | :------------------------------------------------------------------------------------------------- |
199
+ | `defaultErrorHandler` | `Function` | The default error handler. |
200
+ | `error` | `Error` | An error object with a `data` property that contains the data associated with the failure, if any. |
197
201
 
198
- ### Authorization
202
+ ## Authorization
199
203
 
200
204
  `withMicroservice` currently only assists with performing authentication for requests of protected resources. There are currently no plans to implement authorization until we see concrete Token Server usage examples by product teams. If you find that having any sort of implementation for authorization would be helpful, especially in regards to Token Server implementation, please bring any ideas or discussions to the Frontend Platform team's attention so that we can create a platform solution.
@@ -0,0 +1,133 @@
1
+ ---
2
+ title: DataDog RUM
3
+ ---
4
+
5
+ #### [CHANGELOG (@servicetitan/datadog-rum)](https://github.com/servicetitan/uikit/blob/master/packages/datadog-rum/CHANGELOG.md)
6
+
7
+ [`@servicetitan/datadog-rum`](https://github.com/servicetitan/uikit/tree/master/packages/datadog-rum) helps configure DataDog RUM JS SDK in standalone applications. It enriches RUM events with information useful for application and MFE monitoring, and troubleshooting.
8
+
9
+ ## Configuration snippet
10
+
11
+ ```tsx
12
+ import { beforeSend, DatadogProvider } from '@servicetitan/datadog-rum';
13
+ import { datadogRum } from '@datadog/browser-rum';
14
+
15
+ const isEnabled = /.*go\d*\.servicetitan\.com/.test(window.location.hostname);
16
+
17
+ if (isEnabled) {
18
+ datadogRum.init({
19
+ applicationId: '<APPLICATION_ID>',
20
+ clientToken: '<CLIENT_TOKEN>',
21
+ site: 'datadoghq.com',
22
+ service: '<SERVICE_NAME>',
23
+ env: '<ENV_NAME>',
24
+ version: '<APPLICATION_VERSION>',
25
+ sessionSampleRate: 100,
26
+ sessionReplaySampleRate: 0,
27
+ defaultPrivacyLevel: 'mask-user-input',
28
+ beforeSend: beforeSend(),
29
+ });
30
+ }
31
+
32
+ const Root = () => {
33
+ const user = useCurrentUser(); // application-specific hook that identifies the user
34
+
35
+ return (
36
+ <DatadogProvider isEnabled={isEnabled} user={user}>
37
+ <Application />
38
+ </DatadogProvider>
39
+ );
40
+ };
41
+ ```
42
+
43
+ ## DatadogProvider
44
+
45
+ `DatadogProvider` is a higher-order component that supplies RUM SDK with:
46
+
47
+ - Information about the user session
48
+ - Sanitized view event names (`@view.name`) when hash-based routing is used
49
+
50
+ ### Usage
51
+
52
+ Wrap your application with `DatadogProvider` close to the root. E.g.,
53
+
54
+ ```tsx
55
+ import { DatadogProvider } from '@servicetitan/datadog-rum';
56
+
57
+ const Root = () =>
58
+ return (
59
+ <DatadogProvider isEnabled={isEnabled} user={user}>
60
+ ...
61
+ </DatadogProvider>
62
+ );
63
+ };
64
+ ```
65
+
66
+ If your application uses hash-based routing, `DatadogProvider` should be placed inside `HashRouter`. E.g.,
67
+
68
+ ```tsx
69
+ import { HashRouter } from 'react-router-dom';
70
+ import { DatadogProvider } from '@servicetitan/datadog-rum';
71
+
72
+ const Root = () => {
73
+ return (
74
+ <HashRouter>
75
+ <DatadogProvider isEnabled={isEnabled} user={user} options={{ hashRouting: true }}>
76
+ ...
77
+ </DatadogProvider>
78
+ </HashRouter>
79
+ );
80
+ };
81
+ ```
82
+
83
+ ### Parameters
84
+
85
+ | Name | Type | Description |
86
+ | :---------- | :-------- | :------------------------------------------ |
87
+ | `isEnabled` | `boolean` | Enables DD RUM instrumentation |
88
+ | `user` | `User` | (optional) User session information |
89
+ | `options` | `Options` | (optional) Additional configuration options |
90
+
91
+ #### `User`
92
+
93
+ | Name | Type | Description |
94
+ | :-------------- | :----------------------------- | :----------------------------------- |
95
+ | `id` | `string` | User ID |
96
+ | `[key: string]` | `string \| number \| string[]` | (optional) Arbitrary user attributes |
97
+
98
+ #### `Options`
99
+
100
+ | Name | Type | Description |
101
+ | :------------ | :-------- | :------------------------------------------ |
102
+ | `hashRouting` | `boolean` | Sanitizes view names for hash-based routing |
103
+
104
+ ## `beforeSend`
105
+
106
+ `beforeSend` is a callback function for enriching RUM events. Supported functionality:
107
+
108
+ - Adds MFE name and version for error and resource events coming from MFEs (`@error.service`, `@resource.service`, `@error.version`, and `@resource.version`)
109
+ - Adds Cloudflare Ray ID for resource events (`@resource.cfRay`)
110
+
111
+ ### Usage
112
+
113
+ ```ts
114
+ import { beforeSend } from '@servicetitan/datadog-rum';
115
+ import { datadogRum } from '@datadog/browser-rum';
116
+
117
+ const isEnabled = /.*go\d*\.servicetitan\.com/.test(window.location.hostname);
118
+
119
+ if (isEnabled) {
120
+ datadogRum.init({
121
+ applicationId: '<APPLICATION_ID>',
122
+ clientToken: '<CLIENT_TOKEN>',
123
+ site: 'datadoghq.com',
124
+ service: '<SERVICE_NAME>',
125
+ env: '<ENV_NAME>',
126
+ version: '<APPLICATION_VERSION>',
127
+ sessionSampleRate: 100,
128
+ sessionReplaySampleRate: 0,
129
+ defaultPrivacyLevel: 'mask-user-input',
130
+ beforeSend: beforeSend(), // <--beforeSend() returns handler that enriches events
131
+ });
132
+ }
133
+ ```
@@ -2,6 +2,9 @@
2
2
  title: install
3
3
  ---
4
4
 
5
+ import Tabs from '@theme/Tabs';
6
+ import TabItem from '@theme/TabItem';
7
+
5
8
  Installs project dependencies.
6
9
 
7
10
  Use this command instead of plain `npm install` to guarantee that all packages are installed correctly and any necessary post-install steps are performed.
@@ -11,3 +14,151 @@ npx --yes @servicetitan/startup install
11
14
  ```
12
15
 
13
16
  See [package.json in the example project](https://github.com/search?q=repo%3Aservicetitan%2Ffrontend-example+path%3A**%2Fpackage.json+%22startup+install%22&type=code) for how to use `install` in an npm script.
17
+
18
+ ## Options
19
+
20
+ | Option | Description |
21
+ | :----------- | :-------------------------------------------- |
22
+ | `--no-token` | Disable configuring npm authentication token. |
23
+
24
+ ## Configuring NPM Token in Development
25
+
26
+ In development environments, `startup install` automatically configures an npm authentication token that grants access to private ServiceTitan packages. It securely retrieves the token and updates your npm configuration so you can install dependencies without any further setup.
27
+
28
+ :::caution
29
+ `startup install` detects if it is running in development by checking for standard environment variables set by CI systems (e.g.,`CI` and `TEAMCITY_VERSION`). If none of these variables are set, it assumes it is running on a developer machine.
30
+ If it mistakes a CI environment for a developer machine, add a `CI` environment variable (with any value) to the workflow, or [use `--no-token`](#how-to-use---no-token) to disable configuring the npm token.
31
+ :::
32
+
33
+ ## Configuring NPM Token in CI
34
+
35
+ In CI environments such as Teamcity and Github, use the `ST_NPM_READONLY_TOKEN` organization secret to grant access to private ServiceTitan packages. You can do this in two ways:
36
+
37
+ ### Option 1: Inject into .npmrc (Recommended)
38
+
39
+ The recommended method is to use `npm config set --location=project` to manually inject the organization secret into the project's `.npmrc`. For example,
40
+
41
+ <Tabs
42
+ defaultValue="github"
43
+ values={[{ label: "Github Action", value: "github"}, {label: "Dockerfile", value: "docker"}]}>
44
+
45
+ <TabItem value="github">
46
+
47
+ ```yml
48
+ - run: npm config set --location=project "//registry.npmjs.org/:_authToken"="${{ secrets.ST_NPM_READONLY_TOKEN }}"
49
+ - run: npm run build
50
+ ```
51
+
52
+ Alternatively, in Github actions using `action/setup-node`, you can use `registry-url` and `env.NODE_AUTH_TOKEN` to have it configure the project level `.npmrc`. For example,
53
+
54
+ ```yml
55
+ - uses: actions/setup-node@v4
56
+ registry-url: https://registry.npmjs.org
57
+ env:
58
+ NODE_AUTH_TOKEN: ${{ secrets.ST_NPM_READONLY_TOKEN }}
59
+ ```
60
+
61
+ </TabItem>
62
+ <TabItem value="docker">
63
+
64
+ ```dockerfile
65
+ ARG NPM_READONLY_TOKEN
66
+
67
+ RUN npm config set --location=project "//registry.npmjs.org/:_authToken=$NPM_READONLY_TOKEN"
68
+ RUN npm run build
69
+ ```
70
+
71
+ </TabItem>
72
+ </Tabs>
73
+
74
+ ### Option 2: Use environment variable
75
+
76
+ :::caution
77
+ Using an environment variable is not recommended because it requires [extra steps](#how-to-configure-with-legacy-startup) to rotate the token in projects using `startup` v31 or earlier.
78
+ :::
79
+
80
+ To use an environment variable to configure the npm token, modify `.npmrc` to get the token from an environment variable,
81
+
82
+ ```npmrc title=".npmrc"
83
+ //registry.npmjs.org/:_authToken=${NPM_READONLY_TOKEN}
84
+ ```
85
+
86
+ Then, in the CI action, set that environment variable to the organization secret. For example,
87
+
88
+ <Tabs
89
+ defaultValue="github"
90
+ values={[{ label: "Github Action", value: "github"}, {label: "Dockerfile", value: "docker"}]}>
91
+
92
+ <TabItem value="github">
93
+
94
+ ```yml
95
+ - run: npm run build
96
+ env:
97
+ NPM_READONLY_TOKEN: ${{ secrets.ST_NPM_READONLY_TOKEN }}
98
+ ```
99
+
100
+ </TabItem>
101
+ <TabItem value="docker">
102
+
103
+ ```dockerfile
104
+ ARG NPM_READONLY_TOKEN
105
+
106
+ ENV NPM_READONLY_TOKEN=${NPM_READONLY_TOKEN}
107
+
108
+ RUN npm run build
109
+ ```
110
+
111
+ </TabItem></Tabs>
112
+
113
+ ## How to Configure Development Environment When Not Using Startup v32 or Later {#how-to-configure-with-legacy-startup}
114
+
115
+ :::note
116
+ These instructions are only for projects using [Option 2](#option-2-use-environment-variable) with `startup` v31 or earlier (or that are not using `startup` at all);
117
+ `startup` v32 automatically detects and provides environment variables to `npm install`.
118
+ :::
119
+
120
+ If your project is [using an environment variable](#option-2-use-environment-variable), and it isn't using `startup` v32 or later, then developers must also defined the same environment variable manually. And they must repeat these steps each time the token is rotated,
121
+
122
+ 1. Run `npx --yes @servicetitan/startup@latest install` to add the token to your per-user configuration.
123
+ 2. Run `npm config get userconfig` to get the location of your per-user configuration file.
124
+ 3. Find the token in the configuration file, on the line that starts `//registry.npmjs.org/:_authToken=`
125
+ 4. Create a local environment variable with the token. Use the same variable name as in project's `.npmrc`.
126
+
127
+ :::tip
128
+ On MacOS you can accomplish steps 2 and 3 with this single command:
129
+
130
+ ```sh
131
+ grep registry.npmjs.org < $(npm config get userconfig)
132
+ ```
133
+
134
+ :::
135
+
136
+ ## How to Use --no-token
137
+
138
+ If startup mistakes your CI environment for a development machine and it isn't practical to add a `CI` environment variable to the workflow, use `--no-token` to instruct `startup install` not to attempt to configure the npm token.
139
+
140
+ Create a `build:ci` script and accompanying pre script that runs `bootstrap` with `-- --no-token`:
141
+
142
+ ```json title="package.json"
143
+ "scripts": {
144
+ "prebuild:ci": "npm run bootstrap -- --no-token",
145
+ "build:ci": "startup build"
146
+ },
147
+ ```
148
+
149
+ Then, call `build:ci` instead of `build` in the CI workflow:
150
+
151
+ ```sh
152
+ npm run build:ci
153
+ ```
154
+
155
+ :::note
156
+ This assumes you're using this recommended `bootstrap` script:
157
+
158
+ ```json
159
+ "scripts": {
160
+ "bootstrap": "npx --yes @servicetitan/startup install",
161
+ }
162
+ ```
163
+
164
+ :::
@@ -192,6 +192,32 @@ npm pkg set "main"="dist/index.js" -w packages/lib
192
192
 
193
193
  ---
194
194
 
195
+ ### require-all-react-dependencies
196
+
197
+ Ensures that any package which depends on either `react` or `react-dom` explicitly lists both as dependencies.
198
+ React and ReactDOM are tightly coupled and should always be paired together to avoid runtime issues.
199
+
200
+ #### ✅ Autofix
201
+
202
+ This rule supports autofix.
203
+ It adds the missing `react` or `react-dom` dependency, using the same version that is already present. For example, given:
204
+
205
+ ```json title="package.json"
206
+ {
207
+ "dependencies": {
208
+ "react": "^18.3.1"
209
+ }
210
+ }
211
+ ```
212
+
213
+ It runs:
214
+
215
+ ```sh
216
+ npm pkg set dependencies["react-dom"]="^18.3.1"
217
+ ```
218
+
219
+ ---
220
+
195
221
  ### require-explicit-side-effects
196
222
 
197
223
  Warns if a package is missing the `sideEffects` property in its `package.json`.
@@ -290,6 +316,22 @@ It sets the version of each dependency to the [highest](#highest-versions) of th
290
316
 
291
317
  ---
292
318
 
319
+ ### require-one-react-version
320
+
321
+ Ensures the same version of `react` and `react-dom` is used across the workspace.
322
+
323
+ These two packages are fundamentally designed to work together, with `react-dom` relying heavily on the internal structures and mechanisms exposed by `react`.
324
+
325
+ While React 17 introduced features for gradual upgrades and potentially using multiple React versions in specific scenarios, maintaining synchronized versions of `react` and `react-dom` is the recommended and safest practice to ensure compatibility and avoid unforeseen issues.
326
+
327
+ #### ✅ Autofix
328
+
329
+ This rule supports autofix.
330
+ It sets `react` and `react-dom` to the most common version in the repo.
331
+ If multiple versions tie for the most common, it uses the [highest](#highest-versions) version among the top contenders.
332
+
333
+ ---
334
+
293
335
  ### require-one-uikit-version
294
336
 
295
337
  Ensures the same version of related `uikit` packages is installed across the workspace.
@@ -17,4 +17,4 @@ Runs package in the development mode. Applications will be hosted on sequential
17
17
 
18
18
  The `start` command executes the same steps as the `build` command, then watches for changes and automatically reruns each step as needed.
19
19
 
20
- See [Build Steps](/docs/frontend/uikit/startup/build#build-steps).
20
+ See [Build Steps](./build#build-steps).
@@ -2,10 +2,204 @@
2
2
  title: test
3
3
  ---
4
4
 
5
- Runs all existing tests in all packages.
5
+ Runs your project's tests.
6
6
 
7
- To run tests a subset of tests is possible to pass paths to specific directories or test files as positional parameters.
7
+ - Supports both Jest and Vitest.
8
+ - Automatically selects the test runner based on your workspace configuration or the `--runner` option.
9
+ - Handles runner-specific arguments and passes them through to the underlying test tool.
10
+
11
+ ## Usage
12
+
13
+ Run all tests with Jest (default):
14
+
15
+ ```sh
16
+ npx startup test
17
+ ```
18
+
19
+ Run all tests with Vitest:
20
+
21
+ ```sh
22
+ npx startup test --runner vitest
23
+ ```
24
+
25
+ :::tip
26
+ Use the double-dash (--) separator to pass arguments to an `npm` script.
27
+ This separator indicates that any arguments following it should be passed directly to the script being executed, rather than being interpreted by `npm` itself.
28
+ For example, given the standard `startup test` script:
29
+
30
+ ```json
31
+ {
32
+ "test": "npx startup test"
33
+ }
34
+ ```
35
+
36
+ Running,
37
+
38
+ ```sh
39
+ npm run test -- --runner vitest
40
+ ```
41
+
42
+ Executes,
43
+
44
+ ```sh
45
+ npx startup test --runner vitest
46
+ ```
47
+
48
+ :::
49
+
50
+ ## Running Specific Tests
51
+
52
+ To run specific tests, pass their file names or paths as additional arguments.
53
+ This works just like running Jest and Vitest directly, simply specify the file(s) you want to test:
54
+
55
+ ### Examples
56
+
57
+ Run a specific test with Jest (default):
58
+
59
+ ```sh
60
+ npx startup test app.test.tsx
61
+ ```
62
+
63
+ Run a specific test with Vitest:
64
+
65
+ ```sh
66
+ npx startup test --runner vitest app.test.tsx
67
+ ```
68
+
69
+ ## Using Jest
70
+
71
+ The `startup test` command uses the Jest test runner by default.
72
+
73
+ ### Configuration
74
+
75
+ The Jest runner provides a default, built-in configuration optimized for ServiceTitan workspaces.
76
+
77
+ You can override the defaults by specifying [Jest options](https://jestjs.io/docs/configuration) in your workspace `package.json` under the `"cli.jest"` key (the `"cli.test"` key also works, for backward compatibility). You can also use a standard Jest config file (such as `jest.config.js`) to override defaults.
78
+
79
+ Any options passed directly via the command line (e.g., `npx startup test --coverage`) take the highest precedence.
80
+
81
+ :::caution
82
+ If your project includes a standard Jest config file and also specifies Jest options in the workspace `package.json`, ensure that the configurations are mutually exclusive.
83
+ To avoid confusing or unexpected behavior, do not specify the same setting in both locations.
84
+ :::
85
+
86
+ ### Examples
87
+
88
+ Use the workspace `package.json` to set up code that runs before each test file is executed:
89
+
90
+ ```json title="package.json"
91
+ {
92
+ "cli": {
93
+ "jest": {
94
+ "setupFilesAfterEnv": ["<rootDir>/jest.setup.ts"]
95
+ }
96
+ }
97
+ }
98
+ ```
99
+
100
+ :::note
101
+ For backward compatibility, you may also use the `ci.test` key:
102
+ :::
103
+
104
+ ---
105
+
106
+ Use `jest.config.js` to increase Jest's `testTimeout` on Windows:
107
+
108
+ ```js title="jest.config.js"
109
+ module.exports = {
110
+ testTimeout: process.platform === 'win32' ? 60 * 1000 : undefined, // increase timeout on Windows
111
+ };
112
+ ```
113
+
114
+ ---
115
+
116
+ Use the command line to collect test coverage:
117
+
118
+ ```sh
119
+ npx startup test --coverage
120
+ ```
121
+
122
+ Use the command line to enable Jest's watch mode:
123
+
124
+ ```sh
125
+ npx startup test --watch
126
+ ```
127
+
128
+ ## Using Vitest
129
+
130
+ ✨ New in **v31.6.0** ✨
131
+
132
+ You can change the default test runner to `vitest` in your workspace's `package.json`:
133
+
134
+ ```json title="package.json"
135
+ {
136
+ "cli": {
137
+ "testRunner": "vitest"
138
+ }
139
+ }
140
+ ```
141
+
142
+ Or override it per invocation with the `--runner` option.
143
+
144
+ ### Configuration
145
+
146
+ The Vitest runner uses a layered configuration approach to provide sensible defaults while allowing full customization.
147
+
148
+ - Uses the default, built-in configuration as a base.
149
+ - Applies workspace overrides from `package.json`.
150
+ - Applies overrides from your Vitest config file.
151
+ - Applies command line options last.
152
+
153
+ 1. **Default Configuration:**
154
+ The runner starts with a built-in default configuration optimized for ServiceTitan workspaces. This includes sensible coverage settings, the environment (`jsdom`), file exclusions, and more.
155
+
156
+ 2. **Workspace Configuration:**
157
+ You can override defaults by specifying [Vitest options](https://vitest.dev/config) in your workspace `package.json` under the `"cli.vitest"` key.
158
+
159
+ 3. **Vitest Config File:**
160
+ If your project includes a standard Vitest config file (such as `vitest.config.ts` or `vite.config.ts`), its settings will override both the defaults and workspace configuration.
161
+
162
+ 4. **Command Line Arguments:**
163
+ Any options passed directly via the command line (e.g., `npx startup test --runner vitest --coverage`) take highest precedence and override all previous configuration layers.
164
+
165
+ ### Examples
166
+
167
+ Use workspace `package.json` to make `vitest` APIs available [globally](https://vitest.dev/config/#globals) like Jest:
168
+
169
+ ```json title="package.json"
170
+ {
171
+ "cli": {
172
+ "vitest": {
173
+ "globals": true
174
+ }
175
+ }
176
+ }
177
+ ```
178
+
179
+ ---
180
+
181
+ Use Vitest config file to disable watch mode:
182
+
183
+ ```ts title="vitest.config.mts"
184
+ import { defineConfig } from 'vitest/config';
185
+
186
+ export default defineConfig({
187
+ test: {
188
+ watch: false,
189
+ },
190
+ });
191
+ ```
192
+
193
+ ---
194
+
195
+ Use the command line to disable watch mode:
196
+
197
+ ```sh
198
+ npx startup test --runner vitest --run
199
+ ```
200
+
201
+ Use the command line to collect code coverage:
8
202
 
9
203
  ```sh
10
- npx startup test -- packages/desktop/app/modules/inventory/
204
+ npx startup test --runner vitest --coverage
11
205
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@servicetitan/docs-uikit",
3
- "version": "31.5.1",
3
+ "version": "32.0.0",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -16,5 +16,5 @@
16
16
  "cli": {
17
17
  "webpack": false
18
18
  },
19
- "gitHead": "8eb35a6e98b9e3cb3b18b452ce9ba76f278d0e61"
19
+ "gitHead": "af064114b3ec8c08a4b693a66c4062a52b03504c"
20
20
  }