auklet 0.1.0 → 0.1.2

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.
@@ -0,0 +1,221 @@
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
+ token: options.token,
26
+ onCheck: (info) => {
27
+ spinner.text([
28
+ 'Checking registry ',
29
+ info.check,
30
+ ' for ',
31
+ logger.package(info.packageName),
32
+ ]);
33
+ },
34
+ onRetry: (info) => {
35
+ spinner.text([
36
+ 'Checking registry ',
37
+ info.check,
38
+ ' for ',
39
+ logger.package(info.packageName),
40
+ ', retrying ',
41
+ logger.value(`${info.attempt}/${info.maxAttempts}`),
42
+ ]);
43
+ },
44
+ });
45
+ if (registryChecks.some((check) => check.reason)) {
46
+ spinner.fail('Publish registry checks failed');
47
+ }
48
+ else {
49
+ spinner.succeed('Publish registry checks passed');
50
+ }
51
+ new PublishInspectReporter(options, plan, packageFileChecks, registryChecks).report();
52
+ if (registryChecks.some((check) => check.reason)) {
53
+ return 1;
54
+ }
55
+ return 0;
56
+ }
57
+ class PublishInspectReporter {
58
+ options;
59
+ plan;
60
+ packageFileChecks;
61
+ registryChecks;
62
+ logger = createAukletLogger();
63
+ constructor(options, plan, packageFileChecks, registryChecks) {
64
+ this.options = options;
65
+ this.plan = plan;
66
+ this.packageFileChecks = packageFileChecks;
67
+ this.registryChecks = registryChecks;
68
+ }
69
+ report() {
70
+ const model = createPublishInspectModel(this.options, this.plan, this.packageFileChecks, this.registryChecks);
71
+ new PackInspectReporter(this.options.cwd, this.plan.targets, model.packageFileChecks, 'Package file checks').report();
72
+ this.logger.newline();
73
+ this.logger.result({
74
+ title: this.formatResultTitle('Publish inspect', model.registryChecks.some((check) => check.reason)),
75
+ status: 'info',
76
+ body: [
77
+ 'Read-only publish plan. No files, git refs, or packages changed.',
78
+ ],
79
+ details: this.formatDetails(model),
80
+ });
81
+ this.logger.newline();
82
+ this.writeSectionTitle('Targets');
83
+ this.logger.rows({
84
+ columns: ['package', 'version', 'registry', 'access'],
85
+ rows: model.targets.map((target) => [
86
+ this.logger.package(target.packageName),
87
+ this.formatVersionChange(target.version, target.publishVersion),
88
+ this.logger.colors.gray(target.registry),
89
+ this.logger.colors.gray(target.access),
90
+ ]),
91
+ });
92
+ this.logger.newline();
93
+ this.writeSectionTitle('Registry checks');
94
+ this.logger.rows({
95
+ columns: ['package', 'registry', 'auth', 'version'],
96
+ rows: model.registryChecks.map((check) => [
97
+ this.logger.package(check.packageName),
98
+ this.logger.colors.gray(check.registry),
99
+ this.formatCheckStatus(check.auth),
100
+ this.formatCheckStatus(check.version),
101
+ ]),
102
+ });
103
+ const failedChecks = model.registryChecks.filter((check) => check.reason);
104
+ if (failedChecks.length) {
105
+ this.logger.newline();
106
+ this.writeSectionTitle('Registry issues', 'error');
107
+ this.writeRegistryIssues(failedChecks);
108
+ }
109
+ this.logger.newline();
110
+ this.writeSectionTitle('Hooks');
111
+ this.logger.rows({
112
+ columns: ['hook', 'configured'],
113
+ rows: model.hooks.map((hook) => [
114
+ this.logger.colors.gray(hook.name),
115
+ this.formatValue(hook.configured ? 'yes' : 'no'),
116
+ ]),
117
+ });
118
+ this.logger.newline();
119
+ }
120
+ formatDetails(model) {
121
+ return {
122
+ mode: this.formatValue(model.details.mode),
123
+ publish: this.formatValue(model.details.publish),
124
+ packages: this.formatValue(model.details.packages),
125
+ version: this.logger.version(model.details.version),
126
+ tag: this.formatValue(model.details.tag),
127
+ git: this.formatValue(model.details.git),
128
+ format: this.formatValue(model.details.format),
129
+ root: this.logger.path(model.details.root),
130
+ };
131
+ }
132
+ formatVersionChange(from, to) {
133
+ return [
134
+ this.logger.version(from),
135
+ this.logger.colors.gray(' -> '),
136
+ this.logger.version(to),
137
+ ];
138
+ }
139
+ formatValue(value) {
140
+ return this.logger.colors.bold(this.logger.colors.cyan(String(value)));
141
+ }
142
+ formatResultTitle(title, failed) {
143
+ return this.logger.colors.bold(failed ? this.logger.colors.red(title) : this.logger.colors.green(title));
144
+ }
145
+ formatCheckStatus(status) {
146
+ if (status === 'success')
147
+ return this.logger.colors.green('ok');
148
+ return this.logger.colors.red('failed');
149
+ }
150
+ writeSectionTitle(title, status = 'normal') {
151
+ const content = status === 'error' ? this.logger.colors.red(title) : title;
152
+ this.logger.raw(content);
153
+ }
154
+ writeRegistryIssues(checks) {
155
+ for (const [index, check] of checks.entries()) {
156
+ if (index > 0)
157
+ this.logger.newline();
158
+ const [firstLine, ...restLines] = String(check.reason ?? '').split('\n');
159
+ this.logger.item(this.logger.package(check.packageName), ': ', this.logger.colors.red(firstLine));
160
+ for (const line of restLines) {
161
+ this.logger.raw(` ${this.logger.colors.red(line)}`);
162
+ }
163
+ }
164
+ }
165
+ }
166
+ export function createPublishInspectModel(options, plan, packageFileChecks = [], registryChecks = []) {
167
+ return {
168
+ details: {
169
+ mode: plan.workspaceMode,
170
+ publish: plan.dryRun ? 'dry-run' : 'real',
171
+ packages: plan.targets.length,
172
+ version: plan.version,
173
+ tag: resolvePublishTag(options.version) ?? 'latest',
174
+ git: getGitMode(options),
175
+ format: options.format ? 'enabled' : 'disabled',
176
+ root: path.relative(options.cwd, plan.root) || '.',
177
+ },
178
+ packageFileChecks,
179
+ targets: plan.targets.map((target) => ({
180
+ packageName: target.packageName,
181
+ version: target.version,
182
+ publishVersion: target.publishVersion,
183
+ registry: getRegistry(target.packageJson),
184
+ access: getAccess(target),
185
+ })),
186
+ registryChecks,
187
+ hooks: getHookRows(plan.config),
188
+ };
189
+ }
190
+ const getGitMode = (options) => {
191
+ if (options.dryRun)
192
+ return 'skipped (dry-run)';
193
+ if (options.allowDirty)
194
+ return 'skipped (--allow-dirty)';
195
+ if (options.git === false)
196
+ return 'skipped (--no-git)';
197
+ return 'commit + tag';
198
+ };
199
+ const getHookRows = (config) => {
200
+ const hooks = [
201
+ ['beforeBuild', config.beforeBuild],
202
+ ['afterBuild', config.afterBuild],
203
+ ['beforePublish', config.beforePublish],
204
+ ['afterPublish', config.afterPublish],
205
+ ];
206
+ return hooks.map(([name, value]) => ({
207
+ name,
208
+ configured: Boolean(value),
209
+ }));
210
+ };
211
+ const getRegistry = (packageJson) => {
212
+ return getPublishRegistry(packageJson.publishConfig) ?? 'default';
213
+ };
214
+ const getAccess = (target) => {
215
+ const access = target.packageJson.publishConfig?.access;
216
+ if (isString(access) && access)
217
+ return access;
218
+ if (target.packageName.startsWith('@') && !target.private)
219
+ return 'public';
220
+ return 'default';
221
+ };
@@ -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
+ }
@@ -0,0 +1,206 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { isArray, isPlainObject, isString } from 'aidly';
4
+ import { createAukletLogger } from '#auklet/logger';
5
+ import { resolvePublishPlan } from '#auklet/publish/targetResolver';
6
+ export async function runInspectPackCli(args) {
7
+ const options = resolveInspectPackOptions(args);
8
+ const logger = createAukletLogger({ scope: 'inspect' });
9
+ const plan = await resolvePublishPlan({
10
+ cwd: options.cwd,
11
+ filters: options.filters,
12
+ dryRun: true,
13
+ version: undefined,
14
+ }, logger);
15
+ const checks = inspectPackageFiles(plan.targets);
16
+ new PackInspectReporter(options.cwd, plan.targets, checks).report();
17
+ return checks.some((check) => check.status === 'missing') ? 1 : 0;
18
+ }
19
+ const resolveInspectPackOptions = (args) => {
20
+ const filters = [];
21
+ const cliArgs = args.filter((arg) => arg !== '--');
22
+ for (let index = 0; index < cliArgs.length; index += 1) {
23
+ const arg = cliArgs[index];
24
+ if (arg === '--filter') {
25
+ const value = cliArgs[index + 1];
26
+ if (!value)
27
+ throw new Error('[inspect] --filter requires a value.');
28
+ filters.push(value);
29
+ index += 1;
30
+ continue;
31
+ }
32
+ if (arg.startsWith('--filter=')) {
33
+ filters.push(arg.slice('--filter='.length));
34
+ continue;
35
+ }
36
+ throw new Error(`[inspect] unknown inspect pack argument: ${arg}`);
37
+ }
38
+ return {
39
+ cwd: process.cwd(),
40
+ filters,
41
+ };
42
+ };
43
+ export function inspectPackageFiles(targets) {
44
+ return targets.flatMap((target) => inspectTargetPackage(target));
45
+ }
46
+ const inspectTargetPackage = (target) => {
47
+ return collectPackageFileReferences(target.packageJson).map((reference) => ({
48
+ target,
49
+ field: reference.field,
50
+ file: reference.file,
51
+ status: getFileStatus(target.packageRoot, reference.file),
52
+ }));
53
+ };
54
+ const collectPackageFileReferences = (packageJson) => {
55
+ const references = [];
56
+ addStringField(references, packageJson, 'main');
57
+ addStringField(references, packageJson, 'module');
58
+ addStringField(references, packageJson, 'types');
59
+ addStringField(references, packageJson, 'typings');
60
+ addStringField(references, packageJson, 'style');
61
+ addStringField(references, packageJson, 'stylesheet');
62
+ addBinFields(references, packageJson.bin);
63
+ addExportFields(references, packageJson.exports, 'exports');
64
+ addFilesFields(references, packageJson.files);
65
+ return references;
66
+ };
67
+ const addStringField = (references, packageJson, field) => {
68
+ const value = Reflect.get(packageJson, field);
69
+ if (isString(value))
70
+ references.push({ field, file: value });
71
+ };
72
+ const addBinFields = (references, value) => {
73
+ if (isString(value)) {
74
+ references.push({ field: 'bin', file: value });
75
+ return;
76
+ }
77
+ if (!isPlainObject(value))
78
+ return;
79
+ for (const [name, file] of Object.entries(value)) {
80
+ if (isString(file))
81
+ references.push({ field: `bin.${name}`, file });
82
+ }
83
+ };
84
+ const addExportFields = (references, value, field) => {
85
+ if (isString(value)) {
86
+ references.push({ field, file: value });
87
+ return;
88
+ }
89
+ if (isArray(value)) {
90
+ value.forEach((item, index) => {
91
+ addExportFields(references, item, `${field}[${index}]`);
92
+ });
93
+ return;
94
+ }
95
+ if (!isPlainObject(value))
96
+ return;
97
+ for (const [key, item] of Object.entries(value)) {
98
+ addExportFields(references, item, formatFieldKey(field, key));
99
+ }
100
+ };
101
+ const addFilesFields = (references, value) => {
102
+ if (!isArray(value))
103
+ return;
104
+ value.forEach((file, index) => {
105
+ if (isString(file))
106
+ references.push({ field: `files[${index}]`, file });
107
+ });
108
+ };
109
+ const getFileStatus = (packageRoot, file) => {
110
+ if (file.startsWith('!'))
111
+ return 'skipped';
112
+ if (file.startsWith('#') || file.includes('://'))
113
+ return 'skipped';
114
+ if (file.includes('*'))
115
+ return 'pattern';
116
+ const absolutePath = path.resolve(packageRoot, file);
117
+ const relativePath = path.relative(packageRoot, absolutePath);
118
+ if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
119
+ return 'skipped';
120
+ }
121
+ return fs.existsSync(absolutePath) ? 'exists' : 'missing';
122
+ };
123
+ const formatFieldKey = (field, key) => {
124
+ return /^[a-zA-Z_$][\w$-]*$/.test(key)
125
+ ? `${field}.${key}`
126
+ : `${field}[${JSON.stringify(key)}]`;
127
+ };
128
+ export class PackInspectReporter {
129
+ cwd;
130
+ targets;
131
+ checks;
132
+ title;
133
+ logger = createAukletLogger();
134
+ constructor(cwd, targets, checks, title = 'Pack inspect') {
135
+ this.cwd = cwd;
136
+ this.targets = targets;
137
+ this.checks = checks;
138
+ this.title = title;
139
+ }
140
+ report() {
141
+ const missingChecks = this.checks.filter((check) => check.status === 'missing');
142
+ this.logger.newline();
143
+ this.logger.result({
144
+ title: this.formatResultTitle(this.title, missingChecks.length > 0),
145
+ status: missingChecks.length ? 'error' : 'info',
146
+ body: ['Read-only package file checks. No files or packages changed.'],
147
+ details: {
148
+ packages: this.formatValue(this.targets.length),
149
+ checks: this.formatValue(this.checks.length),
150
+ missing: missingChecks.length
151
+ ? this.logger.colors.bold(this.logger.colors.red(missingChecks.length))
152
+ : this.formatValue(0),
153
+ },
154
+ });
155
+ this.logger.newline();
156
+ this.writeSectionTitle('Package files');
157
+ this.logger.rows({
158
+ columns: ['package', 'field', 'file', 'status'],
159
+ rows: this.checks.map((check) => [
160
+ this.logger.package(check.target.packageName),
161
+ this.logger.colors.gray(check.field),
162
+ this.logger.path(check.file),
163
+ this.formatStatus(check.status),
164
+ ]),
165
+ empty: this.logger.colors.gray('No package file references found.'),
166
+ });
167
+ this.logger.newline();
168
+ this.writeSectionTitle('Package roots');
169
+ this.logger.rows({
170
+ columns: ['package', 'root'],
171
+ rows: this.targets.map((target) => [
172
+ this.logger.package(target.packageName),
173
+ this.logger.path(path.relative(this.cwd, target.packageRoot) || '.'),
174
+ ]),
175
+ });
176
+ if (missingChecks.length) {
177
+ this.logger.newline();
178
+ this.writeSectionTitle('Missing files', 'error');
179
+ for (const [index, check] of missingChecks.entries()) {
180
+ if (index > 0)
181
+ this.logger.newline();
182
+ this.logger.item(this.logger.package(check.target.packageName), ': ', this.logger.colors.red(check.field), ' -> ', this.logger.colors.red(check.file));
183
+ }
184
+ }
185
+ this.logger.newline();
186
+ }
187
+ writeSectionTitle(title, status = 'normal') {
188
+ const content = status === 'error' ? this.logger.colors.red(title) : title;
189
+ this.logger.raw(content);
190
+ }
191
+ formatStatus(status) {
192
+ if (status === 'exists')
193
+ return this.logger.colors.green('exists');
194
+ if (status === 'missing')
195
+ return this.logger.colors.red('missing');
196
+ if (status === 'pattern')
197
+ return this.logger.colors.yellow('pattern');
198
+ return this.logger.colors.gray('skipped');
199
+ }
200
+ formatValue(value) {
201
+ return this.logger.colors.bold(this.logger.colors.cyan(String(value)));
202
+ }
203
+ formatResultTitle(title, failed) {
204
+ return this.logger.colors.bold(failed ? this.logger.colors.red(title) : this.logger.colors.green(title));
205
+ }
206
+ }
@@ -0,0 +1,24 @@
1
+ import type { PublishOptions, PublishPlan } from '#auklet/publish/types';
2
+ export type PublishRegistryCheckStatus = 'success' | 'error';
3
+ export type PublishRegistryCheck = {
4
+ packageName: string;
5
+ registry: string;
6
+ auth: PublishRegistryCheckStatus;
7
+ version: PublishRegistryCheckStatus;
8
+ reason: string | null;
9
+ };
10
+ export type PublishRegistryRetryInfo = {
11
+ packageName: string;
12
+ registry: string;
13
+ check: 'auth' | 'version';
14
+ attempt: number;
15
+ maxAttempts: number;
16
+ error: Error;
17
+ };
18
+ export type PublishRegistryCheckInfo = Pick<PublishRegistryRetryInfo, 'packageName' | 'registry' | 'check'>;
19
+ export type InspectPublishRegistryOptions = {
20
+ token?: PublishOptions['token'];
21
+ onCheck?: (info: PublishRegistryCheckInfo) => void;
22
+ onRetry?: (info: PublishRegistryRetryInfo) => void;
23
+ };
24
+ export declare function inspectPublishRegistry(plan: PublishPlan, options?: InspectPublishRegistryOptions): Promise<PublishRegistryCheck[]>;
@@ -0,0 +1,109 @@
1
+ import { retry } from 'aidly';
2
+ import { getPublishRegistry } from '#auklet/publish/api/registry';
3
+ import { hasPublishedPackageVersion, runPnpmWhoami, } from '#auklet/publish/api/pnpmApi';
4
+ const registryCheckTimeout = 5_000;
5
+ const registryCheckRetryTimes = 2;
6
+ export async function inspectPublishRegistry(plan, options = {}) {
7
+ const authResults = new Map();
8
+ const checks = [];
9
+ for (const target of plan.targets) {
10
+ const registry = getPublishRegistry(target.packageJson.publishConfig);
11
+ const registryLabel = registry ?? 'default';
12
+ const authKey = `${target.packageRoot}\n${registryLabel}`;
13
+ if (!authResults.has(authKey)) {
14
+ await checkAuth(authResults, authKey, target.packageRoot, target.packageName, registry, options);
15
+ }
16
+ const authError = authResults.get(authKey) ?? null;
17
+ const versionError = await checkVersion(target.packageRoot, target.packageName, target.publishVersion, registry, options);
18
+ checks.push({
19
+ packageName: target.packageName,
20
+ registry: registryLabel,
21
+ auth: authError ? 'error' : 'success',
22
+ version: versionError ? 'error' : 'success',
23
+ reason: getReason(authError, versionError),
24
+ });
25
+ }
26
+ return checks;
27
+ }
28
+ const checkAuth = async (authResults, authKey, packageRoot, packageName, registry, options) => {
29
+ try {
30
+ options.onCheck?.({
31
+ packageName,
32
+ registry: registry ?? 'default',
33
+ check: 'auth',
34
+ });
35
+ await retryWithLog(() => runPnpmWhoami(packageRoot, {
36
+ packageName,
37
+ registry,
38
+ token: options.token,
39
+ timeout: registryCheckTimeout,
40
+ }), {
41
+ check: 'auth',
42
+ packageName,
43
+ registry,
44
+ onRetry: options.onRetry,
45
+ });
46
+ authResults.set(authKey, null);
47
+ return null;
48
+ }
49
+ catch (error) {
50
+ const authError = toError(error);
51
+ authResults.set(authKey, authError);
52
+ return authError;
53
+ }
54
+ };
55
+ const checkVersion = async (packageRoot, packageName, version, registry, options) => {
56
+ try {
57
+ options.onCheck?.({
58
+ packageName,
59
+ registry: registry ?? 'default',
60
+ check: 'version',
61
+ });
62
+ const exists = await retryWithLog(() => hasPublishedPackageVersion(packageRoot, packageName, version, {
63
+ registry,
64
+ token: options.token,
65
+ timeout: registryCheckTimeout,
66
+ }), {
67
+ check: 'version',
68
+ packageName,
69
+ registry,
70
+ onRetry: options.onRetry,
71
+ });
72
+ return exists
73
+ ? new Error(`version already exists: ${packageName}@${version}`)
74
+ : null;
75
+ }
76
+ catch (error) {
77
+ return toError(error);
78
+ }
79
+ };
80
+ const getReason = (authError, versionError) => {
81
+ return authError?.message ?? versionError?.message ?? null;
82
+ };
83
+ const toError = (error) => {
84
+ return error instanceof Error ? error : new Error(String(error));
85
+ };
86
+ const retryWithLog = (fn, options) => {
87
+ let attempt = 0;
88
+ let previousError = null;
89
+ return retry(async () => {
90
+ attempt += 1;
91
+ if (attempt > 1) {
92
+ options.onRetry?.({
93
+ packageName: options.packageName,
94
+ registry: options.registry ?? 'default',
95
+ check: options.check,
96
+ attempt: attempt - 1,
97
+ maxAttempts: registryCheckRetryTimes,
98
+ error: previousError ?? new Error('publish registry check failed.'),
99
+ });
100
+ }
101
+ try {
102
+ return await fn();
103
+ }
104
+ catch (error) {
105
+ previousError = toError(error);
106
+ throw error;
107
+ }
108
+ }, registryCheckRetryTimes);
109
+ };
@@ -7,6 +7,7 @@ export declare class PublishPreflight {
7
7
  constructor(options: PublishOptions, logger: AukletLogger);
8
8
  run(plan: PublishPlan): Promise<void>;
9
9
  verifyBeforeBuild(plan: PublishPlan): Promise<void>;
10
+ private verifyTokenConfig;
10
11
  private verifyAuthentication;
11
12
  private verifyPnpmPublishDryRun;
12
13
  private verifyPackageVersions;
@@ -1,6 +1,7 @@
1
- import { isPlainObject, isString } from 'aidly';
2
1
  import { PnpmPublishApi } from '#auklet/publish/api/pnpmPublishApi';
3
2
  import { hasPublishedPackageVersion, NpmPackageVersionExistsError, runPnpmWhoami, } from '#auklet/publish/api/pnpmApi';
3
+ import { findNpmrcFiles, findNpmrcWithAuthToken, toNpmrcRegistryKey, } from '#auklet/publish/api/npmrc';
4
+ import { getPublishRegistry } from '#auklet/publish/api/registry';
4
5
  import { logAuthenticationError } from '#auklet/publish/runner/packagePublisher';
5
6
  import { PublishTargetError } from '#auklet/publish/runner/publishTargetError';
6
7
  export class PublishPreflight {
@@ -15,11 +16,23 @@ export class PublishPreflight {
15
16
  await this.verifyPnpmPublishDryRun(plan);
16
17
  }
17
18
  async verifyBeforeBuild(plan) {
19
+ this.verifyTokenConfig(plan);
18
20
  if (plan.dryRun)
19
21
  return;
20
22
  await this.verifyAuthentication(plan);
21
23
  await this.verifyPackageVersions(plan);
22
24
  }
25
+ verifyTokenConfig(plan) {
26
+ if (!this.options.token)
27
+ return;
28
+ for (const target of plan.targets) {
29
+ const registry = getPublishRegistry(target.packageJson.publishConfig);
30
+ const npmrc = findNpmrcWithAuthToken(target.packageRoot, plan.root, registry);
31
+ if (npmrc)
32
+ continue;
33
+ throw new PublishTargetError(target, 'preflight', new Error(createMissingNpmrcAuthMessage(target, plan.root, registry)), []);
34
+ }
35
+ }
23
36
  async verifyAuthentication(plan) {
24
37
  const checked = new Set();
25
38
  for (const target of plan.targets) {
@@ -30,6 +43,7 @@ export class PublishPreflight {
30
43
  await runPnpmWhoami(target.packageRoot, {
31
44
  packageName: target.packageName,
32
45
  registry,
46
+ token: this.options.token,
33
47
  });
34
48
  checked.add(key);
35
49
  }
@@ -52,7 +66,7 @@ export class PublishPreflight {
52
66
  async verifyPackageVersions(plan) {
53
67
  for (const target of plan.targets) {
54
68
  const registry = getPublishRegistry(target.packageJson.publishConfig);
55
- const exists = await hasPublishedPackageVersion(target.packageRoot, target.packageName, target.publishVersion, { registry });
69
+ const exists = await hasPublishedPackageVersion(target.packageRoot, target.packageName, target.publishVersion, { registry, token: this.options.token });
56
70
  if (!exists)
57
71
  continue;
58
72
  this.logExistingVersion(target, registry);
@@ -66,9 +80,20 @@ export class PublishPreflight {
66
80
  }
67
81
  }
68
82
  }
69
- const getPublishRegistry = (publishConfig) => {
70
- if (!isPlainObject(publishConfig))
71
- return undefined;
72
- const registry = Reflect.get(publishConfig, 'registry');
73
- return isString(registry) && registry.length > 0 ? registry : undefined;
74
- };
83
+ function createMissingNpmrcAuthMessage(target, root, registry) {
84
+ const npmrcFiles = findNpmrcFiles(target.packageRoot, root);
85
+ const authTarget = registry
86
+ ? `${toNpmrcRegistryKey(registry)}:_authToken`
87
+ : '_authToken';
88
+ const registryHint = registry
89
+ ? `[publish] ${target.packageName} uses publishConfig.registry: ${registry}.\n`
90
+ : '';
91
+ const location = npmrcFiles.length
92
+ ? 'found npmrc files, but none declares'
93
+ : 'could not find an npmrc file declaring';
94
+ return (`[publish] --token requires npmrc auth config for ${target.packageName}.\n` +
95
+ registryHint +
96
+ `[publish] ${location} ${authTarget}.\n` +
97
+ '[publish] Add an npmrc entry such as:\n' +
98
+ ` ${authTarget}=\${NODE_AUTH_TOKEN}`);
99
+ }
@@ -50,6 +50,7 @@ export type PublishOptions = {
50
50
  filters: Array<string>;
51
51
  git?: boolean;
52
52
  otp?: string;
53
+ token?: string;
53
54
  version?: string;
54
55
  };
55
56
  export type OwnerOptions = {