auklet 0.0.22 → 0.0.24
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/README.md +6 -0
- package/dist/build/runTsdown.js +1 -2
- package/dist/css/core/styleImports/sourceImportExportAnalyzer.js +2 -2
- package/dist/css/vite/moduleGraph/packageSource/monorepo.js +1 -1
- package/dist/css/vite/moduleGraph/packageSource/singlePackage.js +2 -2
- package/dist/publish/api/gitApi.js +6 -6
- package/dist/publish/api/packageJsonApi.js +5 -3
- package/dist/publish/api/pnpmApi.d.ts +11 -1
- package/dist/publish/api/pnpmApi.js +56 -16
- package/dist/publish/api/publishHookApi.js +1 -1
- package/dist/publish/cli.js +5 -5
- package/dist/publish/ownerRunner.js +1 -1
- package/dist/publish/publishRunner.js +2 -2
- package/dist/publish/runner/packageBuilder.js +1 -1
- package/dist/publish/runner/packagePublisher.d.ts +4 -1
- package/dist/publish/runner/packagePublisher.js +12 -1
- package/dist/publish/runner/publishPreflight.d.ts +3 -1
- package/dist/publish/runner/publishPreflight.js +24 -2
- package/dist/publish/runner/publishTargetError.js +1 -1
- package/dist/publish/runner/releaseGitController.js +1 -1
- package/dist/publish/targetResolver.js +30 -14
- package/dist/publish/version.js +6 -6
- package/dist/workspace/packages.js +5 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -125,10 +125,16 @@ Publish controls stay on CLI flags:
|
|
|
125
125
|
auk publish --filter @scope/ui
|
|
126
126
|
auk publish --version patch --dry-run
|
|
127
127
|
auk publish --no-format
|
|
128
|
+
auk publish --otp 123456
|
|
128
129
|
```
|
|
129
130
|
|
|
130
131
|
`--no-format` disables auklet's publish output formatter for that run. It is not
|
|
131
132
|
configured in `package.json`.
|
|
133
|
+
Before writing versions, auklet checks npm authentication from each target
|
|
134
|
+
package directory. Package-local `.npmrc` files and
|
|
135
|
+
`package.json#publishConfig.registry` are respected.
|
|
136
|
+
`--otp` is forwarded to `pnpm publish` for npm accounts or organizations that
|
|
137
|
+
require publish 2FA. In CI, prefer an npm automation token.
|
|
132
138
|
|
|
133
139
|
## Configuration
|
|
134
140
|
|
package/dist/build/runTsdown.js
CHANGED
|
@@ -3,8 +3,7 @@ import { createRequire } from 'node:module';
|
|
|
3
3
|
import { execa } from 'execa';
|
|
4
4
|
const require = createRequire(import.meta.url);
|
|
5
5
|
const tsdownRunFile = require.resolve('tsdown/run');
|
|
6
|
-
const
|
|
7
|
-
const defaultConfigFile = fileURLToPath(new URL(`./tsdownConfig.${currentExtension}`, import.meta.url));
|
|
6
|
+
const defaultConfigFile = fileURLToPath(new URL('./tsdownConfig.js', import.meta.url));
|
|
8
7
|
export function hasTsdownConfigArg(args) {
|
|
9
8
|
return args.some((arg, index) => arg === '--no-config' ||
|
|
10
9
|
arg === '-c' ||
|
|
@@ -121,7 +121,7 @@ const collectExportDeclaration = (file, node, imports, importBindings, localDecl
|
|
|
121
121
|
if (!ts.isStringLiteral(node.moduleSpecifier))
|
|
122
122
|
return;
|
|
123
123
|
if (!node.exportClause || !ts.isNamedExports(node.exportClause)) {
|
|
124
|
-
throw new Error(`[
|
|
124
|
+
throw new Error(`[css] Export-all declarations are not supported for CSS auto import: ${node.moduleSpecifier.text}\n` +
|
|
125
125
|
`Use named exports instead, for example: export { Component } from '${node.moduleSpecifier.text}'.\n` +
|
|
126
126
|
`File: ${file}`);
|
|
127
127
|
}
|
|
@@ -150,7 +150,7 @@ const collectExportDeclaration = (file, node, imports, importBindings, localDecl
|
|
|
150
150
|
continue;
|
|
151
151
|
}
|
|
152
152
|
if (!localDeclarations.has(localName)) {
|
|
153
|
-
throw new Error(`[
|
|
153
|
+
throw new Error(`[css] Unable to resolve exported symbol "${localName}" for CSS auto import.\n` +
|
|
154
154
|
`File: ${file}`);
|
|
155
155
|
}
|
|
156
156
|
}
|
|
@@ -62,7 +62,7 @@ export class MonorepoPackageSource {
|
|
|
62
62
|
}));
|
|
63
63
|
}
|
|
64
64
|
catch (error) {
|
|
65
|
-
throw new Error('[
|
|
65
|
+
throw new Error('[css] failed to read pnpm workspace packages for Vite monorepo mode.', { cause: error });
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
isWorkspaceRootPackage(item) {
|
|
@@ -45,11 +45,11 @@ export class SinglePackageSource {
|
|
|
45
45
|
return this.packageInfo;
|
|
46
46
|
const packageJsonPath = path.join(this.root, 'package.json');
|
|
47
47
|
if (!fs.existsSync(packageJsonPath)) {
|
|
48
|
-
throw new Error(`[
|
|
48
|
+
throw new Error(`[css] package mode requires a package.json at ${this.root}.`);
|
|
49
49
|
}
|
|
50
50
|
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
51
51
|
if (!pkg.name) {
|
|
52
|
-
throw new Error(`[
|
|
52
|
+
throw new Error(`[css] package mode requires package.json#name at ${this.root}.`);
|
|
53
53
|
}
|
|
54
54
|
this.packageInfo = {
|
|
55
55
|
packageName: pkg.name,
|
|
@@ -18,27 +18,27 @@ export async function getGitShortHash(cwd) {
|
|
|
18
18
|
export async function ensureGitClean(cwd) {
|
|
19
19
|
const result = await runGit(['status', '--porcelain'], cwd);
|
|
20
20
|
if (result.exitCode) {
|
|
21
|
-
throw new Error('[
|
|
21
|
+
throw new Error('[publish] failed to check git status.');
|
|
22
22
|
}
|
|
23
23
|
if (result.stdout.trim()) {
|
|
24
|
-
throw new Error('[
|
|
24
|
+
throw new Error('[publish] git working tree must be clean before publishing.');
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
export async function hasGitChanges(cwd) {
|
|
28
28
|
const result = await runGit(['status', '--porcelain'], cwd);
|
|
29
29
|
if (result.exitCode) {
|
|
30
|
-
throw new Error('[
|
|
30
|
+
throw new Error('[publish] failed to check git status.');
|
|
31
31
|
}
|
|
32
32
|
return Boolean(result.stdout.trim());
|
|
33
33
|
}
|
|
34
34
|
export async function commitRelease(cwd, version) {
|
|
35
35
|
const addResult = await runGit(['add', '-A'], cwd);
|
|
36
36
|
if (addResult.exitCode) {
|
|
37
|
-
throw new Error('[
|
|
37
|
+
throw new Error('[publish] failed to stage release changes.');
|
|
38
38
|
}
|
|
39
39
|
const commitResult = await runGit(['commit', '-m', `release: ${version}`], cwd);
|
|
40
40
|
if (commitResult.exitCode) {
|
|
41
|
-
throw new Error('[
|
|
41
|
+
throw new Error('[publish] failed to commit release changes.');
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
export async function createVersionTag(cwd, version, logger) {
|
|
@@ -50,6 +50,6 @@ export async function createVersionTag(cwd, version, logger) {
|
|
|
50
50
|
}
|
|
51
51
|
const result = await runGit(['tag', tagName], cwd);
|
|
52
52
|
if (result.exitCode) {
|
|
53
|
-
throw new Error(`[
|
|
53
|
+
throw new Error(`[publish] failed to create git tag ${tagName}.`);
|
|
54
54
|
}
|
|
55
55
|
}
|
|
@@ -11,7 +11,9 @@ export function readPackageJson(packageRoot) {
|
|
|
11
11
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
12
12
|
}
|
|
13
13
|
catch (error) {
|
|
14
|
-
throw new Error(`[
|
|
14
|
+
throw new Error(`[publish] failed to read package.json at ${filePath}.`, {
|
|
15
|
+
cause: error,
|
|
16
|
+
});
|
|
15
17
|
}
|
|
16
18
|
}
|
|
17
19
|
export function writePackageJson(packageRoot, packageJson) {
|
|
@@ -22,13 +24,13 @@ export function requirePackageName(packageRoot, packageJson) {
|
|
|
22
24
|
if (isString(packageJson.name) && packageJson.name) {
|
|
23
25
|
return packageJson.name;
|
|
24
26
|
}
|
|
25
|
-
throw new Error(`[
|
|
27
|
+
throw new Error(`[publish] package.json#name is required at ${packageRoot}.`);
|
|
26
28
|
}
|
|
27
29
|
export function requirePackageVersion(packageRoot, packageJson) {
|
|
28
30
|
if (isString(packageJson.version) && packageJson.version) {
|
|
29
31
|
return packageJson.version;
|
|
30
32
|
}
|
|
31
|
-
throw new Error(`[
|
|
33
|
+
throw new Error(`[publish] package.json#version is required at ${packageRoot}.`);
|
|
32
34
|
}
|
|
33
35
|
export function getPublishConfig(packageJson) {
|
|
34
36
|
return packageJson.auklet?.publish ?? {};
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import type { WorkspacePackage } from '#auklet/publish/types';
|
|
2
|
+
export declare class NpmPublishAuthenticationError extends Error {
|
|
3
|
+
constructor(packageRoot: string);
|
|
4
|
+
}
|
|
2
5
|
export declare function ensurePnpm(): Promise<string>;
|
|
3
6
|
export declare function readPnpmWorkspacePackages(root: string): Promise<WorkspacePackage[]>;
|
|
4
7
|
export declare function runPnpmBuild(packageRoot: string): Promise<void>;
|
|
5
8
|
export declare function runPnpmPublish(packageRoot: string, args: Array<string>): Promise<void>;
|
|
6
|
-
export declare function runPnpmWhoami(
|
|
9
|
+
export declare function runPnpmWhoami(packageRoot: string, options?: {
|
|
10
|
+
packageName?: string;
|
|
11
|
+
registry?: string;
|
|
12
|
+
}): Promise<string>;
|
|
7
13
|
export declare function runPnpmOwnerAdd(packageName: string, user: string, options: {
|
|
8
14
|
cwd: string;
|
|
9
15
|
otp?: string;
|
|
10
16
|
}): Promise<void>;
|
|
17
|
+
export declare function hasNpmAuthChallenge(result: {
|
|
18
|
+
stdout?: unknown;
|
|
19
|
+
stderr?: unknown;
|
|
20
|
+
}): boolean;
|
|
@@ -3,6 +3,12 @@ import { execa } from 'execa';
|
|
|
3
3
|
import semver from 'semver';
|
|
4
4
|
import { readPnpmWorkspacePackageInfo } from '#auklet/workspace/packages';
|
|
5
5
|
const supportedPnpmRange = '>=10.0.0';
|
|
6
|
+
export class NpmPublishAuthenticationError extends Error {
|
|
7
|
+
constructor(packageRoot) {
|
|
8
|
+
super(`[publish] pnpm publish failed at ${packageRoot}: ` +
|
|
9
|
+
`npm publish requires additional authentication.`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
6
12
|
const runPnpm = async (args, options = {}) => {
|
|
7
13
|
return execa('pnpm', args, {
|
|
8
14
|
reject: false,
|
|
@@ -13,15 +19,15 @@ export async function ensurePnpm() {
|
|
|
13
19
|
const result = await runPnpm(['--version']);
|
|
14
20
|
const stdout = String(result.stdout ?? '');
|
|
15
21
|
if (result.failed || !stdout) {
|
|
16
|
-
throw new Error('[
|
|
17
|
-
'[
|
|
22
|
+
throw new Error('[publish] pnpm is required for publishing.\n' +
|
|
23
|
+
'[publish] Install pnpm first:\n' +
|
|
18
24
|
' corepack enable\n' +
|
|
19
25
|
' corepack prepare pnpm@10 --activate');
|
|
20
26
|
}
|
|
21
27
|
const version = stdout.trim();
|
|
22
28
|
if (!semver.satisfies(version, supportedPnpmRange)) {
|
|
23
|
-
throw new Error(`[
|
|
24
|
-
`[
|
|
29
|
+
throw new Error(`[publish] unsupported pnpm version: ${version}\n` +
|
|
30
|
+
`[publish] expected pnpm ${supportedPnpmRange}`);
|
|
25
31
|
}
|
|
26
32
|
return version;
|
|
27
33
|
}
|
|
@@ -34,7 +40,9 @@ export async function readPnpmWorkspacePackages(root) {
|
|
|
34
40
|
});
|
|
35
41
|
}
|
|
36
42
|
catch (error) {
|
|
37
|
-
throw new Error('[
|
|
43
|
+
throw new Error('[publish] failed to read pnpm workspace packages.', {
|
|
44
|
+
cause: error,
|
|
45
|
+
});
|
|
38
46
|
}
|
|
39
47
|
}
|
|
40
48
|
export async function runPnpmBuild(packageRoot) {
|
|
@@ -43,25 +51,34 @@ export async function runPnpmBuild(packageRoot) {
|
|
|
43
51
|
stdio: 'inherit',
|
|
44
52
|
});
|
|
45
53
|
if (result.exitCode) {
|
|
46
|
-
throw new Error(`[
|
|
54
|
+
throw new Error(`[publish] build failed at ${packageRoot}.`);
|
|
47
55
|
}
|
|
48
56
|
}
|
|
49
57
|
export async function runPnpmPublish(packageRoot, args) {
|
|
50
58
|
const result = await runPnpm(['publish', ...args], {
|
|
51
59
|
cwd: packageRoot,
|
|
52
|
-
stdio: '
|
|
60
|
+
stdio: 'pipe',
|
|
53
61
|
});
|
|
62
|
+
writeProcessOutput(result);
|
|
54
63
|
if (result.exitCode) {
|
|
55
|
-
|
|
64
|
+
if (hasNpmAuthChallenge(result)) {
|
|
65
|
+
throw new NpmPublishAuthenticationError(packageRoot);
|
|
66
|
+
}
|
|
67
|
+
throw new Error(`[publish] pnpm publish failed at ${packageRoot}.`);
|
|
56
68
|
}
|
|
57
69
|
}
|
|
58
|
-
export async function runPnpmWhoami(
|
|
59
|
-
const
|
|
60
|
-
|
|
70
|
+
export async function runPnpmWhoami(packageRoot, options = {}) {
|
|
71
|
+
const args = ['whoami'];
|
|
72
|
+
if (options.registry)
|
|
73
|
+
args.push('--registry', options.registry);
|
|
74
|
+
const result = await runPnpm(args, {
|
|
75
|
+
cwd: packageRoot,
|
|
61
76
|
});
|
|
62
77
|
if (result.exitCode) {
|
|
63
|
-
|
|
64
|
-
|
|
78
|
+
const target = options.packageName ? ` for ${options.packageName}` : '';
|
|
79
|
+
const registry = options.registry ? ` at ${options.registry}` : '';
|
|
80
|
+
throw new Error(`[publish] npm authentication is required${target}${registry} before publishing.\n` +
|
|
81
|
+
'[publish] Run `pnpm login` or configure an npm token before retrying.');
|
|
65
82
|
}
|
|
66
83
|
return String(result.stdout ?? '').trim();
|
|
67
84
|
}
|
|
@@ -74,7 +91,7 @@ export async function runPnpmOwnerAdd(packageName, user, options) {
|
|
|
74
91
|
stdio: 'inherit',
|
|
75
92
|
});
|
|
76
93
|
if (result.exitCode) {
|
|
77
|
-
throw new Error(`[
|
|
94
|
+
throw new Error(`[publish] pnpm owner add failed for ${user} -> ${packageName}.`);
|
|
78
95
|
}
|
|
79
96
|
}
|
|
80
97
|
const isWorkspacePackage = (value) => {
|
|
@@ -90,6 +107,29 @@ const isWorkspacePackage = (value) => {
|
|
|
90
107
|
(value.private === undefined || typeof value.private === 'boolean'));
|
|
91
108
|
};
|
|
92
109
|
function throwInvalidWorkspacePackages() {
|
|
93
|
-
throw new Error('[
|
|
94
|
-
'[
|
|
110
|
+
throw new Error('[publish] failed to read pnpm workspace packages.\n' +
|
|
111
|
+
'[publish] Expected `pnpm list -r --depth -1 --json` to return package objects with name/path/version.');
|
|
112
|
+
}
|
|
113
|
+
export function hasNpmAuthChallenge(result) {
|
|
114
|
+
const output = [result.stdout, result.stderr]
|
|
115
|
+
.map((value) => String(value ?? ''))
|
|
116
|
+
.join('\n');
|
|
117
|
+
return isNpmAuthChallenge(output);
|
|
95
118
|
}
|
|
119
|
+
const writeProcessOutput = (result) => {
|
|
120
|
+
const stdout = String(result.stdout ?? '');
|
|
121
|
+
const stderr = String(result.stderr ?? '');
|
|
122
|
+
if (stdout)
|
|
123
|
+
process.stdout.write(stdout);
|
|
124
|
+
if (stderr)
|
|
125
|
+
process.stderr.write(stderr);
|
|
126
|
+
};
|
|
127
|
+
const isNpmAuthChallenge = (output) => {
|
|
128
|
+
return [
|
|
129
|
+
'Authenticate your account',
|
|
130
|
+
'one-time password',
|
|
131
|
+
'OTP',
|
|
132
|
+
'EOTP',
|
|
133
|
+
'ENEEDAUTH',
|
|
134
|
+
].some((text) => output.includes(text));
|
|
135
|
+
};
|
|
@@ -14,7 +14,7 @@ export async function runPublishHook(options) {
|
|
|
14
14
|
env: createHookEnv(options),
|
|
15
15
|
});
|
|
16
16
|
if (result.exitCode) {
|
|
17
|
-
throw new Error(`[
|
|
17
|
+
throw new Error(`[publish] publish ${options.status} hook failed: ${command}`);
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
}
|
package/dist/publish/cli.js
CHANGED
|
@@ -26,7 +26,7 @@ export async function runPublishCli(args) {
|
|
|
26
26
|
});
|
|
27
27
|
validateFlags(argv, publishFlags);
|
|
28
28
|
if (argv._.length) {
|
|
29
|
-
throw new Error(`[
|
|
29
|
+
throw new Error(`[publish] unknown publish argument: ${argv._.join(' ')}`);
|
|
30
30
|
}
|
|
31
31
|
await ensurePnpm();
|
|
32
32
|
await new PublishRunner({
|
|
@@ -49,10 +49,10 @@ export async function runOwnerCli(args) {
|
|
|
49
49
|
validateFlags(argv, ownerFlags);
|
|
50
50
|
const [subcommand, ...users] = argv._;
|
|
51
51
|
if (subcommand !== 'add') {
|
|
52
|
-
throw new Error('[
|
|
52
|
+
throw new Error('[publish] expected owner command: auk owner add <user...>');
|
|
53
53
|
}
|
|
54
54
|
if (!users.length) {
|
|
55
|
-
throw new Error('[
|
|
55
|
+
throw new Error('[publish] owner add requires at least one user.');
|
|
56
56
|
}
|
|
57
57
|
await ensurePnpm();
|
|
58
58
|
await new OwnerRunner({
|
|
@@ -66,14 +66,14 @@ export async function runOwnerCli(args) {
|
|
|
66
66
|
const validateFlags = (argv, allowedFlags) => {
|
|
67
67
|
for (const flag of Object.keys(argv)) {
|
|
68
68
|
if (!allowedFlags.has(flag)) {
|
|
69
|
-
throw new Error(`[
|
|
69
|
+
throw new Error(`[publish] unknown option: --${flag}`);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
};
|
|
73
73
|
const validateNoPrefixedFlags = (args, allowedFlags) => {
|
|
74
74
|
const flag = args.find((arg) => arg.startsWith('--no-') && !allowedFlags.has(arg));
|
|
75
75
|
if (flag) {
|
|
76
|
-
throw new Error(`[
|
|
76
|
+
throw new Error(`[publish] unknown option: ${flag}`);
|
|
77
77
|
}
|
|
78
78
|
};
|
|
79
79
|
const stripLeadingArgsSeparator = (args) => {
|
|
@@ -8,7 +8,7 @@ export class OwnerRunner {
|
|
|
8
8
|
async run() {
|
|
9
9
|
const packageNames = await resolveOwnerPackageNames(this.options);
|
|
10
10
|
if (!packageNames.length) {
|
|
11
|
-
throw new Error('[
|
|
11
|
+
throw new Error('[publish] no owner target package found.');
|
|
12
12
|
}
|
|
13
13
|
for (const packageName of packageNames) {
|
|
14
14
|
for (const user of this.options.users) {
|
|
@@ -23,8 +23,8 @@ export class PublishRunner {
|
|
|
23
23
|
this.logger = createScopedAukletLogger('publish');
|
|
24
24
|
this.summaryLogger = createAukletLogger();
|
|
25
25
|
this.git = new ReleaseGitController(this.options, this.logger);
|
|
26
|
-
this.publisher = new PackagePublisher(this.options);
|
|
27
|
-
this.preflight = new PublishPreflight(this.options);
|
|
26
|
+
this.publisher = new PackagePublisher(this.options, this.logger);
|
|
27
|
+
this.preflight = new PublishPreflight(this.options, this.logger);
|
|
28
28
|
this.versions = new VersionWriter(this.options, this.logger);
|
|
29
29
|
}
|
|
30
30
|
async run() {
|
|
@@ -4,7 +4,7 @@ export function validateBuildScript(targets) {
|
|
|
4
4
|
for (const target of targets) {
|
|
5
5
|
const buildScript = target.packageJson.scripts?.build;
|
|
6
6
|
if (!isString(buildScript) || !buildScript) {
|
|
7
|
-
throw new Error(`[
|
|
7
|
+
throw new Error(`[publish] package ${target.packageName} must define package.json#scripts.build before publishing.`);
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import type { AukletLogger } from '#auklet/logger';
|
|
1
2
|
import type { PublishOptions, PublishPlan } from '#auklet/publish/types';
|
|
2
3
|
export declare class PackagePublisher {
|
|
3
4
|
private readonly options;
|
|
5
|
+
private readonly logger;
|
|
4
6
|
private readonly pnpm;
|
|
5
|
-
constructor(options: PublishOptions);
|
|
7
|
+
constructor(options: PublishOptions, logger: AukletLogger);
|
|
6
8
|
run(plan: PublishPlan): Promise<void>;
|
|
7
9
|
}
|
|
10
|
+
export declare function logAuthenticationError(logger: AukletLogger, error: unknown): void;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { PnpmPublishApi } from '#auklet/publish/api/pnpmPublishApi';
|
|
2
|
+
import { NpmPublishAuthenticationError } from '#auklet/publish/api/pnpmApi';
|
|
2
3
|
import { PublishTargetError } from '#auklet/publish/runner/publishTargetError';
|
|
3
4
|
export class PackagePublisher {
|
|
4
5
|
options;
|
|
6
|
+
logger;
|
|
5
7
|
pnpm = new PnpmPublishApi();
|
|
6
|
-
constructor(options) {
|
|
8
|
+
constructor(options, logger) {
|
|
7
9
|
this.options = options;
|
|
10
|
+
this.logger = logger;
|
|
8
11
|
}
|
|
9
12
|
async run(plan) {
|
|
10
13
|
const publishedTargets = [];
|
|
@@ -14,8 +17,16 @@ export class PackagePublisher {
|
|
|
14
17
|
publishedTargets.push(target);
|
|
15
18
|
}
|
|
16
19
|
catch (error) {
|
|
20
|
+
logAuthenticationError(this.logger, error);
|
|
17
21
|
throw new PublishTargetError(target, 'publish', error, publishedTargets);
|
|
18
22
|
}
|
|
19
23
|
}
|
|
20
24
|
}
|
|
21
25
|
}
|
|
26
|
+
export function logAuthenticationError(logger, error) {
|
|
27
|
+
if (!(error instanceof NpmPublishAuthenticationError))
|
|
28
|
+
return;
|
|
29
|
+
logger.error('npm publish requires additional authentication.');
|
|
30
|
+
logger.error('If publish 2FA is enabled, retry with `auk publish --otp <code>`.');
|
|
31
|
+
logger.error('For CI, use an npm automation token.');
|
|
32
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import type { AukletLogger } from '#auklet/logger';
|
|
1
2
|
import type { PublishOptions, PublishPlan } from '#auklet/publish/types';
|
|
2
3
|
export declare class PublishPreflight {
|
|
3
4
|
private readonly options;
|
|
5
|
+
private readonly logger;
|
|
4
6
|
private readonly pnpm;
|
|
5
|
-
constructor(options: PublishOptions);
|
|
7
|
+
constructor(options: PublishOptions, logger: AukletLogger);
|
|
6
8
|
run(plan: PublishPlan): Promise<void>;
|
|
7
9
|
verifyAuthentication(plan: PublishPlan): Promise<void>;
|
|
8
10
|
private verifyPnpmPublishDryRun;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
import { isPlainObject, isString } from 'aidly';
|
|
1
2
|
import { PnpmPublishApi } from '#auklet/publish/api/pnpmPublishApi';
|
|
2
3
|
import { runPnpmWhoami } from '#auklet/publish/api/pnpmApi';
|
|
4
|
+
import { logAuthenticationError } from '#auklet/publish/runner/packagePublisher';
|
|
3
5
|
import { PublishTargetError } from '#auklet/publish/runner/publishTargetError';
|
|
4
6
|
export class PublishPreflight {
|
|
5
7
|
options;
|
|
8
|
+
logger;
|
|
6
9
|
pnpm = new PnpmPublishApi();
|
|
7
|
-
constructor(options) {
|
|
10
|
+
constructor(options, logger) {
|
|
8
11
|
this.options = options;
|
|
12
|
+
this.logger = logger;
|
|
9
13
|
}
|
|
10
14
|
async run(plan) {
|
|
11
15
|
await this.verifyPnpmPublishDryRun(plan);
|
|
@@ -13,7 +17,18 @@ export class PublishPreflight {
|
|
|
13
17
|
async verifyAuthentication(plan) {
|
|
14
18
|
if (plan.dryRun)
|
|
15
19
|
return;
|
|
16
|
-
|
|
20
|
+
const checked = new Set();
|
|
21
|
+
for (const target of plan.targets) {
|
|
22
|
+
const registry = getPublishRegistry(target.packageJson.publishConfig);
|
|
23
|
+
const key = `${target.packageRoot}\n${registry ?? ''}`;
|
|
24
|
+
if (checked.has(key))
|
|
25
|
+
continue;
|
|
26
|
+
await runPnpmWhoami(target.packageRoot, {
|
|
27
|
+
packageName: target.packageName,
|
|
28
|
+
registry,
|
|
29
|
+
});
|
|
30
|
+
checked.add(key);
|
|
31
|
+
}
|
|
17
32
|
}
|
|
18
33
|
async verifyPnpmPublishDryRun(plan) {
|
|
19
34
|
const options = {
|
|
@@ -25,8 +40,15 @@ export class PublishPreflight {
|
|
|
25
40
|
await this.pnpm.publish(target, options);
|
|
26
41
|
}
|
|
27
42
|
catch (error) {
|
|
43
|
+
logAuthenticationError(this.logger, error);
|
|
28
44
|
throw new PublishTargetError(target, 'preflight', error, []);
|
|
29
45
|
}
|
|
30
46
|
}
|
|
31
47
|
}
|
|
32
48
|
}
|
|
49
|
+
const getPublishRegistry = (publishConfig) => {
|
|
50
|
+
if (!isPlainObject(publishConfig))
|
|
51
|
+
return undefined;
|
|
52
|
+
const registry = Reflect.get(publishConfig, 'registry');
|
|
53
|
+
return isString(registry) && registry.length > 0 ? registry : undefined;
|
|
54
|
+
};
|
|
@@ -5,7 +5,7 @@ export class PublishTargetError extends Error {
|
|
|
5
5
|
originalError;
|
|
6
6
|
publishedTargets;
|
|
7
7
|
constructor(target, phase, originalError, publishedTargets) {
|
|
8
|
-
super(`[
|
|
8
|
+
super(`[publish] ${phase} failed for ${target.packageName} at ${path.relative(process.cwd(), target.packageRoot) || target.packageRoot}.`);
|
|
9
9
|
this.target = target;
|
|
10
10
|
this.phase = phase;
|
|
11
11
|
this.originalError = originalError;
|
|
@@ -29,7 +29,7 @@ export class ReleaseGitController {
|
|
|
29
29
|
const changed = await hasGitChanges(plan.root);
|
|
30
30
|
if (changed) {
|
|
31
31
|
if (!this.options.version) {
|
|
32
|
-
throw new Error('[
|
|
32
|
+
throw new Error('[publish] build or format changed files. Commit changes before publishing without --version.');
|
|
33
33
|
}
|
|
34
34
|
await commitRelease(plan.root, plan.version);
|
|
35
35
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import semver from 'semver';
|
|
2
3
|
import { createScopedAukletLogger } from '#auklet/logger';
|
|
3
4
|
import { findWorkspaceRoot } from '#auklet/workspace/root';
|
|
4
5
|
import { getPublishConfig, readPackageJson, requirePackageName, requirePackageVersion, } from '#auklet/publish/api/packageJsonApi';
|
|
@@ -12,7 +13,7 @@ export async function resolvePublishPlan(options, logger = createScopedAukletLog
|
|
|
12
13
|
}
|
|
13
14
|
export async function resolveOwnerPackageNames(options) {
|
|
14
15
|
if (options.filters.length && options.packages.length) {
|
|
15
|
-
throw new Error('[
|
|
16
|
+
throw new Error('[publish] owner command cannot use --filter and --package together.');
|
|
16
17
|
}
|
|
17
18
|
if (options.filters.length) {
|
|
18
19
|
const root = requireWorkspaceRoot(options.cwd);
|
|
@@ -25,7 +26,7 @@ export async function resolveOwnerPackageNames(options) {
|
|
|
25
26
|
return [...new Set(options.packages)];
|
|
26
27
|
const packageJson = readPackageJson(options.cwd);
|
|
27
28
|
if (packageJson.private) {
|
|
28
|
-
throw new Error('[
|
|
29
|
+
throw new Error('[publish] current package is private.');
|
|
29
30
|
}
|
|
30
31
|
return [requirePackageName(options.cwd, packageJson)];
|
|
31
32
|
}
|
|
@@ -35,7 +36,7 @@ const resolveCurrentPackagePublishPlan = async (options) => {
|
|
|
35
36
|
const version = requirePackageVersion(packageRoot, packageJson);
|
|
36
37
|
const workspaceRoot = findWorkspaceRoot(packageRoot);
|
|
37
38
|
if (workspaceRoot === packageRoot && packageJson.private) {
|
|
38
|
-
throw new Error('[
|
|
39
|
+
throw new Error('[publish] current directory is a private monorepo root. Use --filter to select workspace packages.');
|
|
39
40
|
}
|
|
40
41
|
const publishVersion = await resolvePublishVersion(version, options.version, packageRoot);
|
|
41
42
|
const target = createPublishTarget({
|
|
@@ -60,7 +61,6 @@ const resolveMonorepoPublishPlan = async (options, logger) => {
|
|
|
60
61
|
const rootVersion = requirePackageVersion(root, rootPackageJson);
|
|
61
62
|
const workspacePackages = await readPnpmWorkspacePackages(root);
|
|
62
63
|
const selectedPackages = filterWorkspacePackages(workspacePackages, options.filters);
|
|
63
|
-
const publishVersion = await resolvePublishVersion(rootVersion, options.version, root);
|
|
64
64
|
const targets = selectedPackages
|
|
65
65
|
.filter((item) => {
|
|
66
66
|
if (!item.private)
|
|
@@ -75,35 +75,51 @@ const resolveMonorepoPublishPlan = async (options, logger) => {
|
|
|
75
75
|
return createPublishTarget({
|
|
76
76
|
packageRoot: item.path,
|
|
77
77
|
packageJson,
|
|
78
|
-
publishVersion,
|
|
78
|
+
publishVersion: '',
|
|
79
79
|
workspaceMode: 'monorepo',
|
|
80
80
|
});
|
|
81
81
|
});
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
const versionBase = getMonorepoVersionBase(rootVersion, targets);
|
|
83
|
+
const publishVersion = await resolvePublishVersion(versionBase, options.version, root);
|
|
84
|
+
const publishTargets = targets.map((target) => ({
|
|
85
|
+
...target,
|
|
86
|
+
publishVersion,
|
|
87
|
+
}));
|
|
88
|
+
validatePublishTargets(publishTargets);
|
|
89
|
+
validateWorkspaceInternalDependencies(publishTargets);
|
|
84
90
|
if (!options.version) {
|
|
85
|
-
validateVersionConsistency(rootVersion,
|
|
91
|
+
validateVersionConsistency(rootVersion, publishTargets);
|
|
86
92
|
}
|
|
87
93
|
return {
|
|
88
94
|
root,
|
|
89
95
|
version: publishVersion,
|
|
90
96
|
dryRun: options.dryRun,
|
|
91
|
-
targets: sortTargetsByWorkspaceDependencies(
|
|
97
|
+
targets: sortTargetsByWorkspaceDependencies(publishTargets),
|
|
92
98
|
config: getPublishConfig(rootPackageJson),
|
|
93
99
|
workspaceMode: 'monorepo',
|
|
94
100
|
};
|
|
95
101
|
};
|
|
102
|
+
const getMonorepoVersionBase = (rootVersion, targets) => {
|
|
103
|
+
return targets.reduce((highest, target) => {
|
|
104
|
+
return isGreaterVersion(target.version, highest) ? target.version : highest;
|
|
105
|
+
}, rootVersion);
|
|
106
|
+
};
|
|
107
|
+
const isGreaterVersion = (version, current) => {
|
|
108
|
+
return semver.valid(version) && semver.valid(current)
|
|
109
|
+
? semver.gt(version, current)
|
|
110
|
+
: false;
|
|
111
|
+
};
|
|
96
112
|
const requireWorkspaceRoot = (cwd) => {
|
|
97
113
|
const root = findWorkspaceRoot(cwd);
|
|
98
114
|
if (!root) {
|
|
99
|
-
throw new Error('[
|
|
115
|
+
throw new Error('[publish] --filter requires a pnpm workspace root.');
|
|
100
116
|
}
|
|
101
117
|
return root;
|
|
102
118
|
};
|
|
103
119
|
const filterWorkspacePackages = (packages, filters) => {
|
|
104
120
|
const matched = packages.filter((item) => filters.some((filter) => matchesFilter(item.name, filter)));
|
|
105
121
|
if (!matched.length) {
|
|
106
|
-
throw new Error(`[
|
|
122
|
+
throw new Error(`[publish] no workspace package matched filter: ${filters.join(', ')}`);
|
|
107
123
|
}
|
|
108
124
|
return matched;
|
|
109
125
|
};
|
|
@@ -134,7 +150,7 @@ const createPublishTarget = (options) => {
|
|
|
134
150
|
const validatePublishTargets = (targets) => {
|
|
135
151
|
const publishableTargets = targets.filter((target) => !target.private);
|
|
136
152
|
if (!publishableTargets.length) {
|
|
137
|
-
throw new Error('[
|
|
153
|
+
throw new Error('[publish] no publishable package found.');
|
|
138
154
|
}
|
|
139
155
|
};
|
|
140
156
|
const sortTargetsByWorkspaceDependencies = (targets) => {
|
|
@@ -147,7 +163,7 @@ const sortTargetsByWorkspaceDependencies = (targets) => {
|
|
|
147
163
|
if (visited.has(target.packageName))
|
|
148
164
|
return;
|
|
149
165
|
if (visiting.has(target.packageName)) {
|
|
150
|
-
throw new Error(`[
|
|
166
|
+
throw new Error(`[publish] circular workspace dependency detected at ${target.packageName}.`);
|
|
151
167
|
}
|
|
152
168
|
visiting.add(target.packageName);
|
|
153
169
|
for (const dependency of getWorkspaceDependencies(target.packageJson)) {
|
|
@@ -183,7 +199,7 @@ const validateWorkspaceInternalDependencies = (targets) => {
|
|
|
183
199
|
continue;
|
|
184
200
|
if (version === 'workspace:*')
|
|
185
201
|
continue;
|
|
186
|
-
throw new Error(`[
|
|
202
|
+
throw new Error(`[publish] package ${target.packageName} ${dependencyGroup} ${packageName} must use workspace:* before publishing.`);
|
|
187
203
|
}
|
|
188
204
|
}
|
|
189
205
|
}
|
package/dist/publish/version.js
CHANGED
|
@@ -5,26 +5,26 @@ const releaseIncrements = new Set(['patch', 'minor', 'major']);
|
|
|
5
5
|
export async function resolvePublishVersion(currentVersion, versionSpec, cwd) {
|
|
6
6
|
if (!versionSpec) {
|
|
7
7
|
if (!semver.valid(currentVersion)) {
|
|
8
|
-
throw new Error(`[
|
|
8
|
+
throw new Error(`[publish] invalid package version: ${currentVersion}`);
|
|
9
9
|
}
|
|
10
10
|
return currentVersion;
|
|
11
11
|
}
|
|
12
12
|
if (releaseIncrements.has(versionSpec)) {
|
|
13
13
|
const parsed = semver.parse(currentVersion);
|
|
14
14
|
if (!parsed) {
|
|
15
|
-
throw new Error(`[
|
|
15
|
+
throw new Error(`[publish] invalid package version: ${currentVersion}`);
|
|
16
16
|
}
|
|
17
17
|
const baseVersion = `${parsed.major}.${parsed.minor}.${parsed.patch}`;
|
|
18
18
|
const next = semver.inc(baseVersion, versionSpec);
|
|
19
19
|
if (!next) {
|
|
20
|
-
throw new Error(`[
|
|
20
|
+
throw new Error(`[publish] failed to bump version ${currentVersion} with ${versionSpec}.`);
|
|
21
21
|
}
|
|
22
22
|
return next;
|
|
23
23
|
}
|
|
24
24
|
if (prereleaseKinds.has(versionSpec)) {
|
|
25
25
|
const parsed = semver.parse(currentVersion);
|
|
26
26
|
if (!parsed) {
|
|
27
|
-
throw new Error(`[
|
|
27
|
+
throw new Error(`[publish] invalid package version: ${currentVersion}`);
|
|
28
28
|
}
|
|
29
29
|
const suffix = (await getGitShortHash(cwd)) ?? createTimestamp();
|
|
30
30
|
return `${parsed.major}.${parsed.minor}.${parsed.patch}-${versionSpec}.${suffix}`;
|
|
@@ -32,12 +32,12 @@ export async function resolvePublishVersion(currentVersion, versionSpec, cwd) {
|
|
|
32
32
|
const explicit = semver.valid(versionSpec);
|
|
33
33
|
if (explicit)
|
|
34
34
|
return explicit;
|
|
35
|
-
throw new Error(`[
|
|
35
|
+
throw new Error(`[publish] unsupported --version value: ${versionSpec}`);
|
|
36
36
|
}
|
|
37
37
|
export function validateVersionConsistency(expectedVersion, versions) {
|
|
38
38
|
for (const item of versions) {
|
|
39
39
|
if (item.version !== expectedVersion) {
|
|
40
|
-
throw new Error(`[
|
|
40
|
+
throw new Error(`[publish] package ${item.packageName} version ${item.version} does not match shared version ${expectedVersion}.`);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -7,7 +7,7 @@ const parsePnpmWorkspacePackages = (value) => {
|
|
|
7
7
|
parsed = JSON.parse(value);
|
|
8
8
|
}
|
|
9
9
|
catch (error) {
|
|
10
|
-
throw new Error('[
|
|
10
|
+
throw new Error('[workspace] failed to parse workspace packages.', {
|
|
11
11
|
cause: error,
|
|
12
12
|
});
|
|
13
13
|
}
|
|
@@ -27,7 +27,7 @@ export async function readPnpmWorkspacePackageInfo(root, options = {}) {
|
|
|
27
27
|
...options,
|
|
28
28
|
});
|
|
29
29
|
if (result.failed) {
|
|
30
|
-
throw new Error('[
|
|
30
|
+
throw new Error('[workspace] failed to read workspace packages.', {
|
|
31
31
|
cause: result.stderr || result.stdout,
|
|
32
32
|
});
|
|
33
33
|
}
|
|
@@ -39,7 +39,7 @@ export function readPnpmWorkspacePackageInfoSync(root) {
|
|
|
39
39
|
reject: false,
|
|
40
40
|
});
|
|
41
41
|
if (result.failed) {
|
|
42
|
-
throw new Error('[
|
|
42
|
+
throw new Error('[workspace] failed to read workspace packages.', {
|
|
43
43
|
cause: result.stderr || result.stdout,
|
|
44
44
|
});
|
|
45
45
|
}
|
|
@@ -57,6 +57,6 @@ const isWorkspacePackageInfo = (value) => {
|
|
|
57
57
|
(value.private === undefined || typeof value.private === 'boolean'));
|
|
58
58
|
};
|
|
59
59
|
function throwInvalidWorkspacePackages() {
|
|
60
|
-
throw new Error('[
|
|
61
|
-
'[
|
|
60
|
+
throw new Error('[workspace] failed to read workspace packages.\n' +
|
|
61
|
+
'[workspace] Expected `pnpm list -r --depth -1 --json` to return package objects with name/path.');
|
|
62
62
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auklet",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": "chentao.arthur",
|
|
6
6
|
"description": "Build utilities for TypeScript packages and module CSS output.",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"postcss": "8.5.15",
|
|
60
60
|
"chokidar": "5.0.0",
|
|
61
61
|
"semver": "7.8.1",
|
|
62
|
-
"briefing": "0.0.
|
|
62
|
+
"briefing": "0.0.5",
|
|
63
63
|
"minimist": "1.2.8",
|
|
64
64
|
"picocolors": "1.1.1",
|
|
65
65
|
"typescript": "6.0.3",
|