@mui/internal-code-infra 0.0.2-canary.13 → 0.0.2-canary.3

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mui/internal-code-infra",
3
- "version": "0.0.2-canary.13",
3
+ "version": "0.0.2-canary.3",
4
4
  "description": "Infra scripts and configs to be used across MUI repos.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -18,15 +18,7 @@
18
18
  "default": "./src/eslint/index.mjs"
19
19
  }
20
20
  },
21
- "bin": {
22
- "code-infra": "./bin/code-infra.mjs"
23
- },
24
21
  "dependencies": {
25
- "@argos-ci/core": "^3.2.0",
26
- "@next/eslint-plugin-next": "^15.3.3",
27
- "@octokit/rest": "^22.0.0",
28
- "@eslint/compat": "^1.3.1",
29
- "chalk": "^5.4.1",
30
22
  "eslint-config-airbnb": "^19.0.4",
31
23
  "eslint-config-airbnb-base": "^15.0.0",
32
24
  "eslint-config-prettier": "^10.1.5",
@@ -39,24 +31,20 @@
39
31
  "eslint-plugin-react-compiler": "^19.1.0-rc.2",
40
32
  "eslint-plugin-react-hooks": "^6.0.0-rc.1",
41
33
  "eslint-plugin-testing-library": "^7.5.3",
42
- "execa": "^7.2.0",
43
- "git-url-parse": "^16.1.0",
34
+ "@next/eslint-plugin-next": "^15.0.0",
44
35
  "globals": "^16.2.0",
45
- "globby": "^14.1.0",
46
36
  "minimatch": "^10.0.3",
47
- "semver": "^7.7.2",
48
- "typescript-eslint": "^8.35.1",
49
- "yargs": "^17.7.2"
37
+ "typescript-eslint": "^8.35.1"
50
38
  },
51
39
  "peerDependencies": {
52
40
  "eslint": "^9.0.0",
53
41
  "prettier": "^3.5.3"
54
42
  },
55
43
  "devDependencies": {
44
+ "@next/eslint-plugin-next": "^15.3.3",
56
45
  "@types/eslint-plugin-jsx-a11y": "^6.10.0",
57
46
  "@types/estree": "^1.0.8",
58
47
  "@types/estree-jsx": "^1.0.5",
59
- "@types/yargs": "^17.0.33",
60
48
  "@typescript-eslint/parser": "^8.35.0",
61
49
  "@typescript-eslint/rule-tester": "^8.35.0",
62
50
  "eslint": "^9.29.0",
@@ -72,7 +60,7 @@
72
60
  "publishConfig": {
73
61
  "access": "public"
74
62
  },
75
- "gitSha": "b09cf2714d16c335022c38eda33f0bce53feda25",
63
+ "gitSha": "1c8c4a4b8bf8b6b3c54d8333c43a098a7c7d84f8",
76
64
  "scripts": {
77
65
  "typescript": "tsc -p tsconfig.json",
78
66
  "test": "pnpm -w test --project @mui/internal-code-infra"
@@ -1,11 +1,8 @@
1
- import { includeIgnoreFile } from '@eslint/compat';
2
1
  import prettier from 'eslint-config-prettier/flat';
3
2
  import reactCompilerPlugin from 'eslint-plugin-react-compiler';
4
3
  import { configs as reactHookConfigs } from 'eslint-plugin-react-hooks';
5
4
  import globals from 'globals';
6
5
  import * as tseslint from 'typescript-eslint';
7
- import * as fs from 'node:fs';
8
- import * as path from 'node:path';
9
6
 
10
7
  import { airbnbBaseConfig, airbnbReactConfig } from './airbnb/base.mjs';
11
8
  import airbnbTypescript from './airbnb/typescript.mjs';
@@ -14,28 +11,12 @@ import muiPlugin from './material-ui/index.mjs';
14
11
  /**
15
12
  * @param {Object} [params]
16
13
  * @param {boolean} [params.enableReactCompiler] - Whether the config is for spec files.
17
- * @param {string} params.baseDirectory - The base directory for the configuration.
14
+ * @param {string} [params.baseDirectory] - The base directory for the configuration.
18
15
  * @returns {import('eslint').Linter.Config[]}
19
16
  */
20
- export function createBaseConfig(
21
- { enableReactCompiler = false, baseDirectory } = { baseDirectory: process.cwd() },
22
- ) {
23
- const ignoreRules = /** @type {import('@eslint/compat').FlatConfig[]} */ (
24
- // All repos should use .lintignore going forward.
25
- // .eslintignore is for backward compatibility. Should be removed in future.
26
- ['.gitignore', '.lintignore', '.eslintignore']
27
- .map((file) => {
28
- if (fs.existsSync(`${baseDirectory}/${file}`)) {
29
- return includeIgnoreFile(path.join(baseDirectory, file), `Ignore rules from ${file}`);
30
- }
31
- return null;
32
- })
33
- .filter(Boolean)
34
- );
35
-
17
+ export function createBaseConfig({ enableReactCompiler = false } = {}) {
36
18
  return /** @type {import('eslint').Linter.Config[]} */ (
37
19
  tseslint.config(
38
- ...ignoreRules,
39
20
  airbnbBaseConfig,
40
21
  airbnbReactConfig,
41
22
  airbnbTypescript,
@@ -13,7 +13,6 @@ export function createDocsConfig() {
13
13
  },
14
14
  },
15
15
  rules: {
16
- 'jsx-a11y/anchor-is-valid': 'off',
17
16
  'no-irregular-whitespace': ['error', { skipJSXText: true, skipStrings: true }],
18
17
  },
19
18
  })
@@ -1,8 +1,5 @@
1
1
  export const EXTENSION_JS = '?(c|m)js?(x)';
2
- export const EXTENSION_JS_NO_MODULE = 'js?(x)';
3
2
  export const EXTENSION_TS = '?(c|m)[jt]s?(x)';
4
- export const EXTENSION_TS_NO_MODULE = '[jt]s?(x)';
5
3
  export const EXTENSION_TS_ONLY = '?(c|m)ts?(x)';
6
- export const EXTENSION_TS_ONLY_NO_MODULE = 'ts?(x)';
7
4
  export const EXTENSION_DTS = `.d.${EXTENSION_TS_ONLY}`;
8
5
  export const EXTENSION_TEST_FILE = `.test.${EXTENSION_TS}`;
@@ -16,7 +16,7 @@ export function createCoreConfig(options = {}) {
16
16
  name: 'material-ui-base',
17
17
  rules: {
18
18
  'no-redeclare': 'off',
19
- '@typescript-eslint/no-redeclare': 'off',
19
+ '@typescript-eslint/no-redeclare': 'error',
20
20
  'consistent-this': ['error', 'self'],
21
21
  curly: ['error', 'all'],
22
22
  'dot-notation': 'error',
@@ -94,7 +94,7 @@ export function createCoreConfig(options = {}) {
94
94
  'material-ui/rules-of-use-theme-variants': 'error',
95
95
  'material-ui/no-empty-box': 'error',
96
96
  'material-ui/no-styled-box': 'error',
97
- 'material-ui/straight-quotes': 'off',
97
+ 'material-ui/straight-quotes': 'error',
98
98
 
99
99
  'react-hooks/exhaustive-deps': ['error', { additionalHooks: 'useEnhancedEffect' }],
100
100
  'react-hooks/rules-of-hooks': 'error',
@@ -1,3 +1,4 @@
1
+ // eslint-disable-next-line material-ui/straight-quotes
1
2
  const nonStraightQuotes = /[‘’“”]/gm;
2
3
 
3
4
  /**
@@ -1,3 +1,5 @@
1
+ /* eslint-disable material-ui/straight-quotes */
2
+
1
3
  import eslint from 'eslint';
2
4
  import parser from '@typescript-eslint/parser';
3
5
  import rule from './straight-quotes.mjs';
@@ -74,7 +74,6 @@ export function createTestConfig(options = {}) {
74
74
  'jsx-a11y/no-noninteractive-tabindex': 'off',
75
75
  'jsx-a11y/no-static-element-interactions': 'off',
76
76
  'jsx-a11y/tabindex-no-positive': 'off',
77
- 'jsx-a11y/anchor-is-valid': 'off',
78
77
 
79
78
  // In tests this is generally intended.
80
79
  'react/button-has-type': 'off',
package/src/prettier.mjs CHANGED
@@ -1,46 +1,34 @@
1
1
  /**
2
- * @typedef {Exclude<import('prettier').Config['overrides'], undefined>[number]} Override
2
+ * @returns {import('prettier').Options}
3
3
  */
4
-
5
- /**
6
- * @type {Override[]}
7
- */
8
- export const docsOverrides = [
9
- {
10
- files: [
11
- 'docs/**/*.md',
12
- 'docs/src/pages/**/*.{js,tsx}',
13
- 'docs/src/app/**/*.{js,tsx}',
14
- 'docs/data/**/*.{js,tsx}',
15
- ],
16
- options: {
17
- // otherwise code blocks overflow on the docs website
18
- // The container is 751px
19
- printWidth: 85,
20
- },
21
- },
22
- ];
23
-
24
- /**
25
- * @type {Override}
26
- */
27
- const jsonOverride = {
28
- files: ['**/*.json'],
29
- options: {
30
- trailingComma: 'none',
31
- },
32
- };
33
-
34
- /**
35
- * @param {Object} [options={}]
36
- * @param {Override[]} [options.overrides]
37
- * @returns {import('prettier').Config}
38
- */
39
- export function createBaseConfig(options = {}) {
4
+ export function createBaseConfig() {
40
5
  return {
41
6
  printWidth: 100,
42
7
  singleQuote: true,
43
8
  trailingComma: 'all',
44
- overrides: [jsonOverride, ...(options.overrides ?? [])],
9
+ overrides: [
10
+ {
11
+ files: ['docs/**/*.md', 'docs/src/pages/**/*.{js,tsx}', 'docs/data/**/*.{js,tsx}'],
12
+ options: {
13
+ // otherwise code blocks overflow on the docs website
14
+ // The container is 751px
15
+ printWidth: 85,
16
+ },
17
+ },
18
+ {
19
+ files: ['docs/pages/blog/**/*.md'],
20
+ options: {
21
+ // otherwise code blocks overflow on the blog website
22
+ // The container is 692px
23
+ printWidth: 82,
24
+ },
25
+ },
26
+ {
27
+ files: ['**/*.json'],
28
+ options: {
29
+ trailingComma: 'none',
30
+ },
31
+ },
32
+ ],
45
33
  };
46
34
  }
@@ -1 +0,0 @@
1
- import '../src/cli/index.mjs';
@@ -1,116 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /* eslint-disable no-console */
4
-
5
- import * as fs from 'fs/promises';
6
- import * as os from 'os';
7
- import * as path from 'path';
8
- import { globby } from 'globby';
9
- import { upload } from '@argos-ci/core';
10
-
11
- /**
12
- * @param {string} name
13
- * @returns {string}
14
- */
15
- function requireEnv(name) {
16
- const val = process.env[name];
17
- if (!val) {
18
- throw new Error(`Missing required env var: ${name}`);
19
- }
20
- return val;
21
- }
22
-
23
- /**
24
- * Alternatve to `@argos-ci/cli` that can upload screenshots in batches.
25
- */
26
-
27
- const BATCH_SIZE = 200;
28
-
29
- /**
30
- * @typedef {Object} Args
31
- * @property {boolean} [verbose] - Run with verbose logging
32
- * @property {string} folder - Screenshots folder path
33
- */
34
-
35
- export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
36
- command: 'argos-push',
37
- describe: 'Upload screenshots to Argos CI in batches',
38
- builder: (yargs) => {
39
- return yargs
40
- .option('folder', {
41
- type: 'string',
42
- demandOption: true,
43
- description: 'Path to the screenshots folder',
44
- })
45
- .option('verbose', {
46
- alias: 'v',
47
- type: 'boolean',
48
- default: false,
49
- description: 'Run with verbose logging',
50
- });
51
- },
52
- handler: async (argv) => {
53
- const { folder, verbose = false } = argv;
54
-
55
- const argosToken = requireEnv('ARGOS_TOKEN');
56
- const circleSha1 = requireEnv('CIRCLE_SHA1');
57
- const circleBranch = requireEnv('CIRCLE_BRANCH');
58
- const circleBuildNum = requireEnv('CIRCLE_BUILD_NUM');
59
-
60
- const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'argos-screenshots-'));
61
-
62
- try {
63
- const screenshots = await globby(`${folder}/**/*`, {
64
- onlyFiles: true,
65
- });
66
-
67
- console.log(`Found ${screenshots.length} screenshots.`);
68
- if (verbose) {
69
- console.log('Screenshots found:');
70
- screenshots.forEach((screenshot) => {
71
- console.log(` - ${screenshot}`);
72
- });
73
- }
74
-
75
- const batches = [];
76
- for (let i = 0; i < screenshots.length; i += BATCH_SIZE) {
77
- batches.push(screenshots.slice(i, i + BATCH_SIZE));
78
- }
79
-
80
- await Promise.all(
81
- batches.map((screenshotBatch, batchIndex) =>
82
- Promise.all(
83
- screenshotBatch.map(async (screenshot) => {
84
- const relativePath = path.relative(folder, screenshot);
85
- const targetPath = path.join(tempDir, `${batchIndex}`, relativePath);
86
- const targetDir = path.dirname(targetPath);
87
-
88
- await fs.mkdir(targetDir, { recursive: true });
89
- await fs.copyFile(screenshot, targetPath);
90
- }),
91
- ),
92
- ),
93
- );
94
-
95
- for (let i = 0; i < batches.length; i += 1) {
96
- // eslint-disable-next-line no-await-in-loop
97
- const result = await upload({
98
- root: `${tempDir}/${i}`,
99
- commit: circleSha1,
100
- branch: circleBranch,
101
- token: argosToken,
102
- parallel: {
103
- total: batches.length,
104
- nonce: circleBuildNum,
105
- },
106
- });
107
-
108
- console.log(
109
- `Batch of ${batches[i].length} screenshots uploaded. Build URL: ${result.build.url}`,
110
- );
111
- }
112
- } finally {
113
- await fs.rm(tempDir, { recursive: true, force: true });
114
- }
115
- },
116
- });
@@ -1,80 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import chalk from 'chalk';
4
- import fs from 'node:fs/promises';
5
- import { globby } from 'globby';
6
- import path from 'path';
7
-
8
- /**
9
- * @typedef {Object} Args
10
- * @property {boolean} [silent] Run in silent mode without logging
11
- */
12
-
13
- /**
14
- * @param {string} message
15
- * @returns {string}
16
- */
17
- const passMessage = (message) => `✓ ${chalk.gray(message)}`;
18
- /**
19
- * @param {string} message
20
- * @returns {string}
21
- */
22
- const failMessage = (message) => `❌ ${chalk.whiteBright(message)}`;
23
-
24
- export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
25
- command: 'jsonlint',
26
- describe: 'Lint JSON files',
27
- builder: (yargs) => {
28
- return yargs.option('silent', {
29
- type: 'boolean',
30
- default: false,
31
- description: "Don't log file names.",
32
- });
33
- },
34
- handler: async (args) => {
35
- const cwd = process.cwd();
36
- const lintIgnoreContent = await (async () => {
37
- // Reads both for backwards compatibility in the desired order
38
- for (const ignoreFile of ['.lintignore', '.eslintignore']) {
39
- const lintIgnorePath = path.join(cwd, ignoreFile);
40
- try {
41
- // eslint-disable-next-line no-await-in-loop
42
- return await fs.readFile(lintIgnorePath, { encoding: 'utf8' });
43
- } catch (ex) {
44
- console.error(ex);
45
- continue;
46
- }
47
- }
48
- return '';
49
- })();
50
-
51
- const lintignore = lintIgnoreContent.split(/\r?\n/).filter(Boolean);
52
-
53
- const filenames = await globby('**/*.json', {
54
- cwd,
55
- gitignore: true,
56
- ignore: [...lintignore, '**/tsconfig*.json'],
57
- followSymbolicLinks: false,
58
- });
59
-
60
- let passed = true;
61
- const checks = filenames.map(async (filename) => {
62
- const content = await fs.readFile(path.join(cwd, filename), { encoding: 'utf8' });
63
- try {
64
- JSON.parse(content);
65
- if (!args.silent) {
66
- // eslint-disable-next-line no-console
67
- console.log(passMessage(filename));
68
- }
69
- } catch (error) {
70
- passed = false;
71
- console.error(failMessage(`Error parsing ${filename}:\n\n${String(error)}`));
72
- }
73
- });
74
-
75
- await Promise.allSettled(checks);
76
- if (!passed) {
77
- throw new Error('❌ At least one file did not pass. Check the console output');
78
- }
79
- },
80
- });
@@ -1,90 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /* eslint-disable no-console */
4
-
5
- /**
6
- * @typedef {import('./pnpm.mjs').Package} Package
7
- */
8
-
9
- import * as fs from 'fs/promises';
10
- import * as path from 'path';
11
- import { getWorkspacePackages } from './pnpm.mjs';
12
-
13
- /**
14
- * @typedef {Object} Args
15
- * @property {boolean} [publicOnly] - Whether to filter to only public packages
16
- * @property {'json'|'path'|'name'|'publish-dir'} [output] - Output format (name, path, or json)
17
- * @property {string} [sinceRef] - Git reference to filter changes since
18
- */
19
-
20
- export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
21
- command: 'list-workspaces',
22
- describe: 'List all pnpm workspaces in the repository',
23
- builder: (yargs) => {
24
- return yargs
25
- .option('public-only', {
26
- type: 'boolean',
27
- default: false,
28
- description: 'Filter to only public packages',
29
- })
30
- .option('output', {
31
- type: 'string',
32
- choices: ['json', 'path', 'name', 'publish-dir'],
33
- default: 'name',
34
- description:
35
- 'Output format: name (package names), path (package paths), publish-dir (publish directories), or json (full JSON)',
36
- })
37
- .option('since-ref', {
38
- type: 'string',
39
- description: 'Filter packages changed since git reference',
40
- });
41
- },
42
- handler: async (argv) => {
43
- const { publicOnly = false, output = 'name', sinceRef } = argv;
44
-
45
- try {
46
- // Get packages using our helper function
47
- const packages = await getWorkspacePackages({ sinceRef, publicOnly });
48
-
49
- if (output === 'json') {
50
- // Serialize packages to JSON
51
- console.log(JSON.stringify(packages, null, 2));
52
- } else if (output === 'path') {
53
- // Print package paths
54
- packages.forEach((pkg) => {
55
- console.log(pkg.path);
56
- });
57
- } else if (output === 'publish-dir') {
58
- // TODO: Remove this option once https://github.com/stackblitz-labs/pkg.pr.new/issues/389 is resolved
59
- // Print publish directories (package.json publishConfig.directory or package path)
60
- const publishDirs = await Promise.all(
61
- packages.map(async (pkg) => {
62
- const packageJsonPath = path.join(pkg.path, 'package.json');
63
- const packageJsonContent = await fs.readFile(packageJsonPath, 'utf8');
64
- const packageJson = JSON.parse(packageJsonContent);
65
-
66
- if (packageJson.publishConfig?.directory) {
67
- return path.join(pkg.path, packageJson.publishConfig.directory);
68
- }
69
-
70
- return pkg.path;
71
- }),
72
- );
73
-
74
- publishDirs.forEach((dir) => {
75
- console.log(dir);
76
- });
77
- } else if (output === 'name') {
78
- // Print package names (default)
79
- packages.forEach((pkg) => {
80
- console.log(pkg.name);
81
- });
82
- } else {
83
- throw new Error(`Unsupported output format: ${output}`);
84
- }
85
- } catch (/** @type {any} */ error) {
86
- console.error('Error listing workspaces:', error.message);
87
- process.exit(1);
88
- }
89
- },
90
- });
@@ -1,328 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /* eslint-disable no-console */
4
-
5
- /**
6
- * @typedef {import('./pnpm.mjs').Package} Package
7
- * @typedef {import('./pnpm.mjs').PublishOptions} PublishOptions
8
- */
9
-
10
- import { Octokit } from '@octokit/rest';
11
- import * as fs from 'fs/promises';
12
- import * as semver from 'semver';
13
- import gitUrlParse from 'git-url-parse';
14
- import { $ } from 'execa';
15
- import { getWorkspacePackages, publishPackages } from './pnpm.mjs';
16
-
17
- /**
18
- * @typedef {Object} Args
19
- * @property {boolean} dry-run Run in dry-run mode without publishing
20
- * @property {boolean} no-git-checks - Skip git checks before publishing
21
- * @property {boolean} provenance - Enable provenance tracking for the publish
22
- */
23
-
24
- /**
25
- * Get the version to release from the root package.json
26
- * @returns {Promise<string>} Version string
27
- */
28
- async function getReleaseVersion() {
29
- const result = await $`pnpm pkg get version`;
30
- const versionData = JSON.parse(result.stdout.trim());
31
- const version = versionData.version;
32
-
33
- const validVersion = semver.valid(version);
34
- if (!validVersion) {
35
- throw new Error(`Invalid version in root package.json: ${version}`);
36
- }
37
-
38
- return validVersion;
39
- }
40
-
41
- /**
42
- * Parse changelog to extract content for a specific version
43
- * @param {string} changelogPath - Path to CHANGELOG.md
44
- * @param {string} version - Version to extract
45
- * @returns {Promise<string>} Changelog content for the version
46
- */
47
- async function parseChangelog(changelogPath, version) {
48
- try {
49
- const content = await fs.readFile(changelogPath, 'utf8');
50
- const lines = content.split('\n');
51
-
52
- const versionHeader = `## ${version}`;
53
- const startIndex = lines.findIndex((line) => line.startsWith(versionHeader));
54
-
55
- if (startIndex === -1) {
56
- throw new Error(`Version ${version} not found in changelog`);
57
- }
58
-
59
- // Skip the version header and find content start
60
- let contentStartIndex = startIndex + 1;
61
-
62
- // Skip whitespace and comment lines
63
- while (contentStartIndex < lines.length) {
64
- const line = lines[contentStartIndex].trim();
65
- if (line === '' || line.startsWith('<!--')) {
66
- contentStartIndex += 1;
67
- } else {
68
- break;
69
- }
70
- }
71
-
72
- // Check if first content line is a date line
73
- if (contentStartIndex < lines.length) {
74
- const line = lines[contentStartIndex].trim();
75
- // Remove leading/trailing underscores if present
76
- const cleanLine = line.replace(/^_+|_+$/g, '');
77
- // Try to parse as date
78
- if (cleanLine && !Number.isNaN(Date.parse(cleanLine))) {
79
- contentStartIndex += 1; // Skip date line
80
- }
81
- }
82
-
83
- // Find the end of this version's content (next ## header)
84
- let endIndex = lines.length;
85
- for (let i = contentStartIndex; i < lines.length; i += 1) {
86
- if (lines[i].startsWith('## ')) {
87
- endIndex = i;
88
- break;
89
- }
90
- }
91
-
92
- return lines.slice(contentStartIndex, endIndex).join('\n').trim();
93
- } catch (/** @type {any} */ error) {
94
- if (error.code === 'ENOENT') {
95
- throw new Error('CHANGELOG.md not found');
96
- }
97
- throw error;
98
- }
99
- }
100
-
101
- /**
102
- * Check if GitHub release already exists
103
- * @param {Octokit} octokit - GitHub API client
104
- * @param {string} owner - Repository owner
105
- * @param {string} repo - Repository name
106
- * @param {string} version - Version to check
107
- * @returns {Promise<boolean>} True if release exists
108
- */
109
- async function checkGitHubReleaseExists(octokit, owner, repo, version) {
110
- try {
111
- await octokit.repos.getReleaseByTag({ owner, repo, tag: `v${version}` });
112
- return true;
113
- } catch (/** @type {any} */ error) {
114
- if (error.status === 404) {
115
- return false;
116
- }
117
- throw error;
118
- }
119
- }
120
-
121
- /**
122
- * Get current repository info from git remote
123
- * @returns {Promise<{owner: string, repo: string}>} Repository owner and name
124
- */
125
- async function getRepositoryInfo() {
126
- try {
127
- const result = await $`git remote get-url origin`;
128
- const url = result.stdout.trim();
129
-
130
- const parsed = gitUrlParse(url);
131
- if (parsed.source !== 'github.com') {
132
- throw new Error('Repository is not hosted on GitHub');
133
- }
134
-
135
- return {
136
- owner: parsed.owner,
137
- repo: parsed.name,
138
- };
139
- } catch (/** @type {any} */ error) {
140
- throw new Error(`Failed to get repository info: ${error.message}`);
141
- }
142
- }
143
-
144
- /**
145
- * Get current git SHA
146
- * @returns {Promise<string>} Current git commit SHA
147
- */
148
- async function getCurrentGitSha() {
149
- const result = await $`git rev-parse HEAD`;
150
- return result.stdout.trim();
151
- }
152
-
153
- /**
154
- * Create and push a git tag
155
- * @param {string} version - Version to tag
156
- * @param {boolean} [dryRun=false] - Whether to run in dry-run mode
157
- * @returns {Promise<void>}
158
- */
159
- async function createGitTag(version, dryRun = false) {
160
- const tagName = `v${version}`;
161
-
162
- try {
163
- await $`git tag ${tagName}`;
164
- const pushArgs = dryRun ? ['--dry-run'] : [];
165
- await $({ stdio: 'inherit' })`git push origin ${tagName} ${pushArgs}`;
166
-
167
- console.log(`🏷️ Created and pushed git tag ${tagName}${dryRun ? ' (dry-run)' : ''}`);
168
- } catch (/** @type {any} */ error) {
169
- throw new Error(`Failed to create git tag: ${error.message}`);
170
- }
171
- }
172
-
173
- /**
174
- * Validate GitHub release requirements
175
- * @param {string} version - Version to validate
176
- * @returns {Promise<{changelogContent: string, repoInfo: {owner: string, repo: string}}>}
177
- */
178
- async function validateGitHubRelease(version) {
179
- console.log('🔍 Validating GitHub release requirements...');
180
-
181
- // Check if CHANGELOG.md exists and parse it
182
- console.log(`📄 Parsing CHANGELOG.md for version ${version}...`);
183
- const changelogContent = await parseChangelog('CHANGELOG.md', version);
184
- console.log('✅ Found changelog content for version');
185
-
186
- // Get repository info
187
- const repoInfo = await getRepositoryInfo();
188
- console.log(`📂 Repository: ${repoInfo.owner}/${repoInfo.repo}`);
189
-
190
- // Check if release already exists on GitHub
191
- const octokit = new Octokit({
192
- auth: process.env.GITHUB_TOKEN,
193
- });
194
-
195
- console.log(`🔍 Checking if GitHub release v${version} already exists...`);
196
- const releaseExists = await checkGitHubReleaseExists(
197
- octokit,
198
- repoInfo.owner,
199
- repoInfo.repo,
200
- version,
201
- );
202
-
203
- if (releaseExists) {
204
- throw new Error(`GitHub release v${version} already exists`);
205
- }
206
- console.log('✅ GitHub release does not exist yet');
207
-
208
- return { changelogContent, repoInfo };
209
- }
210
-
211
- /**
212
- * Publish packages to npm
213
- * @param {Package[]} packages - Packages to publish
214
- * @param {PublishOptions} options - Publishing options
215
- * @returns {Promise<void>}
216
- */
217
- async function publishToNpm(packages, options) {
218
- console.log('\n📦 Publishing packages to npm...');
219
- console.log(`📋 Found ${packages.length} packages:`);
220
- packages.forEach((pkg) => {
221
- console.log(` • ${pkg.name}@${pkg.version}`);
222
- });
223
-
224
- // Use pnpm's built-in duplicate checking - no need to check versions ourselves
225
- await publishPackages(packages, 'latest', options);
226
- console.log('✅ Successfully published to npm');
227
- }
228
-
229
- /**
230
- * Create GitHub release after npm publishing
231
- * @param {string} version - Version to release
232
- * @param {string} changelogContent - Changelog content
233
- * @param {{owner: string, repo: string}} repoInfo - Repository info
234
- * @returns {Promise<void>}
235
- */
236
- async function createRelease(version, changelogContent, repoInfo) {
237
- console.log('\n🚀 Creating GitHub draft release...');
238
-
239
- const octokit = new Octokit({
240
- auth: process.env.GITHUB_TOKEN,
241
- });
242
-
243
- const sha = await getCurrentGitSha();
244
-
245
- await octokit.repos.createRelease({
246
- owner: repoInfo.owner,
247
- repo: repoInfo.repo,
248
- tag_name: `v${version}`,
249
- target_commitish: sha,
250
- name: `v${version}`,
251
- body: changelogContent,
252
- draft: true,
253
- });
254
-
255
- console.log(
256
- `✅ Created draft release v${version} at https://github.com/${repoInfo.owner}/${repoInfo.repo}/releases`,
257
- );
258
- }
259
-
260
- export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
261
- command: 'publish',
262
- describe: 'Publish packages to npm',
263
- builder: (yargs) => {
264
- return yargs
265
- .option('dry-run', {
266
- type: 'boolean',
267
- default: false,
268
- description: 'Run in dry-run mode without publishing',
269
- })
270
- .option('no-git-checks', {
271
- type: 'boolean',
272
- default: false,
273
- description: 'Skip git checks before publishing',
274
- })
275
- .option('provenance', {
276
- type: 'boolean',
277
- default: false,
278
- description: 'Enable provenance tracking for the publish',
279
- });
280
- },
281
- handler: async (argv) => {
282
- const { dryRun = false, provenance = false, githubRelease = false } = argv;
283
-
284
- const options = { dryRun, provenance };
285
-
286
- if (dryRun) {
287
- console.log('🧪 Running in DRY RUN mode - no actual publishing will occur\n');
288
- }
289
-
290
- if (provenance) {
291
- console.log('🔐 Provenance enabled - packages will include provenance information\n');
292
- }
293
-
294
- // Get all packages
295
- console.log('🔍 Discovering all workspace packages...');
296
- const allPackages = await getWorkspacePackages({ publicOnly: true });
297
-
298
- if (allPackages.length === 0) {
299
- console.log('⚠️ No public packages found in workspace');
300
- return;
301
- }
302
-
303
- // Get version from root package.json
304
- const version = await getReleaseVersion();
305
- console.log(`📋 Release version: ${version}`);
306
-
307
- // Early validation for GitHub release (before any publishing)
308
- let githubReleaseData = null;
309
- if (githubRelease) {
310
- githubReleaseData = await validateGitHubRelease(version);
311
- }
312
-
313
- // Publish to npm (pnpm handles duplicate checking automatically)
314
- await publishToNpm(allPackages, options);
315
-
316
- // Create GitHub release or git tag after successful npm publishing
317
- if (githubRelease && githubReleaseData && !dryRun) {
318
- await createRelease(version, githubReleaseData.changelogContent, githubReleaseData.repoInfo);
319
- } else if (githubRelease && dryRun) {
320
- console.log('\n🚀 Would create GitHub draft release (dry-run)');
321
- } else {
322
- // Create git tag when not doing GitHub release
323
- await createGitTag(version, dryRun);
324
- }
325
-
326
- console.log('\n🏁 Publishing complete!');
327
- },
328
- });
@@ -1,253 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /* eslint-disable no-console */
4
-
5
- /**
6
- * @typedef {import('./pnpm.mjs').Package} Package
7
- * @typedef {import('./pnpm.mjs').VersionInfo} VersionInfo
8
- * @typedef {import('./pnpm.mjs').PublishOptions} PublishOptions
9
- */
10
-
11
- import { $ } from 'execa';
12
- import * as semver from 'semver';
13
-
14
- /**
15
- * @typedef {Object} Args
16
- * @property {boolean} [dryRun] - Whether to run in dry-run mode
17
- * @property {boolean} [provenance] - Whether to include provenance information
18
- */
19
-
20
- import {
21
- getWorkspacePackages,
22
- getPackageVersionInfo,
23
- publishPackages,
24
- readPackageJson,
25
- writePackageJson,
26
- getCurrentGitSha,
27
- semverMax,
28
- } from './pnpm.mjs';
29
-
30
- const CANARY_TAG = 'canary';
31
-
32
- /**
33
- * Check if the canary git tag exists
34
- * @returns {Promise<string|null>} Canary tag name if exists, null otherwise
35
- */
36
- async function getLastCanaryTag() {
37
- // Remove local canary tag first to avoid conflicts during fetch
38
- try {
39
- await $`git tag -d ${CANARY_TAG}`;
40
- } catch {
41
- // Tag might not exist locally, which is fine
42
- }
43
-
44
- await $`git fetch origin tag ${CANARY_TAG}`;
45
- const { stdout: remoteCanaryTag } = await $`git ls-remote --tags origin ${CANARY_TAG}`;
46
- return remoteCanaryTag.trim() ? CANARY_TAG : null;
47
- }
48
-
49
- /**
50
- * Create or update the canary git tag
51
- * @param {boolean} [dryRun=false] - Whether to run in dry-run mode
52
- * @returns {Promise<void>}
53
- */
54
- async function createCanaryTag(dryRun = false) {
55
- try {
56
- if (dryRun) {
57
- console.log('🏷️ Would update and push canary tag (dry-run)');
58
- } else {
59
- await $`git tag -f ${CANARY_TAG}`;
60
- await $`git push origin ${CANARY_TAG} --force`;
61
- console.log('🏷️ Updated and pushed canary tag');
62
- }
63
- } catch (/** @type {any} */ error) {
64
- console.error('Failed to create/push canary tag:', error.message);
65
- throw error;
66
- }
67
- }
68
-
69
- /**
70
- * Publish canary versions with updated dependencies
71
- * @param {Package[]} packagesToPublish - Packages that need canary publishing
72
- * @param {Package[]} allPackages - All workspace packages
73
- * @param {Map<string, VersionInfo>} packageVersionInfo - Version info map
74
- * @param {PublishOptions} [options={}] - Publishing options
75
- * @returns {Promise<void>}
76
- */
77
- async function publishCanaryVersions(
78
- packagesToPublish,
79
- allPackages,
80
- packageVersionInfo,
81
- options = {},
82
- ) {
83
- console.log('\n🔥 Publishing canary versions...');
84
-
85
- // Early return if no packages need canary publishing
86
- if (packagesToPublish.length === 0) {
87
- console.log('✅ No packages have changed since last canary publish');
88
- await createCanaryTag(options.dryRun);
89
- return;
90
- }
91
-
92
- const gitSha = await getCurrentGitSha();
93
- const canaryVersions = new Map();
94
- const originalPackageJsons = new Map();
95
-
96
- // First pass: determine canary version numbers for all packages
97
- const changedPackageNames = new Set(packagesToPublish.map((pkg) => pkg.name));
98
-
99
- for (const pkg of allPackages) {
100
- const versionInfo = packageVersionInfo.get(pkg.name);
101
- if (!versionInfo) {
102
- throw new Error(`No version info found for package ${pkg.name}`);
103
- }
104
-
105
- if (changedPackageNames.has(pkg.name)) {
106
- // Generate new canary version for changed packages
107
- const baseVersion = versionInfo.latestCanaryVersion
108
- ? semverMax(versionInfo.latestCanaryVersion, pkg.version)
109
- : pkg.version;
110
- const canaryVersion = semver.inc(baseVersion, 'prerelease', 'canary');
111
- canaryVersions.set(pkg.name, canaryVersion);
112
- console.log(`🏷️ ${pkg.name}: ${canaryVersion} (new)`);
113
- } else if (versionInfo.latestCanaryVersion) {
114
- // Reuse existing canary version for unchanged packages
115
- canaryVersions.set(pkg.name, versionInfo.latestCanaryVersion);
116
- console.log(`🏷️ ${pkg.name}: ${versionInfo.latestCanaryVersion} (reused)`);
117
- }
118
- }
119
-
120
- // Second pass: read and update ALL package.json files in parallel
121
- const packageUpdatePromises = allPackages.map(async (pkg) => {
122
- const originalPackageJson = await readPackageJson(pkg.path);
123
-
124
- const canaryVersion = canaryVersions.get(pkg.name);
125
- if (canaryVersion) {
126
- const updatedPackageJson = {
127
- ...originalPackageJson,
128
- version: canaryVersion,
129
- gitSha,
130
- };
131
-
132
- await writePackageJson(pkg.path, updatedPackageJson);
133
- console.log(`📝 Updated ${pkg.name} package.json to ${canaryVersion}`);
134
- }
135
-
136
- return { pkg, originalPackageJson };
137
- });
138
-
139
- const updateResults = await Promise.all(packageUpdatePromises);
140
-
141
- // Build the original package.json map
142
- for (const { pkg, originalPackageJson } of updateResults) {
143
- originalPackageJsons.set(pkg.name, originalPackageJson);
144
- }
145
-
146
- // Run release build after updating package.json files
147
- console.log('\n🔨 Running release build...');
148
- await $({ stdio: 'inherit' })`pnpm release:build`;
149
- console.log('✅ Release build completed successfully');
150
-
151
- // Third pass: publish only the changed packages using recursive publish
152
- let publishSuccess = false;
153
- try {
154
- console.log(`📤 Publishing ${packagesToPublish.length} canary versions...`);
155
- await publishPackages(packagesToPublish, 'canary', { ...options, noGitChecks: true });
156
-
157
- packagesToPublish.forEach((pkg) => {
158
- const canaryVersion = canaryVersions.get(pkg.name);
159
- console.log(`✅ Published ${pkg.name}@${canaryVersion}`);
160
- });
161
- publishSuccess = true;
162
- } finally {
163
- // Always restore original package.json files in parallel
164
- console.log('\n🔄 Restoring original package.json files...');
165
- const restorePromises = allPackages.map(async (pkg) => {
166
- const originalPackageJson = originalPackageJsons.get(pkg.name);
167
- await writePackageJson(pkg.path, originalPackageJson);
168
- });
169
-
170
- await Promise.all(restorePromises);
171
- }
172
-
173
- if (publishSuccess) {
174
- // Create/update the canary tag after successful publish
175
- await createCanaryTag(options.dryRun);
176
- console.log('\n🎉 All canary versions published successfully!');
177
- }
178
- }
179
-
180
- export default /** @type {import('yargs').CommandModule<{}, Args>} */ ({
181
- command: 'publish-canary',
182
- describe: 'Publish canary packages to npm',
183
- builder: (yargs) => {
184
- return yargs
185
- .option('dry-run', {
186
- type: 'boolean',
187
- default: false,
188
- description: 'Run in dry-run mode without publishing',
189
- })
190
- .option('provenance', {
191
- type: 'boolean',
192
- default: false,
193
- description: 'Include provenance information in published packages',
194
- });
195
- },
196
- handler: async (argv) => {
197
- const { dryRun = false, provenance = false } = argv;
198
-
199
- const options = { dryRun, provenance };
200
-
201
- if (dryRun) {
202
- console.log('🧪 Running in DRY RUN mode - no actual publishing will occur\n');
203
- }
204
-
205
- if (provenance) {
206
- console.log('🔐 Provenance enabled - packages will include provenance information\n');
207
- }
208
-
209
- // Always get all packages first
210
- console.log('🔍 Discovering all workspace packages...');
211
- const allPackages = await getWorkspacePackages({ publicOnly: true });
212
-
213
- if (allPackages.length === 0) {
214
- console.log('⚠️ No public packages found in workspace');
215
- return;
216
- }
217
-
218
- // Check for canary tag to determine selective publishing
219
- const canaryTag = await getLastCanaryTag();
220
-
221
- console.log(
222
- canaryTag
223
- ? '🔍 Checking for packages changed since canary tag...'
224
- : '🔍 No canary tag found, will publish all packages',
225
- );
226
- const packages = canaryTag
227
- ? await getWorkspacePackages({ sinceRef: canaryTag, publicOnly: true })
228
- : allPackages;
229
-
230
- console.log(`📋 Found ${packages.length} packages that need canary publishing:`);
231
- packages.forEach((pkg) => {
232
- console.log(` • ${pkg.name}@${pkg.version}`);
233
- });
234
-
235
- // Fetch version info for all packages in parallel
236
- console.log('\n🔍 Fetching package version information...');
237
- const versionInfoPromises = allPackages.map(async (pkg) => {
238
- const versionInfo = await getPackageVersionInfo(pkg.name, pkg.version);
239
- return { packageName: pkg.name, versionInfo };
240
- });
241
-
242
- const versionInfoResults = await Promise.all(versionInfoPromises);
243
- const packageVersionInfo = new Map();
244
-
245
- for (const { packageName, versionInfo } of versionInfoResults) {
246
- packageVersionInfo.set(packageName, versionInfo);
247
- }
248
-
249
- await publishCanaryVersions(packages, allPackages, packageVersionInfo, options);
250
-
251
- console.log('\n🏁 Publishing complete!');
252
- },
253
- });
package/src/cli/index.mjs DELETED
@@ -1,18 +0,0 @@
1
- import yargs from 'yargs';
2
- import { hideBin } from 'yargs/helpers';
3
-
4
- import cmdPublish from './cmdPublish.mjs';
5
- import cmdPublishCanary from './cmdPublishCanary.mjs';
6
- import cmdListWorkspaces from './cmdListWorkspaces.mjs';
7
- import cmdJsonLint from './cmdJsonLint.mjs';
8
- import cmdArgosPush from './cmdArgosPush.mjs';
9
-
10
- yargs()
11
- .command(cmdPublish)
12
- .command(cmdPublishCanary)
13
- .command(cmdListWorkspaces)
14
- .command(cmdJsonLint)
15
- .command(cmdArgosPush)
16
- .demandCommand(1, 'You need at least one command before moving on')
17
- .help()
18
- .parse(hideBin(process.argv));
package/src/cli/pnpm.mjs DELETED
@@ -1,178 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { $ } from 'execa';
4
- import * as fs from 'fs/promises';
5
- import * as path from 'path';
6
- import * as semver from 'semver';
7
-
8
- /**
9
- * @typedef {Object} Package
10
- * @property {string} name - Package name
11
- * @property {string} version - Package version
12
- * @property {string} path - Package directory path
13
- */
14
-
15
- /**
16
- * @typedef {Object} VersionInfo
17
- * @property {boolean} currentVersionExists - Whether current version exists on npm
18
- * @property {string|null} latestCanaryVersion - Latest canary version if available
19
- */
20
-
21
- /**
22
- * @typedef {Object} PublishOptions
23
- * @property {boolean} [dryRun] - Whether to run in dry-run mode
24
- * @property {boolean} [provenance] - Whether to include provenance information
25
- * @property {boolean} [noGitChecks] - Whether to skip git checks
26
- */
27
-
28
- /**
29
- * @typedef {Object} PnpmListResultItem
30
- * @property {string} [name] - Package name
31
- * @property {string} [version] - Package version
32
- * @property {string} path - Package directory path
33
- * @property {boolean} private - Whether the package is private
34
- */
35
-
36
- /**
37
- * @typedef {Object} GetWorkspacePackagesOptions
38
- * @property {string|null} [sinceRef] - Git reference to filter changes since
39
- * @property {boolean} [publicOnly=false] - Whether to filter to only public packages
40
- */
41
-
42
- /**
43
- * Get workspace packages with optional filtering
44
- * @param {GetWorkspacePackagesOptions} [options={}] - Options for filtering packages
45
- * @returns {Promise<Package[]>} Array of packages
46
- */
47
- export async function getWorkspacePackages(options = {}) {
48
- const { sinceRef = null, publicOnly = false } = options;
49
-
50
- // Build command with conditional filter
51
- const filterArg = sinceRef ? ['--filter', `...[${sinceRef}]`] : [];
52
- const result = await $`pnpm ls -r --json --depth -1 ${filterArg}`;
53
- /** @type {PnpmListResultItem[]} */
54
- const packageData = JSON.parse(result.stdout);
55
-
56
- // Filter packages based on options
57
- const filteredPackages = packageData
58
- .filter((pkg) => !publicOnly || !pkg.private)
59
- .map((pkg) => {
60
- if (!pkg.name || !pkg.version) {
61
- throw new Error(`Invalid package data: ${JSON.stringify(pkg)}`);
62
- }
63
- return {
64
- name: pkg.name,
65
- version: pkg.version,
66
- path: pkg.path,
67
- };
68
- });
69
-
70
- return filteredPackages;
71
- }
72
-
73
- /**
74
- * Get package version info from registry
75
- * @param {string} packageName - Name of the package
76
- * @param {string} baseVersion - Base version to check
77
- * @returns {Promise<VersionInfo>} Version information
78
- */
79
- export async function getPackageVersionInfo(packageName, baseVersion) {
80
- try {
81
- // Check if current stable version exists
82
- let currentVersionExists = false;
83
- try {
84
- await $`pnpm view ${packageName}@${baseVersion} version`;
85
- currentVersionExists = true;
86
- } catch {
87
- currentVersionExists = false;
88
- }
89
-
90
- // Get canary dist-tag to find latest canary version
91
- const canaryResult = await $`pnpm view ${packageName} dist-tags.canary`;
92
- const latestCanaryVersion = semver.valid(canaryResult.stdout.trim());
93
-
94
- return {
95
- currentVersionExists,
96
- latestCanaryVersion,
97
- };
98
- } catch (error) {
99
- return {
100
- currentVersionExists: false,
101
- latestCanaryVersion: null,
102
- };
103
- }
104
- }
105
-
106
- /**
107
- * Publish packages with the given options
108
- * @param {Package[]} packages - Packages to publish
109
- * @param {string} tag - npm tag to publish with
110
- * @param {PublishOptions} [options={}] - Publishing options
111
- * @returns {Promise<void>}
112
- */
113
- export async function publishPackages(packages, tag, options = {}) {
114
- const args = [];
115
-
116
- // Add package filters
117
- packages.forEach((pkg) => {
118
- args.push('--filter', pkg.name);
119
- });
120
-
121
- // Add conditional flags
122
- if (options.dryRun) {
123
- args.push('--dry-run');
124
- }
125
-
126
- if (options.noGitChecks) {
127
- args.push('--no-git-checks');
128
- }
129
-
130
- // Set up environment variables
131
- /** @type {Record<string, string>} */
132
- const env = {};
133
- if (options.provenance) {
134
- env.NPM_CONFIG_PROVENANCE = 'true';
135
- }
136
-
137
- await $({ stdio: 'inherit', env })`pnpm -r publish --access public --tag=${tag} ${args}`;
138
- }
139
-
140
- /**
141
- * Read package.json from a directory
142
- * @param {string} packagePath - Path to package directory
143
- * @returns {Promise<Object>} Parsed package.json content
144
- */
145
- export async function readPackageJson(packagePath) {
146
- const content = await fs.readFile(path.join(packagePath, 'package.json'), 'utf8');
147
- return JSON.parse(content);
148
- }
149
-
150
- /**
151
- * Write package.json to a directory
152
- * @param {string} packagePath - Path to package directory
153
- * @param {Object} packageJson - Package.json object to write
154
- * @returns {Promise<void>}
155
- */
156
- export async function writePackageJson(packagePath, packageJson) {
157
- const content = `${JSON.stringify(packageJson, null, 2)}\n`;
158
- await fs.writeFile(path.join(packagePath, 'package.json'), content);
159
- }
160
-
161
- /**
162
- * Get current git SHA
163
- * @returns {Promise<string>} Current git commit SHA
164
- */
165
- export async function getCurrentGitSha() {
166
- const result = await $`git rev-parse HEAD`;
167
- return result.stdout.trim();
168
- }
169
-
170
- /**
171
- * Get the maximum semver version between two versions
172
- * @param {string} a
173
- * @param {string} b
174
- * @returns {string} The maximum semver version
175
- */
176
- export function semverMax(a, b) {
177
- return semver.gt(a, b) ? a : b;
178
- }