@jsdevtools/npm-publish 1.4.3 → 2.1.0

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.
Files changed (100) hide show
  1. package/README.md +174 -147
  2. package/bin/npm-publish.js +10 -2
  3. package/lib/action/core.d.ts +45 -0
  4. package/lib/action/core.js +59 -0
  5. package/lib/action/core.js.map +1 -0
  6. package/lib/action/main.js +55 -0
  7. package/lib/action/main.js.map +1 -0
  8. package/lib/cli/index.d.ts +4 -2
  9. package/lib/cli/index.js +60 -53
  10. package/lib/cli/index.js.map +1 -1
  11. package/lib/cli/parse-cli-arguments.d.ts +17 -0
  12. package/lib/cli/parse-cli-arguments.js +40 -0
  13. package/lib/cli/parse-cli-arguments.js.map +1 -0
  14. package/lib/compare-and-publish/compare-and-publish.d.ts +21 -0
  15. package/lib/compare-and-publish/compare-and-publish.js +49 -0
  16. package/lib/compare-and-publish/compare-and-publish.js.map +1 -0
  17. package/lib/compare-and-publish/compare-versions.d.ts +16 -0
  18. package/lib/compare-and-publish/compare-versions.js +41 -0
  19. package/lib/compare-and-publish/compare-versions.js.map +1 -0
  20. package/lib/compare-and-publish/get-arguments.d.ts +21 -0
  21. package/lib/compare-and-publish/get-arguments.js +50 -0
  22. package/lib/compare-and-publish/get-arguments.js.map +1 -0
  23. package/lib/compare-and-publish/index.d.ts +1 -0
  24. package/lib/compare-and-publish/index.js +18 -0
  25. package/lib/compare-and-publish/index.js.map +1 -0
  26. package/lib/errors.d.ts +39 -0
  27. package/lib/errors.js +121 -0
  28. package/lib/errors.js.map +1 -0
  29. package/lib/format-publish-result.d.ts +12 -0
  30. package/lib/format-publish-result.js +36 -0
  31. package/lib/format-publish-result.js.map +1 -0
  32. package/lib/index.d.ts +4 -5
  33. package/lib/index.js +11 -10
  34. package/lib/index.js.map +1 -1
  35. package/lib/normalize-options.d.ts +28 -1
  36. package/lib/normalize-options.js +89 -14
  37. package/lib/normalize-options.js.map +1 -1
  38. package/lib/npm/call-npm-cli.d.ts +38 -0
  39. package/lib/npm/call-npm-cli.js +113 -0
  40. package/lib/npm/call-npm-cli.js.map +1 -0
  41. package/lib/npm/index.d.ts +2 -0
  42. package/lib/npm/index.js +19 -0
  43. package/lib/npm/index.js.map +1 -0
  44. package/lib/npm/use-npm-environment.d.ts +15 -0
  45. package/lib/npm/use-npm-environment.js +44 -0
  46. package/lib/npm/use-npm-environment.js.map +1 -0
  47. package/lib/npm-publish.d.ts +7 -4
  48. package/lib/npm-publish.js +25 -26
  49. package/lib/npm-publish.js.map +1 -1
  50. package/lib/options.d.ts +67 -44
  51. package/lib/options.js +5 -0
  52. package/lib/options.js.map +1 -1
  53. package/lib/read-manifest.d.ts +22 -1
  54. package/lib/read-manifest.js +122 -22
  55. package/lib/read-manifest.js.map +1 -1
  56. package/lib/results.d.ts +30 -28
  57. package/lib/results.js +3 -0
  58. package/lib/results.js.map +1 -1
  59. package/package.json +47 -35
  60. package/src/action/core.ts +91 -0
  61. package/src/action/main.ts +32 -0
  62. package/src/cli/index.ts +72 -0
  63. package/src/cli/parse-cli-arguments.ts +49 -0
  64. package/src/compare-and-publish/compare-and-publish.ts +75 -0
  65. package/src/compare-and-publish/compare-versions.ts +48 -0
  66. package/src/compare-and-publish/get-arguments.ts +61 -0
  67. package/src/compare-and-publish/index.ts +1 -0
  68. package/src/errors.ts +137 -0
  69. package/src/format-publish-result.ts +40 -0
  70. package/src/index.ts +7 -0
  71. package/src/normalize-options.ts +123 -0
  72. package/src/npm/call-npm-cli.ts +140 -0
  73. package/src/npm/index.ts +2 -0
  74. package/src/npm/use-npm-environment.ts +57 -0
  75. package/src/npm-publish.ts +40 -0
  76. package/src/options.ts +108 -0
  77. package/src/read-manifest.ts +142 -0
  78. package/src/results.ts +50 -0
  79. package/CHANGELOG.md +0 -49
  80. package/lib/action/index.js +0 -67
  81. package/lib/action/index.js.map +0 -1
  82. package/lib/cli/exit-code.d.ts +0 -1
  83. package/lib/cli/exit-code.js +0 -16
  84. package/lib/cli/exit-code.js.map +0 -1
  85. package/lib/cli/help.d.ts +0 -1
  86. package/lib/cli/help.js +0 -28
  87. package/lib/cli/help.js.map +0 -1
  88. package/lib/cli/parse-args.d.ts +0 -1
  89. package/lib/cli/parse-args.js +0 -58
  90. package/lib/cli/parse-args.js.map +0 -1
  91. package/lib/npm-config.d.ts +0 -1
  92. package/lib/npm-config.js +0 -85
  93. package/lib/npm-config.js.map +0 -1
  94. package/lib/npm-env.d.ts +0 -6
  95. package/lib/npm-env.js +0 -24
  96. package/lib/npm-env.js.map +0 -1
  97. package/lib/npm.d.ts +0 -1
  98. package/lib/npm.js +0 -95
  99. package/lib/npm.js.map +0 -1
  100. /package/lib/action/{index.d.ts → main.d.ts} +0 -0
@@ -0,0 +1,32 @@
1
+ /** Action entry point */
2
+ import { npmPublish } from "../index.js";
3
+ import * as core from "./core.js";
4
+
5
+ /** Run the action. */
6
+ async function run(): Promise<void> {
7
+ const results = await npmPublish({
8
+ token: core.getRequiredSecretInput("token"),
9
+ registry: core.getInput("registry"),
10
+ package: core.getInput("package"),
11
+ tag: core.getInput("tag"),
12
+ access: core.getInput("access"),
13
+ provenance: core.getBooleanInput("provenance"),
14
+ strategy: core.getInput("strategy"),
15
+ dryRun: core.getBooleanInput("dry-run"),
16
+ logger: core.logger,
17
+ temporaryDirectory: process.env["RUNNER_TEMP"],
18
+ });
19
+
20
+ core.setOutput("id", results.id, "");
21
+ core.setOutput("name", results.name);
22
+ core.setOutput("version", results.version);
23
+ core.setOutput("type", results.type, "");
24
+ core.setOutput("old-version", results.oldVersion, "");
25
+ core.setOutput("registry", results.registry.href);
26
+ core.setOutput("tag", results.tag);
27
+ core.setOutput("access", results.access, "default");
28
+ core.setOutput("strategy", results.strategy);
29
+ core.setOutput("dry-run", results.dryRun);
30
+ }
31
+
32
+ run().catch((error: Error) => core.setFailed(error));
@@ -0,0 +1,72 @@
1
+ import { npmPublish, type Logger } from "../index.js";
2
+ import { parseCliArguments } from "./parse-cli-arguments.js";
3
+
4
+ export const USAGE = `
5
+ Usage:
6
+
7
+ npm-publish <options> [package]
8
+
9
+ Arguments:
10
+
11
+ package The path to the package to publish.
12
+ May be a directory, package.json, or .tgz file.
13
+ Defaults to the package in the current directory.
14
+
15
+ Options:
16
+
17
+ --token <token> (Required) npm authentication token.
18
+
19
+ --registry <url> Registry to read from and write to.
20
+ Defaults to "https://registry.npmjs.org/".
21
+
22
+ --tag <tag> The distribution tag to check against and publish to.
23
+ Defaults to "latest".
24
+
25
+ --access <access> Package access, may be "public" or "restricted".
26
+ See npm documentation for details.
27
+
28
+ --provenance Publish with provenance statements.
29
+ See npm documentation for details.
30
+
31
+ --strategy <strategy> Publish strategy, may be "all" or "upgrade".
32
+ Defaults to "all", see documentation for details.
33
+
34
+ --dry-run Do not actually publish anything.
35
+ --quiet Only print errors.
36
+ --debug Print debug logs.
37
+
38
+ -v, --version Print the version number.
39
+ -h, --help Show usage text.
40
+
41
+ Examples:
42
+
43
+ $ npm-publish --token abc123 ./my-package
44
+ `;
45
+
46
+ /**
47
+ * The main entry point of the CLI
48
+ *
49
+ * @param argv - The list of argument strings passed to the program.
50
+ * @param version - The version of this program.
51
+ */
52
+ export async function main(argv: string[], version: string): Promise<void> {
53
+ const cliArguments = parseCliArguments(argv);
54
+
55
+ if (cliArguments.help) {
56
+ console.info(USAGE);
57
+ return;
58
+ }
59
+
60
+ if (cliArguments.version) {
61
+ console.info(version);
62
+ return;
63
+ }
64
+
65
+ const logger: Logger = {
66
+ error: console.error,
67
+ info: cliArguments.quiet === false ? console.info : undefined,
68
+ debug: cliArguments.debug === true ? console.debug : undefined,
69
+ };
70
+
71
+ await npmPublish({ ...cliArguments.options, logger });
72
+ }
@@ -0,0 +1,49 @@
1
+ /** Wrapper module for command-line-args */
2
+
3
+ import commandLineArgs from "command-line-args";
4
+ import type { Options } from "../options.js";
5
+
6
+ const ARGUMENTS_OPTIONS = [
7
+ { name: "package", type: String, defaultOption: true },
8
+ { name: "token", type: String },
9
+ { name: "registry", type: String },
10
+ { name: "tag", type: String },
11
+ { name: "access", type: String },
12
+ { name: "provenance", type: Boolean },
13
+ { name: "strategy", type: String },
14
+ { name: "dry-run", type: Boolean },
15
+ { name: "quiet", type: Boolean },
16
+ { name: "debug", type: Boolean },
17
+ { name: "version", alias: "v", type: Boolean },
18
+ { name: "help", alias: "h", type: Boolean },
19
+ ];
20
+
21
+ /** The parsed command-line arguments */
22
+ export interface ParsedArguments {
23
+ help?: boolean;
24
+ version?: boolean;
25
+ quiet?: boolean;
26
+ debug?: boolean;
27
+ options: Options;
28
+ }
29
+
30
+ /**
31
+ * Parse the given command-line arguments.
32
+ *
33
+ * @param argv The list of argument strings passed to the program.
34
+ * @returns A parsed object of options.
35
+ */
36
+ export function parseCliArguments(argv: string[]): ParsedArguments {
37
+ const { help, version, quiet, debug, ...options } = commandLineArgs(
38
+ ARGUMENTS_OPTIONS,
39
+ { argv, camelCase: true }
40
+ );
41
+
42
+ return {
43
+ help: Boolean(help),
44
+ version: Boolean(version),
45
+ quiet: Boolean(quiet),
46
+ debug: Boolean(debug),
47
+ options: options as Options,
48
+ };
49
+ }
@@ -0,0 +1,75 @@
1
+ import type { PackageManifest } from "../read-manifest.js";
2
+ import type { NormalizedOptions } from "../normalize-options.js";
3
+ import {
4
+ VIEW,
5
+ PUBLISH,
6
+ E404,
7
+ EPUBLISHCONFLICT,
8
+ callNpmCli,
9
+ type NpmCliEnvironment,
10
+ } from "../npm/index.js";
11
+ import { compareVersions, type VersionComparison } from "./compare-versions.js";
12
+ import { getViewArguments, getPublishArguments } from "./get-arguments.js";
13
+
14
+ export interface PublishResult extends VersionComparison {
15
+ id: string | undefined;
16
+ files: PublishFile[];
17
+ }
18
+
19
+ export interface PublishFile {
20
+ path: string;
21
+ size: number;
22
+ }
23
+
24
+ /**
25
+ * Get the currently published versions of a package and publish if needed.
26
+ *
27
+ * @param manifest The package to potentially publish.
28
+ * @param options Configuration options.
29
+ * @param environment Environment variables for the npm cli.
30
+ * @returns Information about the publish, including if it occurred.
31
+ */
32
+ export async function compareAndPublish(
33
+ manifest: PackageManifest,
34
+ options: NormalizedOptions,
35
+ environment: NpmCliEnvironment
36
+ ): Promise<PublishResult> {
37
+ const { name, version, packageSpec } = manifest;
38
+ const cliOptions = { environment, logger: options.logger };
39
+
40
+ const viewArguments = getViewArguments(name, options);
41
+ const publishArguments = getPublishArguments(packageSpec, options);
42
+ let viewCall = await callNpmCli(VIEW, viewArguments, cliOptions);
43
+
44
+ // `npm view` will succeed with no output the package exists in the registry
45
+ // with no `latest` tag. This is only possible with third-party registries.
46
+ // https://github.com/npm/cli/issues/6408
47
+ if (!viewCall.successData && !viewCall.error) {
48
+ // Retry the call to `npm view` with the configured publish tag,
49
+ // to at least try to get something.
50
+ const viewWithTagArguments = getViewArguments(name, options, true);
51
+ viewCall = await callNpmCli(VIEW, viewWithTagArguments, cliOptions);
52
+ }
53
+
54
+ if (viewCall.error && viewCall.errorCode !== E404) {
55
+ throw viewCall.error;
56
+ }
57
+
58
+ const comparison = compareVersions(version, viewCall.successData, options);
59
+ const publishCall = comparison.type
60
+ ? await callNpmCli(PUBLISH, publishArguments, cliOptions)
61
+ : { successData: undefined, errorCode: undefined, error: undefined };
62
+
63
+ if (publishCall.error && publishCall.errorCode !== EPUBLISHCONFLICT) {
64
+ throw publishCall.error;
65
+ }
66
+
67
+ const { successData: publishData } = publishCall;
68
+
69
+ return {
70
+ id: publishData?.id,
71
+ files: publishData?.files ?? [],
72
+ type: publishData ? comparison.type : undefined,
73
+ oldVersion: comparison.oldVersion,
74
+ };
75
+ }
@@ -0,0 +1,48 @@
1
+ import semverDifference from "semver/functions/diff.js";
2
+ import semverGreaterThan from "semver/functions/gt.js";
3
+ import semverValid from "semver/functions/valid.js";
4
+
5
+ import { STRATEGY_ALL } from "../options.js";
6
+ import type { NormalizedOptions } from "../normalize-options.js";
7
+ import type { ReleaseType } from "../results.js";
8
+ import type { NpmViewData } from "../npm/index.js";
9
+
10
+ export interface VersionComparison {
11
+ type: ReleaseType | undefined;
12
+ oldVersion: string | undefined;
13
+ }
14
+
15
+ const INITIAL = "initial";
16
+ const DIFFERENT = "different";
17
+
18
+ /**
19
+ * Compare previously published versions with the package's current version.
20
+ *
21
+ * @param currentVersion The current package version.
22
+ * @param publishedVersions The versions that have already been published.
23
+ * @param options Configuration options
24
+ * @returns The release type and previous version.
25
+ */
26
+ export function compareVersions(
27
+ currentVersion: string,
28
+ publishedVersions: NpmViewData | undefined,
29
+ options: NormalizedOptions
30
+ ): VersionComparison {
31
+ const { versions, "dist-tags": tags } = publishedVersions ?? {};
32
+ const { strategy, tag: publishTag } = options;
33
+ const oldVersion = semverValid(tags?.[publishTag.value]) ?? undefined;
34
+ const isUnique = !versions?.includes(currentVersion);
35
+ let type: ReleaseType | undefined;
36
+
37
+ if (isUnique) {
38
+ if (!oldVersion) {
39
+ type = INITIAL;
40
+ } else if (semverGreaterThan(currentVersion, oldVersion)) {
41
+ type = semverDifference(currentVersion, oldVersion) ?? DIFFERENT;
42
+ } else if (strategy.value === STRATEGY_ALL) {
43
+ type = DIFFERENT;
44
+ }
45
+ }
46
+
47
+ return { type, oldVersion };
48
+ }
@@ -0,0 +1,61 @@
1
+ import type { NormalizedOptions } from "../normalize-options.js";
2
+
3
+ /**
4
+ * Given a package name and publish configuration, get the NPM CLI view
5
+ * arguments.
6
+ *
7
+ * @param packageName Package name.
8
+ * @param options Publish configuration.
9
+ * @param retryWithTag Include a non-latest tag in the package spec for a rety
10
+ * attempt.
11
+ * @returns Arguments to pass to the NPM CLI. If `retryWithTag` is true, but the
12
+ * publish config is using the `latest` tag, will return `undefined`.
13
+ */
14
+ export function getViewArguments(
15
+ packageName: string,
16
+ options: NormalizedOptions,
17
+ retryWithTag = false
18
+ ): string[] {
19
+ const packageSpec = retryWithTag
20
+ ? `${packageName}@${options.tag.value}`
21
+ : packageName;
22
+
23
+ return [packageSpec, "dist-tags", "versions"];
24
+ }
25
+
26
+ /**
27
+ * Given a publish configuration, get the NPM CLI publish arguments.
28
+ *
29
+ * @param packageSpec Package specification path.
30
+ * @param options Publish configuration.
31
+ * @returns Arguments to pass to the NPM CLI.
32
+ */
33
+ export function getPublishArguments(
34
+ packageSpec: string,
35
+ options: NormalizedOptions
36
+ ): string[] {
37
+ const { tag, access, dryRun, provenance } = options;
38
+ const publishArguments = [];
39
+
40
+ if (packageSpec.length > 0) {
41
+ publishArguments.push(packageSpec);
42
+ }
43
+
44
+ if (!tag.isDefault) {
45
+ publishArguments.push("--tag", tag.value);
46
+ }
47
+
48
+ if (!access.isDefault && access.value) {
49
+ publishArguments.push("--access", access.value);
50
+ }
51
+
52
+ if (!provenance.isDefault && provenance.value) {
53
+ publishArguments.push("--provenance");
54
+ }
55
+
56
+ if (!dryRun.isDefault && dryRun.value) {
57
+ publishArguments.push("--dry-run");
58
+ }
59
+
60
+ return publishArguments;
61
+ }
@@ -0,0 +1 @@
1
+ export * from "./compare-and-publish.js";
package/src/errors.ts ADDED
@@ -0,0 +1,137 @@
1
+ import os from "node:os";
2
+
3
+ import {
4
+ ACCESS_PUBLIC,
5
+ ACCESS_RESTRICTED,
6
+ STRATEGY_ALL,
7
+ STRATEGY_UPGRADE,
8
+ } from "./options.js";
9
+
10
+ export class InvalidPackageError extends TypeError {
11
+ public constructor(value: unknown) {
12
+ super(
13
+ `Package must be a directory, package.json, or .tgz file, got "${String(
14
+ value
15
+ )}"`
16
+ );
17
+ this.name = "PackageJsonReadError";
18
+ }
19
+ }
20
+
21
+ export class PackageJsonReadError extends Error {
22
+ public constructor(manifestPath: string, originalError: unknown) {
23
+ const message = [
24
+ `Could not read package.json at ${manifestPath}`,
25
+ originalError instanceof Error ? originalError.message : "",
26
+ ]
27
+ .filter(Boolean)
28
+ .join(os.EOL);
29
+
30
+ super(message);
31
+ this.name = "PackageJsonReadError";
32
+ }
33
+ }
34
+
35
+ export class PackageTarballReadError extends Error {
36
+ public constructor(tarballPath: string, originalError: unknown) {
37
+ const message = [
38
+ `Could not read package.json from ${tarballPath}`,
39
+ originalError instanceof Error ? originalError.message : "",
40
+ ]
41
+ .filter(Boolean)
42
+ .join(os.EOL);
43
+
44
+ super(message);
45
+ this.name = "PackageTarballReadError";
46
+ }
47
+ }
48
+
49
+ export class PackageJsonParseError extends SyntaxError {
50
+ public constructor(packageSpec: string, originalError: unknown) {
51
+ const message = [
52
+ `Invalid JSON, could not parse package.json for ${packageSpec}`,
53
+ originalError instanceof Error ? originalError.message : "",
54
+ ]
55
+ .filter(Boolean)
56
+ .join(os.EOL);
57
+
58
+ super(message);
59
+ this.name = "PackageJsonParseError";
60
+ }
61
+ }
62
+
63
+ export class InvalidPackageNameError extends TypeError {
64
+ public constructor(value: unknown) {
65
+ super(`Package name must be a string, got "${String(value)}"`);
66
+ this.name = "InvalidPackageNameError";
67
+ }
68
+ }
69
+
70
+ export class InvalidPackageVersionError extends TypeError {
71
+ public constructor(value: unknown) {
72
+ super(`Package version must be a string, got "${String(value)}"`);
73
+ this.name = "InvalidPackageVersionError";
74
+ }
75
+ }
76
+
77
+ export class InvalidPackagePublishConfigError extends TypeError {
78
+ public constructor(value: unknown) {
79
+ super(`Publish config must be an object, got "${String(value)}"`);
80
+ this.name = "InvalidPackagePublishConfigError";
81
+ }
82
+ }
83
+
84
+ export class InvalidRegistryUrlError extends TypeError {
85
+ public constructor(value: unknown) {
86
+ super(`Registry URL invalid, got "${String(value)}"`);
87
+ this.name = "InvalidRegistryUrlError";
88
+ }
89
+ }
90
+
91
+ export class InvalidTokenError extends TypeError {
92
+ public constructor() {
93
+ super("Token must be a non-empty string.");
94
+ this.name = "InvalidTokenError";
95
+ }
96
+ }
97
+
98
+ export class InvalidTagError extends TypeError {
99
+ public constructor(value: unknown) {
100
+ super(`Tag must be a non-empty string, got "${String(value)}".`);
101
+ this.name = "InvalidTagError";
102
+ }
103
+ }
104
+
105
+ export class InvalidAccessError extends TypeError {
106
+ public constructor(value: unknown) {
107
+ super(
108
+ `Access must be "${ACCESS_PUBLIC}" or "${ACCESS_RESTRICTED}", got "${String(
109
+ value
110
+ )}".`
111
+ );
112
+ this.name = "InvalidAccessError";
113
+ }
114
+ }
115
+
116
+ export class InvalidStrategyError extends TypeError {
117
+ public constructor(value: unknown) {
118
+ super(
119
+ `Strategy must be "${STRATEGY_UPGRADE}" or "${STRATEGY_ALL}", got "${String(
120
+ value
121
+ )}".`
122
+ );
123
+ this.name = "InvalidStrategyError";
124
+ }
125
+ }
126
+
127
+ export class NpmCallError extends Error {
128
+ public constructor(command: string, exitCode: number, stderr: string) {
129
+ super(
130
+ [
131
+ `Call to "npm ${command}" exited with non-zero exit code ${exitCode}`,
132
+ stderr,
133
+ ].join(os.EOL)
134
+ );
135
+ this.name = "NpmCallError";
136
+ }
137
+ }
@@ -0,0 +1,40 @@
1
+ import os from "node:os";
2
+
3
+ import type { PublishResult } from "./compare-and-publish/index.js";
4
+ import type { PackageManifest } from "./read-manifest.js";
5
+ import type { NormalizedOptions } from "./normalize-options.js";
6
+
7
+ /**
8
+ * Format publish results into a string.
9
+ *
10
+ * @param manifest Package manifest
11
+ * @param options Configuration options.
12
+ * @param result Results from running npm publish.
13
+ * @returns Formatted string.
14
+ */
15
+ export function formatPublishResult(
16
+ manifest: PackageManifest,
17
+ options: NormalizedOptions,
18
+ result: PublishResult
19
+ ): string {
20
+ if (result.id === undefined) {
21
+ return `🙅‍♀️ ${manifest.name}@${manifest.version} publish skipped.`;
22
+ }
23
+
24
+ return [
25
+ `📦 ${result.id}${options.dryRun.value ? " (DRY RUN)" : ""}`,
26
+ "=== Contents ===",
27
+ ...result.files.map(({ path, size }) => `${formatSize(size)}\t${path}`),
28
+ ].join(os.EOL);
29
+ }
30
+
31
+ const formatSize = (size: number): string => {
32
+ if (size < 1000) {
33
+ return `${size} B`;
34
+ }
35
+ if (size < 1_000_000) {
36
+ return `${(size / 1000).toFixed(1)} kB`;
37
+ }
38
+
39
+ return `${(size / 1_000_000).toFixed(1)} MB`;
40
+ };
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ // Export the external type definitions as named exports
2
+ export * from "./options.js";
3
+ export * from "./results.js";
4
+ export * from "./errors.js";
5
+
6
+ // Export `npmPublish` as a named export and the default export
7
+ export { npmPublish } from "./npm-publish.js";
@@ -0,0 +1,123 @@
1
+ import os from "node:os";
2
+
3
+ import * as errors from "./errors.js";
4
+ import type { PackageManifest } from "./read-manifest.js";
5
+ import {
6
+ ACCESS_PUBLIC,
7
+ ACCESS_RESTRICTED,
8
+ STRATEGY_UPGRADE,
9
+ STRATEGY_ALL,
10
+ type Access,
11
+ type Strategy,
12
+ type Options,
13
+ type Logger,
14
+ } from "./options.js";
15
+
16
+ const REGISTRY_NPM = "https://registry.npmjs.org/";
17
+ export const TAG_LATEST = "latest";
18
+
19
+ /** Normalized and sanitized auth, publish, and runtime configurations. */
20
+ export interface NormalizedOptions {
21
+ registry: URL;
22
+ token: string;
23
+ tag: ConfigValue<string>;
24
+ access: ConfigValue<Access | undefined>;
25
+ provenance: ConfigValue<boolean>;
26
+ dryRun: ConfigValue<boolean>;
27
+ strategy: ConfigValue<Strategy>;
28
+ logger: Logger | undefined;
29
+ temporaryDirectory: string;
30
+ }
31
+
32
+ /** A config value, and whether that value differs from default. */
33
+ export interface ConfigValue<TValue> {
34
+ value: TValue;
35
+ isDefault: boolean;
36
+ }
37
+
38
+ /**
39
+ * Normalizes and sanitizes options, and fills-in any default values.
40
+ *
41
+ * @param manifest Package metadata from package.json.
42
+ * @param options User-input options.
43
+ * @returns Validated auth and publish configuration.
44
+ */
45
+ export function normalizeOptions(
46
+ manifest: PackageManifest,
47
+ options: Options
48
+ ): NormalizedOptions {
49
+ const defaultTag = manifest.publishConfig?.tag ?? TAG_LATEST;
50
+
51
+ const defaultRegistry = manifest.publishConfig?.registry ?? REGISTRY_NPM;
52
+
53
+ const defaultAccess =
54
+ manifest.publishConfig?.access ??
55
+ (manifest.scope === undefined ? ACCESS_PUBLIC : undefined);
56
+
57
+ const defaultProvenance = manifest.publishConfig?.provenance ?? false;
58
+
59
+ return {
60
+ token: validateToken(options.token),
61
+ registry: validateRegistry(options.registry ?? defaultRegistry),
62
+ tag: setValue(options.tag, defaultTag, validateTag),
63
+ access: setValue(options.access, defaultAccess, validateAccess),
64
+ provenance: setValue(options.provenance, defaultProvenance, Boolean),
65
+ dryRun: setValue(options.dryRun, false, Boolean),
66
+ strategy: setValue(options.strategy, STRATEGY_ALL, validateStrategy),
67
+ logger: options.logger,
68
+ temporaryDirectory: options.temporaryDirectory ?? os.tmpdir(),
69
+ };
70
+ }
71
+
72
+ const setValue = <TValue>(
73
+ value: unknown,
74
+ defaultValue: unknown,
75
+ validate: (value: unknown) => TValue
76
+ ): ConfigValue<TValue> => ({
77
+ value: validate(value ?? defaultValue),
78
+ isDefault: value === undefined,
79
+ });
80
+
81
+ const validateToken = (value: unknown): string => {
82
+ if (typeof value === "string" && value.length > 0) {
83
+ return value;
84
+ }
85
+
86
+ throw new errors.InvalidTokenError();
87
+ };
88
+
89
+ const validateRegistry = (value: unknown): URL => {
90
+ try {
91
+ return new URL(value as string | URL);
92
+ } catch {
93
+ throw new errors.InvalidRegistryUrlError(value);
94
+ }
95
+ };
96
+
97
+ const validateTag = (value: unknown): string => {
98
+ if (typeof value === "string" && value.length > 0) {
99
+ return value;
100
+ }
101
+
102
+ throw new errors.InvalidTagError(value);
103
+ };
104
+
105
+ const validateAccess = (value: unknown): Access | undefined => {
106
+ if (
107
+ value === undefined ||
108
+ value === ACCESS_PUBLIC ||
109
+ value === ACCESS_RESTRICTED
110
+ ) {
111
+ return value;
112
+ }
113
+
114
+ throw new errors.InvalidAccessError(value);
115
+ };
116
+
117
+ const validateStrategy = (value: unknown): Strategy => {
118
+ if (value === STRATEGY_ALL || value === STRATEGY_UPGRADE) {
119
+ return value;
120
+ }
121
+
122
+ throw new errors.InvalidStrategyError(value);
123
+ };