Skip to content

Commit ee58160

Browse files
CLOUDP-352308 Enhanced CLI options for robust linking (#3311)
* feat: update CLI options for improved linking process * docs: add changelog * docs: apply review feedback * apply: review comments + docs update * apply: review comments (2)
1 parent 43810b4 commit ee58160

File tree

10 files changed

+176
-79
lines changed

10 files changed

+176
-79
lines changed

.changeset/giant-phones-jog.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@lg-tools/link': minor
3+
'@lg-tools/cli': minor
4+
---
5+
6+
- Added new flags to `link` script to cover broader use cases:
7+
- `--no-parallel`: Run the link command sequentially for each package. Useful for when the default parallel approach fails
8+
- `--launch-env`: A string of environment variable lines as `KEY=VALUE`, separated by a newline. Only the specified environment variables will be used during npm link commands in the source and destination directories. This is useful to workaround environment variable pollution by tools such as version managers (e.g., asdf) or script runners (e.g., pnpm) that override the script's environment which impacts the launched `npm link` commands. We recommend using `--launch-env="$(env)"` to use your original shell environment.
9+
- Improve documentation for linking in DEVELOPER.md

DEVELOPER.md

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,22 +68,54 @@ When you run the scaffold script, a `README` file will appear, which is a templa
6868

6969
### Locally
7070

71-
We use @testing-library/react for writing tests locally. This library helps mock out user interactions with components. You can run all tests by running `yarn test` or turn on watch mode with `yarn test --watch`.
71+
We use @testing-library/react for writing tests locally. This library helps mock out user interactions with components. You can run all tests by running `pnpm test` or turn on watch mode with `pnpm test --watch`.
7272

7373
### Linking
7474

75-
We also have a link script, such that you can test components that are in development in environments beyond Storybook. To do so, run `yarn run link -- [path-to-application]`.
75+
We provide a `link` script to help you test in-development components within environments outside of Storybook such as in your application. To do this, run:
7676

77-
Note: There are some known issues using `yarn link` from yarn workspaces. Using Verdaccio, while more involved, is the more reliable and recommended approach for testing in an external project.
77+
```
78+
pnpm run link --to=[path-to-application]
79+
```
80+
81+
The script does the following in order:
82+
83+
- It scans the destination application for any installed `leafygreen-ui` components in its `node_modules` folder.
84+
**NOTE:** If the package is new and unpublished/not installed, you will need to create a directory for the new component within the destination application inside `node_modules` before running this command.
85+
- If any `leafygreen-ui` components are found then:
86+
- The script runs `pnpm link` in the corresponding leafygreen-ui package directory to publish a link to the package in the pnpm global registry.
87+
- The script then runs `pnpm link <package-name>` in the destination application directory to install the package from the published link in the pnpm global registry.
88+
89+
After the script completes, you can make changes directly to the component in your local `leafygreen-ui` repository. Once you do this, make sure to rebuild the component and the changes will be visible on your running application.
90+
91+
If you encounter issues while linking, try the following any of the following flags:
92+
93+
- When linking multiple packages with `--scope` or multiple `--packages` options, link processes run in parallel by default. If you experience failures, add the `--no-parallel` flag to run the linking tasks sequentially, which can help avoid race conditions.
94+
95+
- If you are using a Node version manager such as `asdf` or `nvm`, add the `--launch-env="$(env)"` flag. This ensures the link script spawns commands using your current shell’s environment, preventing environment pollutions that may happen through the tooling of the version manager.
96+
97+
- In your destination application project, make sure your module resolver picks up `'react'` from your own `node_modules` (not from LeafyGreen’s). If using webpack, you can enforce this by adding an alias in your webpack configuration:
98+
99+
```js
100+
resolve: {
101+
alias: {
102+
react: path.dirname(require.resolve('react/package.json'))
103+
},
104+
},
105+
```
106+
107+
Note: There are some known issues in linking packages with `pnpm link`, if you encounter issues, try using a local registry instead. `Verdaccio` for example is a more reliable and recommended approach for testing in an external project.
78108

79109
### Using a local registry (Verdaccio)
80110

81-
Publishing test versions to a local registry can be helpful when you need to make changes and test in an external app (or other library). To do this, you can install and use [Verdaccio](https://verdaccio.org/)
111+
Publishing test versions to a local registry can be helpful when you need to make changes and test
112+
in an external app (or other library). To do this, you can install and
113+
use [Verdaccio](https://verdaccio.org/)
82114

83115
#### 1. Install `verdaccio`
84116

85117
```bash
86-
yarn install --global verdaccio
118+
pnpm install --global verdaccio
87119
```
88120

89121
#### 2. Start `verdaccio`, and make note on the localhost port (should be `http://localhost:4873/` by default)
@@ -147,23 +179,23 @@ With your local version published, open up some external app. If the app uses a
147179
Next, install the newly published version of your package in the external project.
148180

149181
```bash
150-
yarn install @leafygreen-ui/<package-name>
182+
pnpm install @leafygreen-ui/<package-name>
151183
```
152184

153185
#### 6. Publishing additional versions
154186

155-
To publish additional versions, manually the version number in `packages/<package-name>/package.json`, and re-run step 4. Then, either manually update the external project's `package.json`, or re-run `yarn install @leafygreen-ui/<package-name>`.
187+
To publish additional versions, manually update the version number in `packages/<package-name>/package.json`, and re-run step 4. Then, either manually update the external project's `package.json`, or re-run `pnpm install @leafygreen-ui/<package-name>`.
156188

157189
#### 7. Publishing to NPM
158190

159191
If you want to stop publishing to and/or reading from your local Verdaccio server, remove the reference to the server URL in `~/.npmrc` (and the external project's local `.npmrc`/`.yarnrc`)
160192

161193
## Creating a new component
162194

163-
- Run `yarn create-package <package-name>` to create a new component directory with default configurations
195+
- Run `pnpm create-package <package-name>` to create a new component directory with default configurations
164196
- Add the new component to `build.tsconfig.json`
165197
- If you are using any `leafygreen-ui` dependencies in your new component, add the dependency to the component directory's `tsconfig.json`.
166-
- Run `yarn run init` to link all packages before starting development
198+
- Run `pnpm run init` to link all packages before starting development
167199

168200
## Marking a Storybook story to be imported in mongodb.design
169201

README.md

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -240,26 +240,7 @@ pnpm build --filter="[package]"
240240

241241
### Development within an Application
242242

243-
To actively develop `leafygreen-ui` components within an application, the following script will link all `leafygreen-ui` components within your application to the local `leafygreen-ui` repository.
244-
245-
This will allow you to make changes to your local repository of `leafygreen-ui` and see those changes immediately reflected within your running application. This allows you to develop both in isolation (within `leafygreen-ui`) and in the context of your application.
246-
247-
To do this, clone this repository and navigate to the root directory (where `package.json` is located), then run the following:
248-
249-
```
250-
pnpm run link -- ${PATH_TO_APPLICATION}
251-
```
252-
253-
The script does several things in order:
254-
255-
1. This builds every `leafygreen-ui` component so they are ready to be linked
256-
257-
2. It scans your application for any installed `leafygreen-ui` components in your `node_modules/@leafygreen-ui` folder.
258-
**NOTE:** If the package is new and unpublished/not installed, you will need to create a directory for the new component within your application inside `node_modules/@leafygreen-ui` before running this command.
259-
260-
3. If any `leafygreen-ui` components are found then the script uses `pnpm link` to link every `node_modules/@leafygreen-ui` module to your local `leafygreen-ui` repository.
261-
262-
After the script completes, you can make changes directly to the component in your local `leafygreen-ui` repository. Once you do this, run `pnpm build` in the root of the `leafygreen-ui` repository and the changes will be visible on your running application.
243+
To actively develop `leafygreen-ui` components within an application, we have a `link` script that will link all `leafygreen-ui` components within your application to the local `leafygreen-ui` repository. See the [DEVELOPER.md](./DEVELOPER.md) file for more information.
263244

264245
## Creating New Component
265246

tools/cli/src/index.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,22 @@ cli
8383
'When running from a consuming application, defines the source of linked packages',
8484
)
8585
.option('-v --verbose', 'Prints additional information to the console', false)
86-
.option('--scope <name>', 'The NPM organization')
86+
.option(
87+
'--no-parallel',
88+
'Run the link command sequentially for each package. Useful for debugging or when the parallel approach fails',
89+
)
90+
.option(
91+
'--launch-env <launchEnv>',
92+
'A string of environment variable lines as `KEY=VALUE`, separated by a newline. ' +
93+
'Only the specified environment variables will be used during npm link commands in the source and destination directories. ' +
94+
'This is useful to workaround environment variable pollution by tools such as version managers (e.g., asdf) or script runners (e.g., pnpm) that interfere with `npm link`. ' +
95+
'We recommend using --launch-env="$(env)" to use your original shell environment.',
96+
undefined,
97+
)
98+
.option('--scope <name>', 'The NPM organization, e.g. @lg-charts')
8799
.option(
88100
'--packages <names...>',
89-
'Specific package names (requires `scope` option, or full package name)',
101+
'Specific package names (requires `scope` option, or full package name) e.g. `@lg-charts/core` or `core` if @lg-charts is the scope',
90102
)
91103
.action(linkPackages);
92104

tools/link/src/link.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,32 @@ interface LinkOptions {
1313
verbose: boolean;
1414
to?: string;
1515
from?: string;
16+
parallel?: boolean;
17+
launchEnv?: string;
1618
}
1719

1820
export async function linkPackages(
1921
dest: string | undefined,
2022
opts: LinkOptions,
2123
) {
22-
const { verbose, scope: scopeFlag, packages, to, from } = opts;
24+
const {
25+
verbose,
26+
scope: scopeFlag,
27+
packages,
28+
to,
29+
from,
30+
parallel,
31+
launchEnv: launchEnvString,
32+
} = opts;
33+
let launchEnv: NodeJS.ProcessEnv | undefined;
34+
35+
if (launchEnvString) {
36+
const keyValuePairs = launchEnvString
37+
.split('\n')
38+
.filter(line => line.trim() && line.includes('='))
39+
.map(line => line.trim().split(/=(.*)/).slice(0, 2));
40+
launchEnv = Object.fromEntries(keyValuePairs);
41+
}
2342

2443
const rootDir = process.cwd();
2544

@@ -75,13 +94,16 @@ export async function linkPackages(
7594
for (const [scopeName, scopePath] of Object.entries(availableScopes)) {
7695
if (!scopeFlag || scopeFlag.includes(scopeName)) {
7796
linkPromises.push(
78-
linkPackagesForScope(
79-
{ scopeName, scopePath },
97+
linkPackagesForScope({
98+
scopeName,
99+
scopePath,
80100
source,
81101
destination,
82102
packages,
83103
verbose,
84-
),
104+
parallel,
105+
launchEnv,
106+
}),
85107
);
86108
}
87109
}

tools/link/src/utils/createLinkFrom.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { PackageDetails } from './types';
1010
interface CreateLinkOptions extends PackageDetails {
1111
verbose?: boolean;
1212
packageManager?: SupportedPackageManager;
13+
env?: NodeJS.ProcessEnv;
1314
}
1415

1516
/**
@@ -24,6 +25,7 @@ export async function createLinkFrom(
2425
packageName,
2526
packageManager,
2627
verbose,
28+
env,
2729
}: CreateLinkOptions,
2830
): Promise<void> {
2931
const scopeSrc = scopePath;
@@ -44,6 +46,7 @@ export async function createLinkFrom(
4446
name: `link_src:${packageName}`,
4547
cwd: path.join(packagesDirectory, packageName),
4648
verbose,
49+
env,
4750
});
4851
} catch (_) {
4952
throw new Error(`Couldn't create link for package: ${packageName}`);

tools/link/src/utils/install.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export async function installPackages(
88
options?: {
99
packageManager?: SupportedPackageManager;
1010
verbose?: boolean;
11+
env?: NodeJS.ProcessEnv;
1112
},
1213
): Promise<void> {
1314
if (fsx.existsSync(path)) {
@@ -18,6 +19,7 @@ export async function installPackages(
1819
name: 'install',
1920
cwd: path,
2021
verbose: options?.verbose,
22+
env: options?.env,
2123
});
2224
} catch (err) {
2325
throw new Error(`Error installing packages\n` + err);

tools/link/src/utils/linkPackageTo.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ interface LinkPackagesToOptions
88
extends Pick<PackageDetails, 'scopeName' | 'packageName'> {
99
verbose?: boolean;
1010
packageManager?: SupportedPackageManager;
11+
env?: NodeJS.ProcessEnv;
1112
}
1213

1314
/**
@@ -16,7 +17,13 @@ interface LinkPackagesToOptions
1617
*/
1718
export async function linkPackageTo(
1819
destination: string,
19-
{ scopeName, packageName, verbose, packageManager }: LinkPackagesToOptions,
20+
{
21+
scopeName,
22+
packageName,
23+
verbose,
24+
packageManager,
25+
env,
26+
}: LinkPackagesToOptions,
2027
): Promise<void> {
2128
const fullPackageName = `${scopeName}/${packageName}`;
2229
// eslint-disable-next-line no-console
@@ -30,6 +37,7 @@ export async function linkPackageTo(
3037
name: `link_dst:${packageName}`,
3138
cwd: destination,
3239
verbose,
40+
env,
3341
});
3442
} catch (_) {
3543
throw new Error(`Couldn't link package: ${fullPackageName}`);

tools/link/src/utils/linkPackagesForScope.spec.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,12 @@ describe('tools/link/linkPackagesForScope', () => {
3737
fsx.mkdirSync('./tmp/app/node_modules/@example');
3838
fsx.mkdirSync('./tmp/app/node_modules/@example/test-package');
3939

40-
await linkPackagesForScope(
41-
{
42-
scopeName: '@example',
43-
scopePath: 'scope',
44-
},
45-
path.resolve('./tmp/packages'),
46-
path.resolve('./tmp/app'),
47-
);
40+
await linkPackagesForScope({
41+
scopeName: '@example',
42+
scopePath: 'scope',
43+
source: path.resolve('./tmp/packages'),
44+
destination: path.resolve('./tmp/app'),
45+
});
4846

4947
// Creates links
5048
expect(spawnLoggedSpy).toHaveBeenCalledWith(

0 commit comments

Comments
 (0)