auklet 0.0.29 → 0.1.1

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.
@@ -1,3 +1,4 @@
1
+ import { execa, type Options } from 'execa';
1
2
  import type { WorkspacePackage } from '#auklet/publish/types';
2
3
  export declare class NpmPublishAuthenticationError extends Error {
3
4
  readonly packageRoot: string;
@@ -6,6 +7,7 @@ export declare class NpmPublishAuthenticationError extends Error {
6
7
  export declare class NpmPackageVersionExistsError extends Error {
7
8
  constructor(packageName: string, version: string, registry?: string);
8
9
  }
10
+ export declare function withPnpmTimeout(subprocess: ReturnType<typeof execa>, timeout: number): Promise<import("node_modules/execa/types/return/result").CommonResult<false, Options> & import("node_modules/execa/types/return/result").OmitErrorIfReject<boolean | undefined>>;
9
11
  export declare function ensurePnpm(): Promise<string>;
10
12
  export declare function readPnpmWorkspacePackages(root: string): Promise<WorkspacePackage[]>;
11
13
  export declare function runPnpmBuild(packageRoot: string): Promise<void>;
@@ -13,9 +15,11 @@ export declare function runPnpmPublish(packageRoot: string, args: Array<string>)
13
15
  export declare function runPnpmWhoami(packageRoot: string, options?: {
14
16
  packageName?: string;
15
17
  registry?: string;
18
+ timeout?: number;
16
19
  }): Promise<string>;
17
20
  export declare function hasPublishedPackageVersion(packageRoot: string, packageName: string, version: string, options?: {
18
21
  registry?: string;
22
+ timeout?: number;
19
23
  }): Promise<boolean>;
20
24
  export declare function runPnpmOwnerAdd(packageName: string, user: string, options: {
21
25
  cwd: string;
@@ -17,15 +17,43 @@ export class NpmPackageVersionExistsError extends Error {
17
17
  }
18
18
  }
19
19
  const runPnpm = async (args, options = {}) => {
20
- return execa('pnpm', args, {
20
+ const timeout = options.timeout;
21
+ const subprocess = execa('pnpm', args, {
21
22
  reject: false,
22
23
  ...options,
24
+ timeout: undefined,
23
25
  });
26
+ if (!timeout)
27
+ return subprocess;
28
+ return withPnpmTimeout(subprocess, timeout);
24
29
  };
30
+ export async function withPnpmTimeout(subprocess, timeout) {
31
+ let timeoutId = null;
32
+ const result = await Promise.race([
33
+ subprocess,
34
+ new Promise((resolve) => {
35
+ timeoutId = setTimeout(() => {
36
+ const error = new Error(`pnpm command timed out after ${timeout}ms.`);
37
+ subprocess.kill('SIGKILL', error);
38
+ resolve({
39
+ failed: true,
40
+ timedOut: true,
41
+ exitCode: undefined,
42
+ stdout: '',
43
+ stderr: error.message,
44
+ });
45
+ }, timeout);
46
+ }),
47
+ ]);
48
+ if (timeoutId)
49
+ clearTimeout(timeoutId);
50
+ subprocess.catch(() => { });
51
+ return result;
52
+ }
25
53
  export async function ensurePnpm() {
26
54
  const result = await runPnpm(['--version']);
27
55
  const stdout = String(result.stdout ?? '');
28
- if (result.failed || !stdout) {
56
+ if (hasFailedPnpmResult(result) || !stdout) {
29
57
  throw new Error('[publish] pnpm is required for publishing.\n' +
30
58
  '[publish] Install pnpm first:\n' +
31
59
  ' corepack enable\n' +
@@ -57,7 +85,7 @@ export async function runPnpmBuild(packageRoot) {
57
85
  cwd: packageRoot,
58
86
  stdio: 'inherit',
59
87
  });
60
- if (result.exitCode) {
88
+ if (hasFailedPnpmResult(result)) {
61
89
  throw new Error(`[publish] build failed at ${packageRoot}.`);
62
90
  }
63
91
  }
@@ -69,7 +97,7 @@ export async function runPnpmPublish(packageRoot, args) {
69
97
  });
70
98
  if (isDryRun)
71
99
  writeProcessOutput(result);
72
- if (result.exitCode) {
100
+ if (hasFailedPnpmResult(result)) {
73
101
  if (hasNpmAuthChallenge(result)) {
74
102
  throw new NpmPublishAuthenticationError(packageRoot);
75
103
  }
@@ -82,12 +110,15 @@ export async function runPnpmWhoami(packageRoot, options = {}) {
82
110
  args.push('--registry', options.registry);
83
111
  const result = await runPnpm(args, {
84
112
  cwd: packageRoot,
113
+ timeout: options.timeout,
85
114
  });
86
- if (result.exitCode) {
115
+ if (hasFailedPnpmResult(result)) {
87
116
  const target = options.packageName ? ` for ${options.packageName}` : '';
88
117
  const registry = options.registry ? ` at ${options.registry}` : '';
118
+ const reason = getPnpmFailureReason(result);
89
119
  throw new Error(`[publish] npm authentication is required${target}${registry} before publishing.\n` +
90
- '[publish] Run `pnpm login` or configure an npm token before retrying.');
120
+ '[publish] Run `pnpm login` or configure an npm token before retrying.' +
121
+ (reason ? `\n[publish] Reason: ${reason}` : ''));
91
122
  }
92
123
  return String(result.stdout ?? '').trim();
93
124
  }
@@ -97,12 +128,16 @@ export async function hasPublishedPackageVersion(packageRoot, packageName, versi
97
128
  args.push('--registry', options.registry);
98
129
  const result = await runPnpm(args, {
99
130
  cwd: packageRoot,
131
+ timeout: options.timeout,
100
132
  });
101
- if (!result.exitCode)
133
+ if (!hasFailedPnpmResult(result)) {
102
134
  return String(result.stdout ?? '').trim() === version;
135
+ }
103
136
  if (isPackageNotFound(result))
104
137
  return false;
105
- throw new Error(`[publish] failed to check published version for ${packageName}@${version}.`);
138
+ const reason = getPnpmFailureReason(result);
139
+ throw new Error(`[publish] failed to check published version for ${packageName}@${version}.` +
140
+ (reason ? `\n[publish] Reason: ${reason}` : ''));
106
141
  }
107
142
  export async function runPnpmOwnerAdd(packageName, user, options) {
108
143
  const args = ['owner', 'add', user, packageName];
@@ -112,7 +147,7 @@ export async function runPnpmOwnerAdd(packageName, user, options) {
112
147
  cwd: options.cwd,
113
148
  stdio: 'inherit',
114
149
  });
115
- if (result.exitCode) {
150
+ if (hasFailedPnpmResult(result)) {
116
151
  throw new Error(`[publish] pnpm owner add failed for ${user} -> ${packageName}.`);
117
152
  }
118
153
  }
@@ -128,6 +163,16 @@ const isWorkspacePackage = (value) => {
128
163
  value.version.length > 0 &&
129
164
  (value.private === undefined || typeof value.private === 'boolean'));
130
165
  };
166
+ const hasFailedPnpmResult = (result) => {
167
+ return result.failed === true || result.exitCode !== 0;
168
+ };
169
+ const getPnpmFailureReason = (result) => {
170
+ const stderr = String(result.stderr ?? '').trim();
171
+ if (stderr)
172
+ return stderr;
173
+ const stdout = String(result.stdout ?? '').trim();
174
+ return stdout || null;
175
+ };
131
176
  function throwInvalidWorkspacePackages() {
132
177
  throw new Error('[publish] failed to read pnpm workspace packages.\n' +
133
178
  '[publish] Expected `pnpm list -r --depth -1 --json` to return package objects with name/path/version.');
@@ -1,2 +1,3 @@
1
1
  import type { PublishOptions, PublishTarget } from '#auklet/publish/types';
2
2
  export declare function createPublishArgs(target: PublishTarget, options: PublishOptions): string[];
3
+ export declare function resolvePublishTag(versionSpec?: string): "alpha" | "beta" | null;
@@ -8,7 +8,7 @@ export function createPublishArgs(target, options) {
8
8
  args.push('--ignore-scripts');
9
9
  if (shouldAddPublicAccess(target))
10
10
  args.push('--access', 'public');
11
- const tag = getPublishTag(options.version);
11
+ const tag = resolvePublishTag(options.version);
12
12
  if (tag)
13
13
  args.push('--tag', tag);
14
14
  return args;
@@ -18,8 +18,8 @@ const shouldAddPublicAccess = (target) => {
18
18
  !target.private &&
19
19
  !target.packageJson.publishConfig?.access);
20
20
  };
21
- const getPublishTag = (versionSpec) => {
21
+ export function resolvePublishTag(versionSpec) {
22
22
  if (versionSpec === 'alpha' || versionSpec === 'beta')
23
23
  return versionSpec;
24
24
  return null;
25
- };
25
+ }
@@ -0,0 +1 @@
1
+ export declare function getPublishRegistry(publishConfig: unknown): string | undefined;
@@ -0,0 +1,7 @@
1
+ import { isPlainObject, isString } from 'aidly';
2
+ export function getPublishRegistry(publishConfig) {
3
+ if (!isPlainObject(publishConfig))
4
+ return undefined;
5
+ const registry = Reflect.get(publishConfig, 'registry');
6
+ return isString(registry) && registry.length > 0 ? registry : undefined;
7
+ }
@@ -1,2 +1,13 @@
1
1
  export declare function runPublishCli(args: Array<string>): Promise<void>;
2
+ export declare function resolvePublishCliOptions(args: Array<string>, cwd?: string): {
3
+ cwd: string;
4
+ otp: string | undefined;
5
+ filters: string[];
6
+ version: string | undefined;
7
+ git: boolean;
8
+ format: boolean;
9
+ dryRun: boolean;
10
+ allowDirty: boolean;
11
+ ignoreScripts: boolean;
12
+ };
2
13
  export declare function runOwnerCli(args: Array<string>): Promise<void>;
@@ -9,37 +9,47 @@ const publishFlags = new Set([
9
9
  'version',
10
10
  'dry-run',
11
11
  'format',
12
+ 'git',
12
13
  'otp',
13
14
  'ignore-scripts',
14
15
  'allow-dirty',
15
16
  ]);
16
17
  const ownerFlags = new Set(['_', 'filter', 'package', 'otp']);
17
18
  export async function runPublishCli(args) {
18
- const cliArgs = stripLeadingArgsSeparator(args);
19
- validateNoPrefixedFlags(cliArgs, new Set(['--no-format']));
20
- const argv = minimist(cliArgs, {
21
- string: ['filter', 'version', 'otp'],
22
- boolean: ['dry-run', 'format', 'ignore-scripts', 'allow-dirty'],
23
- default: {
24
- format: true,
25
- },
26
- });
27
- validateFlags(argv, publishFlags);
19
+ await ensurePnpm();
20
+ await new PublishRunner(resolvePublishCliOptions(args)).run();
21
+ }
22
+ export function resolvePublishCliOptions(args, cwd = process.cwd()) {
23
+ const argv = parsePublishArgs(args);
28
24
  if (argv._.length) {
29
25
  throw new Error(`[publish] unknown publish argument: ${argv._.join(' ')}`);
30
26
  }
31
- await ensurePnpm();
32
- await new PublishRunner({
33
- cwd: process.cwd(),
27
+ return {
28
+ cwd,
29
+ otp: stringOption(argv.otp),
34
30
  filters: toArray(argv.filter),
35
31
  version: stringOption(argv.version),
36
- dryRun: argv['dry-run'] === true,
32
+ git: argv.git !== false,
37
33
  format: argv.format !== false,
38
- otp: stringOption(argv.otp),
34
+ dryRun: argv['dry-run'] === true,
39
35
  allowDirty: argv['allow-dirty'] === true,
40
36
  ignoreScripts: argv['ignore-scripts'] === true,
41
- }).run();
37
+ };
42
38
  }
39
+ const parsePublishArgs = (args) => {
40
+ const cliArgs = stripLeadingArgsSeparator(args);
41
+ validateNoPrefixedFlags(cliArgs, new Set(['--no-format', '--no-git']));
42
+ const argv = minimist(cliArgs, {
43
+ string: ['filter', 'version', 'otp'],
44
+ boolean: ['dry-run', 'format', 'git', 'ignore-scripts', 'allow-dirty'],
45
+ default: {
46
+ git: true,
47
+ format: true,
48
+ },
49
+ });
50
+ validateFlags(argv, publishFlags);
51
+ return argv;
52
+ };
43
53
  export async function runOwnerCli(args) {
44
54
  const cliArgs = stripLeadingArgsSeparator(args);
45
55
  validateNoPrefixedFlags(cliArgs, new Set());
@@ -0,0 +1,29 @@
1
+ import { type PublishRegistryCheck } from '#auklet/publish/inspectRegistry';
2
+ import { type PackFileCheck } from '#auklet/publish/inspectPack';
3
+ import type { PublishOptions, PublishPlan } from '#auklet/publish/types';
4
+ export declare function runInspectPublishCli(args: Array<string>): Promise<0 | 1>;
5
+ export declare function createPublishInspectModel(options: PublishOptions, plan: PublishPlan, packageFileChecks?: Array<PackFileCheck>, registryChecks?: Array<PublishRegistryCheck>): {
6
+ details: {
7
+ mode: "monorepo" | "single";
8
+ publish: string;
9
+ packages: number;
10
+ version: string;
11
+ tag: string;
12
+ git: string;
13
+ format: string;
14
+ root: string;
15
+ };
16
+ packageFileChecks: PackFileCheck[];
17
+ targets: {
18
+ packageName: string;
19
+ version: string;
20
+ publishVersion: string;
21
+ registry: string;
22
+ access: string;
23
+ }[];
24
+ registryChecks: PublishRegistryCheck[];
25
+ hooks: {
26
+ name: string;
27
+ configured: boolean;
28
+ }[];
29
+ };
@@ -0,0 +1,220 @@
1
+ import path from 'node:path';
2
+ import { isString } from 'aidly';
3
+ import { createAukletLogger } from '#auklet/logger';
4
+ import { resolvePublishTag } from '#auklet/publish/api/publishArgs';
5
+ import { getPublishRegistry } from '#auklet/publish/api/registry';
6
+ import { ensurePnpm } from '#auklet/publish/api/pnpmApi';
7
+ import { resolvePublishCliOptions } from '#auklet/publish/cli';
8
+ import { inspectPublishRegistry, } from '#auklet/publish/inspectRegistry';
9
+ import { inspectPackageFiles, PackInspectReporter, } from '#auklet/publish/inspectPack';
10
+ import { resolvePublishPlan } from '#auklet/publish/targetResolver';
11
+ export async function runInspectPublishCli(args) {
12
+ await ensurePnpm();
13
+ const options = resolvePublishCliOptions(args);
14
+ const logger = createAukletLogger({ scope: 'inspect' });
15
+ const plan = await resolvePublishPlan(options, logger);
16
+ const packageFileChecks = inspectPackageFiles(plan.targets);
17
+ const packageFileCheckFailed = packageFileChecks.some((check) => check.status === 'missing');
18
+ if (packageFileCheckFailed) {
19
+ new PublishInspectReporter(options, plan, packageFileChecks, []).report();
20
+ return 1;
21
+ }
22
+ const spinner = logger.spinner('Checking publish registry');
23
+ spinner.start();
24
+ const registryChecks = await inspectPublishRegistry(plan, {
25
+ onCheck: (info) => {
26
+ spinner.text([
27
+ 'Checking registry ',
28
+ info.check,
29
+ ' for ',
30
+ logger.package(info.packageName),
31
+ ]);
32
+ },
33
+ onRetry: (info) => {
34
+ spinner.text([
35
+ 'Checking registry ',
36
+ info.check,
37
+ ' for ',
38
+ logger.package(info.packageName),
39
+ ', retrying ',
40
+ logger.value(`${info.attempt}/${info.maxAttempts}`),
41
+ ]);
42
+ },
43
+ });
44
+ if (registryChecks.some((check) => check.reason)) {
45
+ spinner.fail('Publish registry checks failed');
46
+ }
47
+ else {
48
+ spinner.succeed('Publish registry checks passed');
49
+ }
50
+ new PublishInspectReporter(options, plan, packageFileChecks, registryChecks).report();
51
+ if (registryChecks.some((check) => check.reason)) {
52
+ return 1;
53
+ }
54
+ return 0;
55
+ }
56
+ class PublishInspectReporter {
57
+ options;
58
+ plan;
59
+ packageFileChecks;
60
+ registryChecks;
61
+ logger = createAukletLogger();
62
+ constructor(options, plan, packageFileChecks, registryChecks) {
63
+ this.options = options;
64
+ this.plan = plan;
65
+ this.packageFileChecks = packageFileChecks;
66
+ this.registryChecks = registryChecks;
67
+ }
68
+ report() {
69
+ const model = createPublishInspectModel(this.options, this.plan, this.packageFileChecks, this.registryChecks);
70
+ new PackInspectReporter(this.options.cwd, this.plan.targets, model.packageFileChecks, 'Package file checks').report();
71
+ this.logger.newline();
72
+ this.logger.result({
73
+ title: this.formatResultTitle('Publish inspect', model.registryChecks.some((check) => check.reason)),
74
+ status: 'info',
75
+ body: [
76
+ 'Read-only publish plan. No files, git refs, or packages changed.',
77
+ ],
78
+ details: this.formatDetails(model),
79
+ });
80
+ this.logger.newline();
81
+ this.writeSectionTitle('Targets');
82
+ this.logger.rows({
83
+ columns: ['package', 'version', 'registry', 'access'],
84
+ rows: model.targets.map((target) => [
85
+ this.logger.package(target.packageName),
86
+ this.formatVersionChange(target.version, target.publishVersion),
87
+ this.logger.colors.gray(target.registry),
88
+ this.logger.colors.gray(target.access),
89
+ ]),
90
+ });
91
+ this.logger.newline();
92
+ this.writeSectionTitle('Registry checks');
93
+ this.logger.rows({
94
+ columns: ['package', 'registry', 'auth', 'version'],
95
+ rows: model.registryChecks.map((check) => [
96
+ this.logger.package(check.packageName),
97
+ this.logger.colors.gray(check.registry),
98
+ this.formatCheckStatus(check.auth),
99
+ this.formatCheckStatus(check.version),
100
+ ]),
101
+ });
102
+ const failedChecks = model.registryChecks.filter((check) => check.reason);
103
+ if (failedChecks.length) {
104
+ this.logger.newline();
105
+ this.writeSectionTitle('Registry issues', 'error');
106
+ this.writeRegistryIssues(failedChecks);
107
+ }
108
+ this.logger.newline();
109
+ this.writeSectionTitle('Hooks');
110
+ this.logger.rows({
111
+ columns: ['hook', 'configured'],
112
+ rows: model.hooks.map((hook) => [
113
+ this.logger.colors.gray(hook.name),
114
+ this.formatValue(hook.configured ? 'yes' : 'no'),
115
+ ]),
116
+ });
117
+ this.logger.newline();
118
+ }
119
+ formatDetails(model) {
120
+ return {
121
+ mode: this.formatValue(model.details.mode),
122
+ publish: this.formatValue(model.details.publish),
123
+ packages: this.formatValue(model.details.packages),
124
+ version: this.logger.version(model.details.version),
125
+ tag: this.formatValue(model.details.tag),
126
+ git: this.formatValue(model.details.git),
127
+ format: this.formatValue(model.details.format),
128
+ root: this.logger.path(model.details.root),
129
+ };
130
+ }
131
+ formatVersionChange(from, to) {
132
+ return [
133
+ this.logger.version(from),
134
+ this.logger.colors.gray(' -> '),
135
+ this.logger.version(to),
136
+ ];
137
+ }
138
+ formatValue(value) {
139
+ return this.logger.colors.bold(this.logger.colors.cyan(String(value)));
140
+ }
141
+ formatResultTitle(title, failed) {
142
+ return this.logger.colors.bold(failed ? this.logger.colors.red(title) : this.logger.colors.green(title));
143
+ }
144
+ formatCheckStatus(status) {
145
+ if (status === 'success')
146
+ return this.logger.colors.green('ok');
147
+ return this.logger.colors.red('failed');
148
+ }
149
+ writeSectionTitle(title, status = 'normal') {
150
+ const content = status === 'error' ? this.logger.colors.red(title) : title;
151
+ this.logger.raw(content);
152
+ }
153
+ writeRegistryIssues(checks) {
154
+ for (const [index, check] of checks.entries()) {
155
+ if (index > 0)
156
+ this.logger.newline();
157
+ const [firstLine, ...restLines] = String(check.reason ?? '').split('\n');
158
+ this.logger.item(this.logger.package(check.packageName), ': ', this.logger.colors.red(firstLine));
159
+ for (const line of restLines) {
160
+ this.logger.raw(` ${this.logger.colors.red(line)}`);
161
+ }
162
+ }
163
+ }
164
+ }
165
+ export function createPublishInspectModel(options, plan, packageFileChecks = [], registryChecks = []) {
166
+ return {
167
+ details: {
168
+ mode: plan.workspaceMode,
169
+ publish: plan.dryRun ? 'dry-run' : 'real',
170
+ packages: plan.targets.length,
171
+ version: plan.version,
172
+ tag: resolvePublishTag(options.version) ?? 'latest',
173
+ git: getGitMode(options),
174
+ format: options.format ? 'enabled' : 'disabled',
175
+ root: path.relative(options.cwd, plan.root) || '.',
176
+ },
177
+ packageFileChecks,
178
+ targets: plan.targets.map((target) => ({
179
+ packageName: target.packageName,
180
+ version: target.version,
181
+ publishVersion: target.publishVersion,
182
+ registry: getRegistry(target.packageJson),
183
+ access: getAccess(target),
184
+ })),
185
+ registryChecks,
186
+ hooks: getHookRows(plan.config),
187
+ };
188
+ }
189
+ const getGitMode = (options) => {
190
+ if (options.dryRun)
191
+ return 'skipped (dry-run)';
192
+ if (options.allowDirty)
193
+ return 'skipped (--allow-dirty)';
194
+ if (options.git === false)
195
+ return 'skipped (--no-git)';
196
+ return 'commit + tag';
197
+ };
198
+ const getHookRows = (config) => {
199
+ const hooks = [
200
+ ['beforeBuild', config.beforeBuild],
201
+ ['afterBuild', config.afterBuild],
202
+ ['beforePublish', config.beforePublish],
203
+ ['afterPublish', config.afterPublish],
204
+ ];
205
+ return hooks.map(([name, value]) => ({
206
+ name,
207
+ configured: Boolean(value),
208
+ }));
209
+ };
210
+ const getRegistry = (packageJson) => {
211
+ return getPublishRegistry(packageJson.publishConfig) ?? 'default';
212
+ };
213
+ const getAccess = (target) => {
214
+ const access = target.packageJson.publishConfig?.access;
215
+ if (isString(access) && access)
216
+ return access;
217
+ if (target.packageName.startsWith('@') && !target.private)
218
+ return 'public';
219
+ return 'default';
220
+ };
@@ -0,0 +1,28 @@
1
+ import type { PublishTarget } from '#auklet/publish/types';
2
+ export type PackFileCheckStatus = 'exists' | 'missing' | 'pattern' | 'skipped';
3
+ export type PackFileCheck = {
4
+ target: PublishTarget;
5
+ field: string;
6
+ file: string;
7
+ status: PackFileCheckStatus;
8
+ };
9
+ export declare function runInspectPackCli(args: Array<string>): Promise<0 | 1>;
10
+ export declare function inspectPackageFiles(targets: Array<PublishTarget>): {
11
+ target: PublishTarget;
12
+ field: string;
13
+ file: string;
14
+ status: PackFileCheckStatus;
15
+ }[];
16
+ export declare class PackInspectReporter {
17
+ private readonly cwd;
18
+ private readonly targets;
19
+ private readonly checks;
20
+ private readonly title;
21
+ private readonly logger;
22
+ constructor(cwd: string, targets: Array<PublishTarget>, checks: Array<PackFileCheck>, title?: string);
23
+ report(): void;
24
+ private writeSectionTitle;
25
+ private formatStatus;
26
+ private formatValue;
27
+ private formatResultTitle;
28
+ }