@mui/internal-code-infra 0.0.4-canary.62 → 0.0.4-canary.64

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.
@@ -135,6 +135,19 @@ export declare function readPackageJson(packagePath: string): Promise<import('..
135
135
  * @returns {Promise<void>}
136
136
  */
137
137
  export declare function writePackageJson(packagePath: string, packageJson: Object): Promise<void>;
138
+ /**
139
+ * Write the computed overrides into whichever manifest already defines
140
+ * overrides — preferring pnpm-workspace.yaml, falling back to package.json
141
+ * `pnpm.overrides`, defaulting to pnpm-workspace.yaml — and persist the result.
142
+ * A missing pnpm-workspace.yaml is treated as empty, so a fresh file is created
143
+ * with just the `overrides:` block. Rejects a `resolutions` field in
144
+ * package.json because pnpm 11 silently ignores it.
145
+ *
146
+ * @param {string} workspaceDir - Workspace root directory
147
+ * @param {Record<string, string>} overrides - Overrides to apply
148
+ * @returns {Promise<void>}
149
+ */
150
+ export declare function writeOverridesToWorkspace(workspaceDir: string, overrides: Record<string, string>): Promise<void>;
138
151
  /**
139
152
  * Resolve a package@version specifier to an exact version
140
153
  * @param {string} packageSpec - Package specifier in format "package@version"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mui/internal-code-infra",
3
- "version": "0.0.4-canary.62",
3
+ "version": "0.0.4-canary.64",
4
4
  "author": "MUI Team",
5
5
  "description": "Infra scripts and configs to be used across MUI repos.",
6
6
  "license": "MIT",
@@ -138,10 +138,11 @@
138
138
  "unified": "^11.0.5",
139
139
  "unified-lint-rule": "^3.0.1",
140
140
  "unist-util-visit": "^5.1.0",
141
+ "yaml": "^2.9.0",
141
142
  "yargs": "^18.0.0",
142
- "@mui/internal-babel-plugin-resolve-imports": "2.0.7-canary.36",
143
+ "@mui/internal-babel-plugin-display-name": "1.0.4-canary.20",
143
144
  "@mui/internal-babel-plugin-minify-errors": "2.0.8-canary.27",
144
- "@mui/internal-babel-plugin-display-name": "1.0.4-canary.20"
145
+ "@mui/internal-babel-plugin-resolve-imports": "2.0.7-canary.36"
145
146
  },
146
147
  "peerDependencies": {
147
148
  "@next/eslint-plugin-next": "*",
@@ -191,7 +192,7 @@
191
192
  "publishConfig": {
192
193
  "access": "public"
193
194
  },
194
- "gitSha": "ca1c0b42f26266f8a75b7075025a98a6ceb091c7",
195
+ "gitSha": "42ab800dc8926bfe1c6ea3020a013565143f1bd6",
195
196
  "scripts": {
196
197
  "build": "tsgo -p tsconfig.build.json",
197
198
  "typescript": "tsgo -noEmit",
@@ -9,6 +9,7 @@ import { Transform } from 'node:stream';
9
9
  import { Worker } from 'node:worker_threads';
10
10
 
11
11
  const DEFAULT_CONCURRENCY = 4;
12
+ const SERVER_START_TIMEOUT = 10000;
12
13
 
13
14
  const crawlWorkerUrl = new URL('./crawlWorker.mjs', import.meta.url);
14
15
 
@@ -620,30 +621,56 @@ export async function crawl(rawOptions) {
620
621
 
621
622
  /** @type {AbortController | null} */
622
623
  let controller = null;
623
- if (options.startCommand) {
624
- console.log(chalk.blue(`Starting server with "${options.startCommand}"...`));
625
- controller = new AbortController();
626
- const appProcess = execaCommand(options.startCommand, {
627
- stdout: 'pipe',
628
- stderr: 'pipe',
629
- cancelSignal: controller.signal,
630
- env: {
631
- FORCE_COLOR: '1',
632
- ...process.env,
633
- },
634
- });
635
624
 
636
- // Prefix server logs
637
- const serverPrefix = chalk.gray('server: ');
638
- appProcess.stdout.pipe(prefixLines(serverPrefix)).pipe(process.stdout);
639
- appProcess.stderr.pipe(prefixLines(serverPrefix)).pipe(process.stderr);
640
- appProcess.catch(() => {});
625
+ try {
626
+ if (options.startCommand) {
627
+ console.log(chalk.blue(`Starting server with "${options.startCommand}"...`));
628
+ controller = new AbortController();
629
+ const appProcess = execaCommand(options.startCommand, {
630
+ stdout: 'pipe',
631
+ stderr: 'pipe',
632
+ cancelSignal: controller.signal,
633
+ env: {
634
+ FORCE_COLOR: '1',
635
+ ...process.env,
636
+ },
637
+ });
638
+
639
+ // Prefix server logs
640
+ const serverPrefix = chalk.gray('server: ');
641
+ appProcess.stdout.pipe(prefixLines(serverPrefix)).pipe(process.stdout);
642
+ appProcess.stderr.pipe(prefixLines(serverPrefix)).pipe(process.stderr);
643
+ appProcess.catch(() => {});
641
644
 
642
- await pollUrl(options.host, 10000);
645
+ // Poll the first page we are about to crawl (resolved against host) so we
646
+ // wait for the actual entry point to be serveable rather than the
647
+ // homepage, which may be a different (slower) page.
648
+ const healthcheckUrl = new URL(options.seedUrls[0] ?? '/', options.host).href;
649
+ await pollUrl(healthcheckUrl, SERVER_START_TIMEOUT);
643
650
 
644
- console.log(`Server started on ${chalk.underline(options.host)}`);
651
+ console.log(`Server started on ${chalk.underline(options.host)}`);
652
+ }
653
+
654
+ return await runCrawl(options, startTime);
655
+ } finally {
656
+ // Always stop the server, even when startup or crawling throws. Without
657
+ // this, a failed healthcheck (or any error) would leave the dev server
658
+ // running, which on slow environments (e.g. Netlify) leads to orphaned
659
+ // servers piling up across retries.
660
+ if (controller) {
661
+ console.log(chalk.blue('Stopping server...'));
662
+ controller.abort();
663
+ }
645
664
  }
665
+ }
646
666
 
667
+ /**
668
+ * Runs the crawl against an already-running server.
669
+ * @param {ResolvedCrawlOptions} options - Fully resolved crawl options
670
+ * @param {number} startTime - Timestamp (ms) when the crawl began, for duration reporting
671
+ * @returns {Promise<CrawlResult>} Crawl results including all links, pages, and issues found
672
+ */
673
+ async function runCrawl(options, startTime) {
647
674
  const knownTargets = await resolveKnownTargets(options);
648
675
 
649
676
  /** @type {Map<string, Promise<PageData>>} */
@@ -733,11 +760,6 @@ export async function crawl(rawOptions) {
733
760
 
734
761
  await queue.waitAll();
735
762
 
736
- if (controller) {
737
- console.log(chalk.blue('Stopping server...'));
738
- controller.abort();
739
- }
740
-
741
763
  const results = new Map(
742
764
  await Promise.all(
743
765
  Array.from(crawledPages.entries(), async ([a, b]) => /** @type {const} */ ([a, await b])),
@@ -1,11 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import fs from 'node:fs/promises';
4
- import os from 'node:os';
5
- import path from 'node:path';
6
3
  import * as semver from 'semver';
7
4
  import { $ } from 'execa';
8
- import { resolveVersion, findDependencyVersionFromSpec } from '../utils/pnpm.mjs';
5
+ import { findWorkspaceDir } from '@pnpm/find-workspace-dir';
6
+ import {
7
+ resolveVersion,
8
+ findDependencyVersionFromSpec,
9
+ writeOverridesToWorkspace,
10
+ } from '../utils/pnpm.mjs';
9
11
 
10
12
  /**
11
13
  * @typedef {Object} Args
@@ -103,11 +105,8 @@ async function handler(args) {
103
105
  // eslint-disable-next-line no-console
104
106
  console.log(`Using overrides: ${JSON.stringify(overrides, null, 2)}`);
105
107
 
106
- const packageJsonPath = path.resolve(process.cwd(), 'package.json');
107
- const packageJson = JSON.parse(await fs.readFile(packageJsonPath, { encoding: 'utf8' }));
108
- packageJson.resolutions ??= {};
109
- Object.assign(packageJson.resolutions, overrides);
110
- await fs.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}${os.EOL}`);
108
+ const workspaceDir = (await findWorkspaceDir(process.cwd())) ?? process.cwd();
109
+ await writeOverridesToWorkspace(workspaceDir, overrides);
111
110
 
112
111
  await $({ stdio: 'inherit' })`pnpm dedupe`;
113
112
  }
@@ -5,6 +5,7 @@ import { $ } from 'execa';
5
5
  import * as fs from 'node:fs/promises';
6
6
  import * as path from 'node:path';
7
7
  import * as semver from 'semver';
8
+ import { parseDocument, isMap } from 'yaml';
8
9
 
9
10
  /**
10
11
  * @typedef {Object} PrivatePackage
@@ -385,6 +386,68 @@ export async function writePackageJson(packagePath, packageJson) {
385
386
  await fs.writeFile(path.join(packagePath, 'package.json'), content);
386
387
  }
387
388
 
389
+ /**
390
+ * Write the computed overrides into whichever manifest already defines
391
+ * overrides — preferring pnpm-workspace.yaml, falling back to package.json
392
+ * `pnpm.overrides`, defaulting to pnpm-workspace.yaml — and persist the result.
393
+ * A missing pnpm-workspace.yaml is treated as empty, so a fresh file is created
394
+ * with just the `overrides:` block. Rejects a `resolutions` field in
395
+ * package.json because pnpm 11 silently ignores it.
396
+ *
397
+ * @param {string} workspaceDir - Workspace root directory
398
+ * @param {Record<string, string>} overrides - Overrides to apply
399
+ * @returns {Promise<void>}
400
+ */
401
+ export async function writeOverridesToWorkspace(workspaceDir, overrides) {
402
+ const workspaceYamlPath = path.join(workspaceDir, 'pnpm-workspace.yaml');
403
+ const yamlPromise = fs.readFile(workspaceYamlPath, { encoding: 'utf8' }).catch((error) => {
404
+ if (/** @type {NodeJS.ErrnoException} */ (error).code !== 'ENOENT') {
405
+ throw error;
406
+ }
407
+ return '';
408
+ });
409
+ const [rootPackageJson, yamlSource] = await Promise.all([
410
+ readPackageJson(workspaceDir),
411
+ yamlPromise,
412
+ ]);
413
+
414
+ const { resolutions } = rootPackageJson;
415
+ if (resolutions && Object.keys(resolutions).length > 0) {
416
+ throw new Error(
417
+ 'Found a "resolutions" field in package.json. pnpm 11 ignores it silently. ' +
418
+ 'Move those entries into the "overrides:" key of pnpm-workspace.yaml.',
419
+ );
420
+ }
421
+
422
+ // Parsed once, reused for both the read (does it have overrides?) and the write.
423
+ const doc = parseDocument(yamlSource);
424
+ const existing = doc.get('overrides');
425
+ const workspaceHasOverrides = isMap(existing) && existing.items.length > 0;
426
+
427
+ const pnpm = /** @type {{ overrides?: Record<string, string> } | undefined} */ (
428
+ rootPackageJson.pnpm
429
+ );
430
+ const packageJsonOverrides = pnpm?.overrides;
431
+
432
+ // Write where overrides already live; default to the workspace file.
433
+ if (
434
+ !workspaceHasOverrides &&
435
+ packageJsonOverrides &&
436
+ Object.keys(packageJsonOverrides).length > 0
437
+ ) {
438
+ await writePackageJson(workspaceDir, {
439
+ ...rootPackageJson,
440
+ pnpm: { ...pnpm, overrides: { ...packageJsonOverrides, ...overrides } },
441
+ });
442
+ return;
443
+ }
444
+
445
+ for (const [name, version] of Object.entries(overrides)) {
446
+ doc.setIn(['overrides', name], version);
447
+ }
448
+ await fs.writeFile(workspaceYamlPath, doc.toString());
449
+ }
450
+
388
451
  /**
389
452
  * Resolve a package@version specifier to an exact version
390
453
  * @param {string} packageSpec - Package specifier in format "package@version"
@@ -3,7 +3,12 @@ import * as path from 'node:path';
3
3
  import { describe, it, expect } from 'vitest';
4
4
 
5
5
  import { makeTempDir } from './testUtils.mjs';
6
- import { checkPublishDependencies } from './pnpm.mjs';
6
+ import {
7
+ checkPublishDependencies,
8
+ readPackageJson,
9
+ writePackageJson,
10
+ writeOverridesToWorkspace,
11
+ } from './pnpm.mjs';
7
12
 
8
13
  /**
9
14
  * Write a package.json file to a temp subdirectory and return the directory path.
@@ -578,3 +583,149 @@ describe('checkPublishDependencies', () => {
578
583
  });
579
584
  });
580
585
  });
586
+
587
+ /**
588
+ * Set up a temp workspace with a package.json and optional pnpm-workspace.yaml.
589
+ * @param {object} packageJson - package.json contents
590
+ * @param {string} [workspaceYaml] - pnpm-workspace.yaml contents, omitted to skip the file
591
+ * @returns {Promise<string>} The workspace directory
592
+ */
593
+ async function makeWorkspace(packageJson, workspaceYaml) {
594
+ const cwd = await makeTempDir();
595
+ const writes = [writePackageJson(cwd, packageJson)];
596
+ if (workspaceYaml !== undefined) {
597
+ writes.push(fs.writeFile(path.join(cwd, 'pnpm-workspace.yaml'), workspaceYaml));
598
+ }
599
+ await Promise.all(writes);
600
+ return cwd;
601
+ }
602
+
603
+ /**
604
+ * @param {string} cwd
605
+ * @returns {Promise<string>}
606
+ */
607
+ function readWorkspaceYaml(cwd) {
608
+ return fs.readFile(path.join(cwd, 'pnpm-workspace.yaml'), 'utf8');
609
+ }
610
+
611
+ describe('writeOverridesToWorkspace', () => {
612
+ describe('writing to pnpm-workspace.yaml', () => {
613
+ it('creates the file with an overrides block when none exists', async () => {
614
+ const cwd = await makeWorkspace({ name: 'root' });
615
+
616
+ await writeOverridesToWorkspace(cwd, { foo: '1.2.3' });
617
+
618
+ expect(await readWorkspaceYaml(cwd)).toMatchInlineSnapshot(`
619
+ "overrides:
620
+ foo: 1.2.3
621
+ "
622
+ `);
623
+ });
624
+
625
+ it('merges into an existing block, preserving comments and quoting scoped names', async () => {
626
+ const cwd = await makeWorkspace(
627
+ { name: 'root' },
628
+ [
629
+ 'packages:',
630
+ " - 'packages/*'",
631
+ 'overrides:',
632
+ ' # keep this pin',
633
+ " bar: '2.0.0'",
634
+ '',
635
+ ].join('\n'),
636
+ );
637
+
638
+ await writeOverridesToWorkspace(cwd, { playwright: '1.49.1', '@playwright/test': '1.49.1' });
639
+
640
+ expect(await readWorkspaceYaml(cwd)).toMatchInlineSnapshot(`
641
+ "packages:
642
+ - 'packages/*'
643
+ overrides:
644
+ # keep this pin
645
+ bar: '2.0.0'
646
+ playwright: 1.49.1
647
+ "@playwright/test": 1.49.1
648
+ "
649
+ `);
650
+ // The package.json is left untouched.
651
+ expect(await readPackageJson(cwd)).toEqual({ name: 'root' });
652
+ });
653
+
654
+ it('overwrites a same-named override, keeping its quote style', async () => {
655
+ const cwd = await makeWorkspace(
656
+ { name: 'root' },
657
+ ['overrides:', " foo: '1.0.0'", ''].join('\n'),
658
+ );
659
+
660
+ await writeOverridesToWorkspace(cwd, { foo: '2.0.0' });
661
+
662
+ expect(await readWorkspaceYaml(cwd)).toMatchInlineSnapshot(`
663
+ "overrides:
664
+ foo: '2.0.0'
665
+ "
666
+ `);
667
+ });
668
+
669
+ it('prefers the workspace file when both manifests define overrides', async () => {
670
+ const cwd = await makeWorkspace(
671
+ { name: 'root', pnpm: { overrides: { baz: '3.0.0' } } },
672
+ ['overrides:', " bar: '2.0.0'", ''].join('\n'),
673
+ );
674
+
675
+ await writeOverridesToWorkspace(cwd, { foo: '1.2.3' });
676
+
677
+ expect(await readWorkspaceYaml(cwd)).toContain('foo: 1.2.3');
678
+ // package.json overrides are left where they were.
679
+ expect(await readPackageJson(cwd)).toEqual({
680
+ name: 'root',
681
+ pnpm: { overrides: { baz: '3.0.0' } },
682
+ });
683
+ });
684
+ });
685
+
686
+ describe('writing to package.json', () => {
687
+ it('honors the package.json location when no workspace overrides exist', async () => {
688
+ const cwd = await makeWorkspace({
689
+ name: 'root',
690
+ pnpm: { overrides: { foo: '1.0.0' }, packageExtensions: { thing: {} } },
691
+ });
692
+
693
+ await writeOverridesToWorkspace(cwd, { bar: '2.0.0' });
694
+
695
+ expect(await readPackageJson(cwd)).toEqual({
696
+ name: 'root',
697
+ pnpm: { overrides: { foo: '1.0.0', bar: '2.0.0' }, packageExtensions: { thing: {} } },
698
+ });
699
+ // No workspace file is created.
700
+ await expect(fs.access(path.join(cwd, 'pnpm-workspace.yaml'))).rejects.toThrow();
701
+ });
702
+
703
+ it('lets computed overrides win over an existing package.json override', async () => {
704
+ const cwd = await makeWorkspace({ name: 'root', pnpm: { overrides: { foo: '1.0.0' } } });
705
+
706
+ await writeOverridesToWorkspace(cwd, { foo: '2.0.0' });
707
+
708
+ expect(await readPackageJson(cwd)).toEqual({
709
+ name: 'root',
710
+ pnpm: { overrides: { foo: '2.0.0' } },
711
+ });
712
+ });
713
+ });
714
+
715
+ describe('rejecting resolutions', () => {
716
+ it('throws and writes nothing when package.json has a resolutions field', async () => {
717
+ const cwd = await makeWorkspace({ name: 'root', resolutions: { foo: '1.0.0' } });
718
+
719
+ await expect(writeOverridesToWorkspace(cwd, { bar: '2.0.0' })).rejects.toThrow(/resolutions/);
720
+ await expect(fs.access(path.join(cwd, 'pnpm-workspace.yaml'))).rejects.toThrow();
721
+ });
722
+
723
+ it('ignores an empty resolutions field', async () => {
724
+ const cwd = await makeWorkspace({ name: 'root', resolutions: {} });
725
+
726
+ await writeOverridesToWorkspace(cwd, { foo: '1.2.3' });
727
+
728
+ expect(await readWorkspaceYaml(cwd)).toContain('foo: 1.2.3');
729
+ });
730
+ });
731
+ });