@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 +5 -17
- package/src/eslint/baseConfig.mjs +2 -21
- package/src/eslint/docsConfig.mjs +0 -1
- package/src/eslint/extensions.mjs +0 -3
- package/src/eslint/material-ui/config.mjs +2 -2
- package/src/eslint/material-ui/rules/straight-quotes.mjs +1 -0
- package/src/eslint/material-ui/rules/straight-quotes.test.mjs +2 -0
- package/src/eslint/testConfig.mjs +0 -1
- package/src/prettier.mjs +26 -38
- package/bin/code-infra.mjs +0 -1
- package/src/cli/cmdArgosPush.mjs +0 -116
- package/src/cli/cmdJsonLint.mjs +0 -80
- package/src/cli/cmdListWorkspaces.mjs +0 -90
- package/src/cli/cmdPublish.mjs +0 -328
- package/src/cli/cmdPublishCanary.mjs +0 -253
- package/src/cli/index.mjs +0 -18
- package/src/cli/pnpm.mjs +0 -178
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mui/internal-code-infra",
|
|
3
|
-
"version": "0.0.2-canary.
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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": "
|
|
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,
|
|
@@ -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': '
|
|
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': '
|
|
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',
|
|
@@ -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
|
-
* @
|
|
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: [
|
|
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
|
}
|
package/bin/code-infra.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import '../src/cli/index.mjs';
|
package/src/cli/cmdArgosPush.mjs
DELETED
|
@@ -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
|
-
});
|
package/src/cli/cmdJsonLint.mjs
DELETED
|
@@ -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
|
-
});
|
package/src/cli/cmdPublish.mjs
DELETED
|
@@ -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
|
-
}
|