chrome-webstore-upload 3.0.3 → 3.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.
Files changed (4) hide show
  1. package/index.d.ts +25 -0
  2. package/index.js +32 -50
  3. package/package.json +29 -7
  4. package/readme.md +12 -15
package/index.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ import { type JsonObject } from 'type-fest';
2
+ export declare const refreshTokenURI = "https://www.googleapis.com/oauth2/v4/token";
3
+ export type APIClientOptions = {
4
+ extensionId: string;
5
+ clientId: string;
6
+ refreshToken: string;
7
+ clientSecret: string | undefined;
8
+ };
9
+ declare class APIClient {
10
+ extensionId: string;
11
+ clientId: string;
12
+ refreshToken: string;
13
+ clientSecret: string | undefined;
14
+ constructor(options: APIClientOptions);
15
+ uploadExisting(readStream: ReadableStream, token?: Promise<string>): Promise<JsonObject>;
16
+ publish(target?: string, token?: Promise<string>, deployPercentage?: number | undefined): Promise<JsonObject>;
17
+ get(projection?: string, token?: Promise<string>): Promise<JsonObject>;
18
+ fetchToken(): Promise<string>;
19
+ _headers(token: string): {
20
+ Authorization: string;
21
+ 'x-goog-api-version': string;
22
+ };
23
+ }
24
+ export default function chromeWebstoreUpload(options: APIClientOptions): APIClient;
25
+ export {};
package/index.js CHANGED
@@ -1,18 +1,19 @@
1
1
  // API documentation:
2
2
  // https://developer.chrome.com/docs/webstore/api
3
3
  // https://developer.chrome.com/docs/webstore/using-api
4
-
5
4
  const rootURI = 'https://www.googleapis.com';
6
5
  export const refreshTokenURI = 'https://www.googleapis.com/oauth2/v4/token';
7
- const uploadExistingURI = id =>
8
- `${rootURI}/upload/chromewebstore/v1.1/items/${id}`;
9
- const publishURI = (id, target) =>
10
- `${rootURI}/chromewebstore/v1.1/items/${id}/publish?publishTarget=${target}`;
11
- const getURI = (id, projection) =>
12
- `${rootURI}/chromewebstore/v1.1/items/${id}?projection=${projection}`;
13
-
6
+ const uploadExistingURI = (id) => `${rootURI}/upload/chromewebstore/v1.1/items/${id}`;
7
+ const publishURI = ({ extensionId, target = 'default', deployPercentage }) => {
8
+ const url = new URL(`${rootURI}/chromewebstore/v1.1/items/${extensionId}/publish`);
9
+ url.searchParams.set('publishTarget', target);
10
+ if (deployPercentage !== undefined) {
11
+ url.searchParams.set('deployPercentage', String(deployPercentage));
12
+ }
13
+ return url.href;
14
+ };
15
+ const getURI = (id, projection) => `${rootURI}/chromewebstore/v1.1/items/${id}?projection=${projection}`;
14
16
  const requiredFields = ['extensionId', 'clientId', 'refreshToken'];
15
-
16
17
  function throwIfNotOk(request, response) {
17
18
  if (!request.ok) {
18
19
  const error = new Error(request.statusText ?? 'Unknown error');
@@ -20,89 +21,75 @@ function throwIfNotOk(request, response) {
20
21
  throw error;
21
22
  }
22
23
  }
23
-
24
24
  class APIClient {
25
+ extensionId;
26
+ clientId;
27
+ refreshToken;
28
+ clientSecret;
25
29
  constructor(options) {
26
30
  if (typeof fetch !== 'function') {
27
- throw new TypeError('`chrome-webstore-upload` requires Node.js 18.0 or newer because it relies on the global `fetch` function.');
31
+ throw new TypeError('`chrome-webstore-upload` requires Node.js 18.17 or newer because it relies on the global `fetch` function.');
32
+ }
33
+ if (typeof options !== 'object') {
34
+ throw new TypeError('The options object is required');
28
35
  }
29
-
30
36
  for (const field of requiredFields) {
31
37
  if (!options[field]) {
32
38
  throw new Error(`Option "${field}" is required`);
33
39
  }
34
-
35
- this[field] = options[field];
36
- }
37
-
38
- if ('clientSecret' in options) {
39
- this.clientSecret = options.clientSecret;
40
40
  }
41
+ this.extensionId = options.extensionId;
42
+ this.clientId = options.clientId;
43
+ this.refreshToken = options.refreshToken;
44
+ this.clientSecret = options.clientSecret;
41
45
  }
42
-
43
46
  async uploadExisting(readStream, token = this.fetchToken()) {
44
47
  if (!readStream) {
45
48
  throw new Error('Read stream missing');
46
49
  }
47
-
48
50
  const { extensionId } = this;
49
-
50
51
  const request = await fetch(uploadExistingURI(extensionId), {
51
52
  method: 'PUT',
52
53
  headers: this._headers(await token),
54
+ // @ts-expect-error Node extension? 🤷‍♂️ Required https://github.com/nodejs/node/issues/46221
53
55
  duplex: 'half',
54
56
  body: readStream,
55
57
  });
56
-
57
58
  const response = await request.json();
58
-
59
59
  throwIfNotOk(request, response);
60
-
61
60
  return response;
62
61
  }
63
-
64
- async publish(target = 'default', token = this.fetchToken()) {
62
+ async publish(target = 'default', token = this.fetchToken(), deployPercentage = undefined) {
65
63
  const { extensionId } = this;
66
-
67
- const request = await fetch(publishURI(extensionId, target), {
64
+ const request = await fetch(publishURI({ extensionId, target, deployPercentage }), {
68
65
  method: 'POST',
69
66
  headers: this._headers(await token),
70
67
  });
71
-
72
68
  const response = await request.json();
73
-
74
69
  throwIfNotOk(request, response);
75
-
76
70
  return response;
77
71
  }
78
-
79
72
  async get(projection = 'DRAFT', token = this.fetchToken()) {
80
73
  const { extensionId } = this;
81
-
82
74
  const request = await fetch(getURI(extensionId, projection), {
83
75
  method: 'GET',
84
76
  headers: this._headers(await token),
85
77
  });
86
-
87
78
  const response = await request.json();
88
-
89
79
  throwIfNotOk(request, response);
90
-
91
80
  return response;
92
81
  }
93
-
94
82
  async fetchToken() {
95
83
  const { clientId, clientSecret, refreshToken } = this;
96
84
  const json = {
97
85
  client_id: clientId,
98
86
  refresh_token: refreshToken,
99
87
  grant_type: 'refresh_token',
88
+ client_secret: clientSecret,
100
89
  };
101
-
102
- if (clientSecret) {
103
- json.client_secret = clientSecret;
90
+ if (!clientSecret) {
91
+ delete json.client_secret;
104
92
  }
105
-
106
93
  const request = await fetch(refreshTokenURI, {
107
94
  method: 'POST',
108
95
  body: JSON.stringify(json),
@@ -110,14 +97,10 @@ class APIClient {
110
97
  'Content-Type': 'application/json',
111
98
  },
112
99
  });
113
-
114
- await throwIfNotOk(request);
115
-
116
100
  const response = await request.json();
117
-
118
- return response.access_token;
101
+ throwIfNotOk(request, response);
102
+ return response['access_token'];
119
103
  }
120
-
121
104
  _headers(token) {
122
105
  return {
123
106
  Authorization: `Bearer ${token}`,
@@ -125,7 +108,6 @@ class APIClient {
125
108
  };
126
109
  }
127
110
  }
128
-
129
- export default function chromeWebstoreUpload(...args) {
130
- return new APIClient(...args);
111
+ export default function chromeWebstoreUpload(options) {
112
+ return new APIClient(options);
131
113
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-webstore-upload",
3
- "version": "3.0.3",
3
+ "version": "3.1.1",
4
4
  "description": "Upload Chrome Extensions to the Chrome Web Store",
5
5
  "keywords": [
6
6
  "chrome",
@@ -21,16 +21,21 @@
21
21
  ],
22
22
  "type": "module",
23
23
  "exports": "./index.js",
24
+ "types": "./index.d.ts",
24
25
  "files": [
25
- "index.js"
26
+ "index.js",
27
+ "index.d.ts"
26
28
  ],
27
29
  "scripts": {
28
- "test": "xo && vitest run",
29
- "upload": "npm run bundle && npm run test:upload",
30
30
  "prebundle": "dot-json test/extension/manifest.json version $(utc-version)",
31
31
  "bundle": "web-ext build --filename live-test.zip --overwrite-dest",
32
32
  "postbundle": "git restore test/extension/manifest.json",
33
- "test:upload": "eval $(cat .env) node test/live-test.js"
33
+ "build": "tsc",
34
+ "watch": "tsc -w",
35
+ "prepack": "npm run build",
36
+ "test": "xo && vitest run && tsc",
37
+ "test:upload": "eval $(cat .env) node test/live-test.js",
38
+ "upload": "npm run bundle && npm run test:upload"
34
39
  },
35
40
  "xo": {
36
41
  "rules": {
@@ -40,15 +45,32 @@
40
45
  "always"
41
46
  ]
42
47
  },
48
+ "overrides": [
49
+ {
50
+ "files": [
51
+ "**/*.ts"
52
+ ],
53
+ "rules": {
54
+ "@typescript-eslint/naming-convention": "off",
55
+ "@typescript-eslint/object-curly-spacing": [
56
+ "error",
57
+ "always"
58
+ ]
59
+ }
60
+ }
61
+ ],
43
62
  "space": 4
44
63
  },
45
64
  "devDependencies": {
65
+ "@sindresorhus/tsconfig": "^5.0.0",
46
66
  "dot-json": "^1.3.0",
47
67
  "fetch-mock": "^9.11.0",
48
68
  "node-fetch": "^2.7.0",
69
+ "type-fest": "^4.9.0",
70
+ "typescript": "^5.3.3",
49
71
  "utc-version": "^2.0.2",
50
- "vitest": "^1.0.4",
51
- "xo": "^0.56.0"
72
+ "vitest": "^1.6.0",
73
+ "xo": "^0.58.0"
52
74
  },
53
75
  "engines": {
54
76
  "node": ">=18"
package/readme.md CHANGED
@@ -38,10 +38,9 @@ import fs from 'fs';
38
38
 
39
39
  const myZipFile = fs.createReadStream('./mypackage.zip');
40
40
  const token = 'xxxx'; // optional. One will be fetched if not provided
41
- store.uploadExisting(myZipFile, token).then(res => {
42
- // Response is a Resource Representation
43
- // https://developer.chrome.com/webstore/webstore_api/items#resource
44
- });
41
+ const response = await store.uploadExisting(myZipFile, token);
42
+ // response is a Resource Representation
43
+ // https://developer.chrome.com/webstore/webstore_api/items#resource
45
44
  ```
46
45
 
47
46
  ### Publish extension
@@ -49,10 +48,10 @@ store.uploadExisting(myZipFile, token).then(res => {
49
48
  ```javascript
50
49
  const target = 'default'; // optional. Can also be 'trustedTesters'
51
50
  const token = 'xxxx'; // optional. One will be fetched if not provided
52
- store.publish(target, token).then(res => {
53
- // Response is documented here:
54
- // https://developer.chrome.com/webstore/webstore_api/items/publish
55
- });
51
+ const deployPercentage = 25; // optional. Will default to 100%.
52
+ const response = await store.publish(target, token, deployPercentage);
53
+ // response is documented here:
54
+ // https://developer.chrome.com/webstore/webstore_api/items#publish
56
55
  ```
57
56
 
58
57
  ### Get a Chrome Web Store item
@@ -60,18 +59,16 @@ store.publish(target, token).then(res => {
60
59
  ```javascript
61
60
  const projection = "DRAFT"; // optional. Can also be 'PUBLISHED' but only "DRAFT" is supported at this time.
62
61
  const token = "xxxx"; // optional. One will be fetched if not provided
63
- store.get(projection, token).then((res) => {
64
- // Response is documented here:
65
- // https://developer.chrome.com/docs/webstore/webstore_api/items/get
66
- });
62
+ const response = await store.get(projection, token);
63
+ // response is documented here:
64
+ // https://developer.chrome.com/docs/webstore/webstore_api/items#get
67
65
  ```
68
66
 
69
67
  ### Fetch token
70
68
 
71
69
  ```javascript
72
- store.fetchToken().then(token => {
73
- // Token is a string
74
- });
70
+ const token = store.fetchToken();
71
+ // token is astring
75
72
  ```
76
73
 
77
74
  ## Tips