@jsdevtools/npm-publish 2.0.0 → 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 (73) hide show
  1. package/README.md +39 -26
  2. package/lib/action/main.js +1 -0
  3. package/lib/action/main.js.map +1 -1
  4. package/lib/cli/index.d.ts +1 -1
  5. package/lib/cli/index.js +4 -1
  6. package/lib/cli/index.js.map +1 -1
  7. package/lib/cli/parse-cli-arguments.js +1 -0
  8. package/lib/cli/parse-cli-arguments.js.map +1 -1
  9. package/lib/compare-and-publish/compare-and-publish.d.ts +21 -0
  10. package/lib/compare-and-publish/compare-and-publish.js +49 -0
  11. package/lib/compare-and-publish/compare-and-publish.js.map +1 -0
  12. package/lib/compare-and-publish/compare-versions.d.ts +16 -0
  13. package/lib/compare-and-publish/compare-versions.js +41 -0
  14. package/lib/compare-and-publish/compare-versions.js.map +1 -0
  15. package/lib/compare-and-publish/get-arguments.d.ts +21 -0
  16. package/lib/compare-and-publish/get-arguments.js +50 -0
  17. package/lib/compare-and-publish/get-arguments.js.map +1 -0
  18. package/lib/compare-and-publish/index.d.ts +1 -0
  19. package/lib/compare-and-publish/index.js +18 -0
  20. package/lib/compare-and-publish/index.js.map +1 -0
  21. package/lib/errors.d.ts +3 -0
  22. package/lib/errors.js +8 -1
  23. package/lib/errors.js.map +1 -1
  24. package/lib/format-publish-result.d.ts +3 -3
  25. package/lib/format-publish-result.js +5 -5
  26. package/lib/format-publish-result.js.map +1 -1
  27. package/lib/normalize-options.d.ts +4 -2
  28. package/lib/normalize-options.js +14 -12
  29. package/lib/normalize-options.js.map +1 -1
  30. package/lib/npm/call-npm-cli.d.ts +26 -4
  31. package/lib/npm/call-npm-cli.js +49 -27
  32. package/lib/npm/call-npm-cli.js.map +1 -1
  33. package/lib/npm/index.d.ts +2 -29
  34. package/lib/npm/index.js +16 -38
  35. package/lib/npm/index.js.map +1 -1
  36. package/lib/npm/use-npm-environment.d.ts +4 -2
  37. package/lib/npm/use-npm-environment.js +7 -5
  38. package/lib/npm/use-npm-environment.js.map +1 -1
  39. package/lib/npm-publish.js +7 -12
  40. package/lib/npm-publish.js.map +1 -1
  41. package/lib/options.d.ts +14 -3
  42. package/lib/read-manifest.d.ts +4 -7
  43. package/lib/read-manifest.js +4 -1
  44. package/lib/read-manifest.js.map +1 -1
  45. package/lib/results.d.ts +5 -1
  46. package/lib/results.js +3 -0
  47. package/lib/results.js.map +1 -1
  48. package/package.json +7 -3
  49. package/src/action/main.ts +1 -0
  50. package/src/cli/index.ts +4 -1
  51. package/src/cli/parse-cli-arguments.ts +1 -0
  52. package/src/compare-and-publish/compare-and-publish.ts +75 -0
  53. package/src/compare-and-publish/compare-versions.ts +48 -0
  54. package/src/compare-and-publish/get-arguments.ts +61 -0
  55. package/src/compare-and-publish/index.ts +1 -0
  56. package/src/errors.ts +7 -0
  57. package/src/format-publish-result.ts +6 -6
  58. package/src/normalize-options.ts +16 -12
  59. package/src/npm/call-npm-cli.ts +90 -48
  60. package/src/npm/index.ts +2 -64
  61. package/src/npm/use-npm-environment.ts +10 -4
  62. package/src/npm-publish.ts +11 -18
  63. package/src/options.ts +15 -3
  64. package/src/read-manifest.ts +8 -9
  65. package/src/results.ts +6 -1
  66. package/lib/compare-versions.d.ts +0 -20
  67. package/lib/compare-versions.js +0 -36
  68. package/lib/compare-versions.js.map +0 -1
  69. package/lib/npm/get-publish-arguments.d.ts +0 -9
  70. package/lib/npm/get-publish-arguments.js +0 -29
  71. package/lib/npm/get-publish-arguments.js.map +0 -1
  72. package/src/compare-versions.ts +0 -52
  73. package/src/npm/get-publish-arguments.ts +0 -34
@@ -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 CHANGED
@@ -95,6 +95,13 @@ export class InvalidTokenError extends TypeError {
95
95
  }
96
96
  }
97
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
+
98
105
  export class InvalidAccessError extends TypeError {
99
106
  public constructor(value: unknown) {
100
107
  super(
@@ -1,6 +1,6 @@
1
1
  import os from "node:os";
2
2
 
3
- import type { PublishResult } from "./npm/index.js";
3
+ import type { PublishResult } from "./compare-and-publish/index.js";
4
4
  import type { PackageManifest } from "./read-manifest.js";
5
5
  import type { NormalizedOptions } from "./normalize-options.js";
6
6
 
@@ -9,22 +9,22 @@ import type { NormalizedOptions } from "./normalize-options.js";
9
9
  *
10
10
  * @param manifest Package manifest
11
11
  * @param options Configuration options.
12
- * @param results Results from running npm publish.
12
+ * @param result Results from running npm publish.
13
13
  * @returns Formatted string.
14
14
  */
15
15
  export function formatPublishResult(
16
16
  manifest: PackageManifest,
17
17
  options: NormalizedOptions,
18
- results?: PublishResult
18
+ result: PublishResult
19
19
  ): string {
20
- if (results === undefined) {
20
+ if (result.id === undefined) {
21
21
  return `🙅‍♀️ ${manifest.name}@${manifest.version} publish skipped.`;
22
22
  }
23
23
 
24
24
  return [
25
- `📦 ${results.id}${options.dryRun.value ? " (DRY RUN)" : ""}`,
25
+ `📦 ${result.id}${options.dryRun.value ? " (DRY RUN)" : ""}`,
26
26
  "=== Contents ===",
27
- ...results.files.map(({ path, size }) => `${formatSize(size)}\t${path}`),
27
+ ...result.files.map(({ path, size }) => `${formatSize(size)}\t${path}`),
28
28
  ].join(os.EOL);
29
29
  }
30
30
 
@@ -13,8 +13,8 @@ import {
13
13
  type Logger,
14
14
  } from "./options.js";
15
15
 
16
- const DEFAULT_REGISTRY = "https://registry.npmjs.org/";
17
- const DEFAULT_TAG = "latest";
16
+ const REGISTRY_NPM = "https://registry.npmjs.org/";
17
+ export const TAG_LATEST = "latest";
18
18
 
19
19
  /** Normalized and sanitized auth, publish, and runtime configurations. */
20
20
  export interface NormalizedOptions {
@@ -22,6 +22,7 @@ export interface NormalizedOptions {
22
22
  token: string;
23
23
  tag: ConfigValue<string>;
24
24
  access: ConfigValue<Access | undefined>;
25
+ provenance: ConfigValue<boolean>;
25
26
  dryRun: ConfigValue<boolean>;
26
27
  strategy: ConfigValue<Strategy>;
27
28
  logger: Logger | undefined;
@@ -37,28 +38,31 @@ export interface ConfigValue<TValue> {
37
38
  /**
38
39
  * Normalizes and sanitizes options, and fills-in any default values.
39
40
  *
40
- * @param options User-input options.
41
41
  * @param manifest Package metadata from package.json.
42
+ * @param options User-input options.
42
43
  * @returns Validated auth and publish configuration.
43
44
  */
44
45
  export function normalizeOptions(
45
- options: Options,
46
- manifest: PackageManifest
46
+ manifest: PackageManifest,
47
+ options: Options
47
48
  ): NormalizedOptions {
48
- const defaultTag = manifest.publishConfig?.tag ?? DEFAULT_TAG;
49
+ const defaultTag = manifest.publishConfig?.tag ?? TAG_LATEST;
49
50
 
50
- const defaultRegistry = manifest.publishConfig?.registry ?? DEFAULT_REGISTRY;
51
+ const defaultRegistry = manifest.publishConfig?.registry ?? REGISTRY_NPM;
51
52
 
52
53
  const defaultAccess =
53
54
  manifest.publishConfig?.access ??
54
55
  (manifest.scope === undefined ? ACCESS_PUBLIC : undefined);
55
56
 
57
+ const defaultProvenance = manifest.publishConfig?.provenance ?? false;
58
+
56
59
  return {
57
60
  token: validateToken(options.token),
58
61
  registry: validateRegistry(options.registry ?? defaultRegistry),
59
62
  tag: setValue(options.tag, defaultTag, validateTag),
60
63
  access: setValue(options.access, defaultAccess, validateAccess),
61
- dryRun: setValue(options.dryRun, false, validateDryRun),
64
+ provenance: setValue(options.provenance, defaultProvenance, Boolean),
65
+ dryRun: setValue(options.dryRun, false, Boolean),
62
66
  strategy: setValue(options.strategy, STRATEGY_ALL, validateStrategy),
63
67
  logger: options.logger,
64
68
  temporaryDirectory: options.temporaryDirectory ?? os.tmpdir(),
@@ -91,11 +95,11 @@ const validateRegistry = (value: unknown): URL => {
91
95
  };
92
96
 
93
97
  const validateTag = (value: unknown): string => {
94
- return value as string;
95
- };
98
+ if (typeof value === "string" && value.length > 0) {
99
+ return value;
100
+ }
96
101
 
97
- const validateDryRun = (value: unknown): boolean => {
98
- return value as boolean;
102
+ throw new errors.InvalidTagError(value);
99
103
  };
100
104
 
101
105
  const validateAccess = (value: unknown): Access | undefined => {
@@ -5,20 +5,93 @@ import * as errors from "../errors.js";
5
5
  import type { Logger } from "../options.js";
6
6
  import type { NpmCliEnvironment } from "./use-npm-environment.js";
7
7
 
8
- export interface NpmCliOptions<TReturn> {
9
- environment?: NpmCliEnvironment;
10
- ifError?: Record<string, TReturn>;
8
+ export interface NpmCliOptions {
9
+ environment: NpmCliEnvironment;
11
10
  logger?: Logger | undefined;
12
11
  }
13
12
 
13
+ export interface NpmCallResult<CommandT extends Command> {
14
+ successData: SuccessData<CommandT> | undefined;
15
+ errorCode: string | undefined;
16
+ error: Error | undefined;
17
+ }
18
+
19
+ type SuccessData<T extends Command> = T extends typeof VIEW
20
+ ? NpmViewData
21
+ : T extends typeof PUBLISH
22
+ ? NpmPublishData
23
+ : unknown;
24
+
25
+ export interface NpmViewData {
26
+ "dist-tags": Record<string, string>;
27
+ versions: string[];
28
+ }
29
+ export interface NpmPublishData {
30
+ id: string;
31
+ files: { path: string; size: number }[];
32
+ }
33
+
34
+ export type Command = typeof VIEW | typeof PUBLISH | string;
35
+ export const VIEW = "view";
36
+ export const PUBLISH = "publish";
37
+
38
+ export const E404 = "E404";
39
+ export const EPUBLISHCONFLICT = "EPUBLISHCONFLICT";
40
+
14
41
  const NPM = os.platform() === "win32" ? "npm.cmd" : "npm";
15
42
  const JSON_MATCH_RE = /(\{[\s\S]*\})/mu;
16
43
 
17
- const execNpm = (
44
+ /**
45
+ * Call the NPM CLI in JSON mode.
46
+ *
47
+ * @param command The command of the NPM CLI to call
48
+ * @param cliArguments Any arguments to send to the command
49
+ * @param options Customize environment variables or add an error handler.
50
+ * @returns The parsed JSON, or stdout if unparsable.
51
+ */
52
+ export async function callNpmCli<CommandT extends Command>(
53
+ command: CommandT,
54
+ cliArguments: string[],
55
+ options: NpmCliOptions
56
+ ): Promise<NpmCallResult<CommandT>> {
57
+ const { stdout, stderr, exitCode } = await execNpm(
58
+ [command, "--ignore-scripts", "--json", ...cliArguments],
59
+ options.environment,
60
+ options.logger
61
+ );
62
+
63
+ let successData;
64
+ let errorCode;
65
+ let error;
66
+
67
+ if (exitCode === 0) {
68
+ successData = parseJson<SuccessData<CommandT>>(stdout);
69
+ } else {
70
+ const errorPayload = parseJson<{ error?: { code?: string | null } }>(
71
+ stdout,
72
+ stderr
73
+ );
74
+
75
+ errorCode = errorPayload?.error?.code?.toUpperCase();
76
+ error = new errors.NpmCallError(command, exitCode, stderr);
77
+ }
78
+
79
+ return { successData, errorCode, error };
80
+ }
81
+
82
+ /**
83
+ * Execute the npm CLI.
84
+ *
85
+ * @param commandArguments Npm subcommand and arguments.
86
+ * @param environment Environment variables.
87
+ * @param logger Optional logger.
88
+ * @returns Stdout, stderr, and the exit code.
89
+ */
90
+ async function execNpm(
18
91
  commandArguments: string[],
19
- environment: Record<string, string> = {},
92
+ environment: Record<string, string>,
20
93
  logger?: Logger
21
- ): Promise<{ stdout: string; stderr: string; exitCode: number }> => {
94
+ ): Promise<{ stdout: string; stderr: string; exitCode: number }> {
22
95
  logger?.debug?.(`Running command: ${NPM} ${commandArguments.join(" ")}`);
23
96
 
24
97
  return new Promise((resolve) => {
@@ -39,9 +112,18 @@ const execNpm = (
39
112
  });
40
113
  });
41
114
  });
42
- };
115
+ }
43
116
 
44
- const parseJson = <TParsed>(...values: string[]): TParsed | undefined => {
117
+ /**
118
+ * Parse CLI outputs for JSON data.
119
+ *
120
+ * Certain versions of the npm CLI may intersperse JSON with human-readable
121
+ * output, which this function accounts for.
122
+ *
123
+ * @param values CLI outputs to check
124
+ * @returns Parsed JSON, if able to parse.
125
+ */
126
+ function parseJson<TParsed>(...values: string[]): TParsed | undefined {
45
127
  for (const value of values) {
46
128
  const jsonValue = JSON_MATCH_RE.exec(value)?.[1];
47
129
 
@@ -55,44 +137,4 @@ const parseJson = <TParsed>(...values: string[]): TParsed | undefined => {
55
137
  }
56
138
 
57
139
  return undefined;
58
- };
59
-
60
- /**
61
- * Call the NPM CLI in JSON mode.
62
- *
63
- * @param command The command of the NPM CLI to call
64
- * @param cliArguments Any arguments to send to the command
65
- * @param options Customize environment variables or add an error handler.
66
- * @returns The parsed JSON, or stdout if unparsable.
67
- */
68
- export async function callNpmCli<TReturn = string>(
69
- command: string,
70
- cliArguments: string[],
71
- options: NpmCliOptions<TReturn> = {}
72
- ): Promise<TReturn> {
73
- const { stdout, stderr, exitCode } = await execNpm(
74
- [command, "--ignore-scripts", "--json", ...cliArguments],
75
- options.environment,
76
- options.logger
77
- );
78
-
79
- if (exitCode !== 0) {
80
- const errorPayload = parseJson<{ error?: { code?: string | null } }>(
81
- stdout,
82
- stderr
83
- );
84
- const errorCode = errorPayload?.error?.code?.toLowerCase();
85
-
86
- if (
87
- typeof errorCode === "string" &&
88
- options.ifError &&
89
- errorCode in options.ifError
90
- ) {
91
- return options.ifError[errorCode] as TReturn;
92
- }
93
-
94
- throw new errors.NpmCallError(command, exitCode, stderr);
95
- }
96
-
97
- return parseJson(stdout) ?? (stdout as unknown as TReturn);
98
140
  }
package/src/npm/index.ts CHANGED
@@ -1,64 +1,2 @@
1
- import type { NormalizedOptions } from "../normalize-options.js";
2
- import { useNpmEnvironment } from "./use-npm-environment.js";
3
- import { callNpmCli } from "./call-npm-cli.js";
4
- import { getPublishArguments } from "./get-publish-arguments.js";
5
-
6
- export interface PublishedVersions {
7
- "dist-tags": Record<string, string>;
8
- versions: string[];
9
- }
10
-
11
- export interface PublishFile {
12
- path: string;
13
- size: number;
14
- }
15
-
16
- export interface PublishResult {
17
- id: string;
18
- files: PublishFile[];
19
- }
20
-
21
- /**
22
- * Get a package's published versions.
23
- *
24
- * @param packageName The name of the package to get published versions for.
25
- * @param options Configuration options.
26
- * @returns All published versions and tags.
27
- */
28
- export async function getVersions(
29
- packageName: string,
30
- options: NormalizedOptions
31
- ): Promise<PublishedVersions> {
32
- return useNpmEnvironment(options, (environment) => {
33
- return callNpmCli<PublishedVersions>(
34
- "view",
35
- [packageName, "dist-tags", "versions"],
36
- {
37
- logger: options.logger,
38
- environment,
39
- ifError: { e404: { "dist-tags": {}, versions: [] } },
40
- }
41
- );
42
- });
43
- }
44
-
45
- /**
46
- * Publish a package.
47
- *
48
- * @param packageSpec Package specification to pass to npm.
49
- * @param options Configuration options.
50
- * @returns Release metadata.
51
- */
52
- export async function publish(
53
- packageSpec: string,
54
- options: NormalizedOptions
55
- ): Promise<PublishResult> {
56
- const publishArguments = getPublishArguments(packageSpec, options);
57
-
58
- return useNpmEnvironment(options, (environment) => {
59
- return callNpmCli<PublishResult>("publish", publishArguments, {
60
- logger: options.logger,
61
- environment,
62
- });
63
- });
64
- }
1
+ export * from "./call-npm-cli.js";
2
+ export * from "./use-npm-environment.js";
@@ -2,11 +2,14 @@ import fs from "node:fs/promises";
2
2
  import os from "node:os";
3
3
  import path from "node:path";
4
4
 
5
+ import type { PackageManifest } from "../read-manifest.js";
5
6
  import type { NormalizedOptions } from "../normalize-options.js";
6
7
 
7
8
  export type NpmCliEnvironment = Record<string, string>;
8
9
 
9
10
  export type NpmCliTask<TReturn> = (
11
+ manifest: PackageManifest,
12
+ options: NormalizedOptions,
10
13
  environment: NpmCliEnvironment
11
14
  ) => Promise<TReturn>;
12
15
 
@@ -14,12 +17,14 @@ export type NpmCliTask<TReturn> = (
14
17
  * Create a temporary .npmrc file with the given auth token, and call a task
15
18
  * with env vars set to use that .npmrc.
16
19
  *
20
+ * @param manifest Pacakge metadata.
17
21
  * @param options Configuration options.
18
22
  * @param task A function called with the configured environment. After the
19
23
  * function resolves, the temporary .npmrc file will be removed.
20
24
  * @returns The resolved value of `task`
21
25
  */
22
26
  export async function useNpmEnvironment<TReturn>(
27
+ manifest: PackageManifest,
23
28
  options: NormalizedOptions,
24
29
  task: NpmCliTask<TReturn>
25
30
  ): Promise<TReturn> {
@@ -28,6 +33,10 @@ export async function useNpmEnvironment<TReturn>(
28
33
  path.join(temporaryDirectory, "npm-publish-")
29
34
  );
30
35
  const npmrc = path.join(npmrcDirectory, ".npmrc");
36
+ const environment = {
37
+ NODE_AUTH_TOKEN: token,
38
+ npm_config_userconfig: npmrc,
39
+ };
31
40
 
32
41
  const config = [
33
42
  "; created by jsdevtools/npm-publish",
@@ -41,10 +50,7 @@ export async function useNpmEnvironment<TReturn>(
41
50
  logger?.debug?.(`Temporary .npmrc created at ${npmrc}\n${config}`);
42
51
 
43
52
  try {
44
- return await task({
45
- NODE_AUTH_TOKEN: token,
46
- npm_config_userconfig: npmrc,
47
- });
53
+ return await task(manifest, options, environment);
48
54
  } finally {
49
55
  await fs.rm(npmrcDirectory, { force: true, recursive: true });
50
56
  }
@@ -1,7 +1,7 @@
1
1
  import { readManifest } from "./read-manifest.js";
2
2
  import { normalizeOptions } from "./normalize-options.js";
3
- import { getVersions, publish } from "./npm/index.js";
4
- import { compareVersions } from "./compare-versions.js";
3
+ import { useNpmEnvironment } from "./npm/index.js";
4
+ import { compareAndPublish } from "./compare-and-publish/index.js";
5
5
  import { formatPublishResult } from "./format-publish-result.js";
6
6
  import type { Options } from "./options.js";
7
7
  import type { Results } from "./results.js";
@@ -13,31 +13,24 @@ import type { Results } from "./results.js";
13
13
  * @returns Release metadata.
14
14
  */
15
15
  export async function npmPublish(options: Options): Promise<Results> {
16
- const { packageSpec, manifest } = await readManifest(options.package);
17
- const normalizedOptions = normalizeOptions(options, manifest);
18
- const publishedVersions = await getVersions(manifest.name, normalizedOptions);
19
- const versionComparison = compareVersions(
20
- manifest.version,
21
- publishedVersions,
22
- normalizedOptions
16
+ const manifest = await readManifest(options.package);
17
+ const normalizedOptions = normalizeOptions(manifest, options);
18
+ const publishResult = await useNpmEnvironment(
19
+ manifest,
20
+ normalizedOptions,
21
+ compareAndPublish
23
22
  );
24
23
 
25
- let publishResult;
26
-
27
- if (versionComparison.type !== undefined) {
28
- publishResult = await publish(packageSpec, normalizedOptions);
29
- }
30
-
31
24
  normalizedOptions.logger?.info?.(
32
25
  formatPublishResult(manifest, normalizedOptions, publishResult)
33
26
  );
34
27
 
35
28
  return {
36
- id: publishResult?.id,
29
+ id: publishResult.id,
30
+ type: publishResult.type,
31
+ oldVersion: publishResult.oldVersion,
37
32
  name: manifest.name,
38
33
  version: manifest.version,
39
- type: versionComparison.type,
40
- oldVersion: versionComparison.oldVersion,
41
34
  registry: normalizedOptions.registry,
42
35
  tag: normalizedOptions.tag.value,
43
36
  access: normalizedOptions.access.value,
package/src/options.ts CHANGED
@@ -39,7 +39,7 @@ export interface Options {
39
39
  *
40
40
  * Defaults to "https://registry.npmjs.org/".
41
41
  *
42
- * Can be overridden by the package.json's `publishConfig` field.
42
+ * Can be set by the package.json's `publishConfig` field.
43
43
  */
44
44
  registry?: string | URL | undefined;
45
45
 
@@ -48,7 +48,7 @@ export interface Options {
48
48
  *
49
49
  * Defaults to "latest".
50
50
  *
51
- * Can be overridden by the package.json's `publishConfig` field.
51
+ * Can be set by the package.json's `publishConfig` field.
52
52
  */
53
53
  tag?: string | undefined;
54
54
 
@@ -62,10 +62,22 @@ export interface Options {
62
62
  * Defaults to "restricted" for scoped packages, unless that package has been
63
63
  * previously published as `public`
64
64
  *
65
- * Can be overridden by the package.json's `publishConfig` field.
65
+ * Can be set by the package.json's `publishConfig` field.
66
66
  */
67
67
  access?: Access | undefined;
68
68
 
69
+ /**
70
+ * Generate provenance statements.
71
+ *
72
+ * Publish must be run from a supported CI provider to succeed. When run from
73
+ * GitHub Actions, requires `id-token: write` permission.
74
+ *
75
+ * Defaults to `false`.
76
+ *
77
+ * Can be set by the package.json's `publishConfig` field.
78
+ */
79
+ provenance?: boolean | undefined;
80
+
69
81
  /**
70
82
  * Version check strategy.
71
83
  *