chrome-webstore-upload 4.0.0-0 → 4.0.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/package.json +10 -6
  2. package/readme.md +18 -1
  3. package/index.d.ts +0 -42
  4. package/index.js +0 -126
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-webstore-upload",
3
- "version": "4.0.0-0",
3
+ "version": "4.0.1",
4
4
  "description": "Upload Chrome Extensions to the Chrome Web Store",
5
5
  "keywords": [
6
6
  "chrome",
@@ -20,11 +20,10 @@
20
20
  "Federico Brigante <me@fregante.com> (https://fregante.com)"
21
21
  ],
22
22
  "type": "module",
23
- "exports": "./index.js",
24
- "types": "./index.d.ts",
23
+ "exports": "./distribution/index.js",
24
+ "types": "./distribution/index.d.ts",
25
25
  "files": [
26
- "index.js",
27
- "index.d.ts"
26
+ "distribution"
28
27
  ],
29
28
  "scripts": {
30
29
  "prebundle": "dot-json test/extension/manifest.json version $(utc-version)",
@@ -63,6 +62,7 @@
63
62
  },
64
63
  "devDependencies": {
65
64
  "@sindresorhus/tsconfig": "^5.0.0",
65
+ "@types/yazl": "^3.3.0",
66
66
  "dot-json": "^1.3.0",
67
67
  "fetch-mock": "^9.11.0",
68
68
  "node-fetch": "^2.7.0",
@@ -72,9 +72,13 @@
72
72
  "xo": "^0.58.0"
73
73
  },
74
74
  "engines": {
75
- "node": ">=18"
75
+ "node": ">=20"
76
76
  },
77
77
  "webExt": {
78
78
  "sourceDir": "test/extension"
79
+ },
80
+ "dependencies": {
81
+ "junk": "^4.0.1",
82
+ "yazl": "^3.3.1"
79
83
  }
80
84
  }
package/readme.md CHANGED
@@ -33,16 +33,33 @@ const store = chromeWebstoreUpload({
33
33
 
34
34
  ### Upload to existing extension
35
35
 
36
+ You can upload a zip file, crx file, or a directory. If you provide a directory, it will be automatically zipped.
37
+
36
38
  ```javascript
37
39
  import fs from 'fs';
38
40
 
41
+ // Upload a zip file
39
42
  const myZipFile = fs.createReadStream('./mypackage.zip');
40
43
  const token = 'xxxx'; // optional. One will be fetched if not provided
41
- const response = await store.uploadExisting(myZipFile, token);
44
+ const maxAwaitInProgressResponseSeconds = 60; // optional. If the API response is IN_PROGRESS, this method will wait until it becomes successful, or until the specified timeout
45
+ const response = await store.uploadExisting(myZipFile, token, maxAwaitInProgressResponseSeconds);
42
46
  // response is a Resource Representation
43
47
  // https://developer.chrome.com/webstore/webstore_api/items#resource
44
48
  ```
45
49
 
50
+ ```javascript
51
+ // Upload a directory (it will be zipped automatically)
52
+ const response = await store.uploadExisting('./path/to/extension-directory', token, maxAwaitInProgressResponseSeconds);
53
+ // The directory must contain a manifest.json file
54
+ ```
55
+
56
+ ```javascript
57
+ // Upload a .zip or .crx file by path
58
+ const response = await store.uploadExisting('./path/to/extension.zip', token, maxAwaitInProgressResponseSeconds);
59
+ // or
60
+ const response = await store.uploadExisting('./path/to/extension.crx', token, maxAwaitInProgressResponseSeconds);
61
+ ```
62
+
46
63
  ### Publish extension
47
64
 
48
65
  ```javascript
package/index.d.ts DELETED
@@ -1,42 +0,0 @@
1
- import { type ReadStream } from 'node:fs';
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
- export type ItemResource = {
10
- kind: 'chromewebstore#item';
11
- id: string;
12
- publicKey: string;
13
- uploadState: 'FAILURE' | 'IN_PROGRESS' | 'NOT_FOUND' | 'SUCCESS';
14
- itemError: Array<{
15
- error_code: string;
16
- error_detail: string;
17
- }>;
18
- };
19
- export type PublishResponse = {
20
- kind: 'chromewebstore#item';
21
- item_id: string;
22
- status: Array<'OK' | 'NOT_AUTHORIZED' | 'INVALID_DEVELOPER' | 'DEVELOPER_NO_OWNERSHIP' | 'DEVELOPER_SUSPENDED' | 'ITEM_NOT_FOUND' | 'ITEM_PENDING_REVIEW' | 'ITEM_TAKEN_DOWN' | 'PUBLISHER_SUSPENDED'>;
23
- statusDetail: string[];
24
- };
25
- declare class APIClient {
26
- extensionId: string;
27
- clientId: string;
28
- refreshToken: string;
29
- clientSecret: string | undefined;
30
- constructor(options: APIClientOptions);
31
- uploadExisting(readStream: ReadStream | ReadableStream, token?: string | Promise<string>, maxAwaitInProgressResponseSeconds?: number): Promise<ItemResource>;
32
- publish(target?: string, token?: string | Promise<string>, deployPercentage?: number | undefined): Promise<PublishResponse>;
33
- get(projection?: string, token?: string | Promise<string>): Promise<ItemResource>;
34
- fetchToken(): Promise<string>;
35
- _waitUploadSuccess(response: ItemResource, maxAwaitInProgressResponseSeconds: number): Promise<ItemResource>;
36
- _headers(token: string): {
37
- Authorization: string;
38
- 'x-goog-api-version': string;
39
- };
40
- }
41
- export default function chromeWebstoreUpload(options: APIClientOptions): APIClient;
42
- export {};
package/index.js DELETED
@@ -1,126 +0,0 @@
1
- // API documentation:
2
- // https://developer.chrome.com/docs/webstore/api
3
- // https://developer.chrome.com/docs/webstore/using-api
4
- const rootURI = 'https://www.googleapis.com';
5
- export const refreshTokenURI = 'https://www.googleapis.com/oauth2/v4/token';
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}`;
16
- const requiredFields = ['extensionId', 'clientId', 'refreshToken'];
17
- const retryIntervalSeconds = 2;
18
- function throwIfNotOk(request, response) {
19
- if (!request.ok) {
20
- const error = new Error(request.statusText ?? 'Unknown error');
21
- error.response = response;
22
- throw error;
23
- }
24
- }
25
- class APIClient {
26
- extensionId;
27
- clientId;
28
- refreshToken;
29
- clientSecret;
30
- constructor(options) {
31
- if (typeof fetch !== 'function') {
32
- throw new TypeError('`chrome-webstore-upload` requires Node.js 18.17 or newer because it relies on the global `fetch` function.');
33
- }
34
- if (typeof options !== 'object') {
35
- throw new TypeError('The options object is required');
36
- }
37
- for (const field of requiredFields) {
38
- if (!options[field]) {
39
- throw new Error(`Option "${field}" is required`);
40
- }
41
- }
42
- this.extensionId = options.extensionId;
43
- this.clientId = options.clientId;
44
- this.refreshToken = options.refreshToken;
45
- this.clientSecret = options.clientSecret;
46
- }
47
- async uploadExisting(readStream, token = this.fetchToken(), maxAwaitInProgressResponseSeconds = 0) {
48
- if (!readStream) {
49
- throw new Error('Read stream missing');
50
- }
51
- const { extensionId } = this;
52
- const request = await fetch(uploadExistingURI(extensionId), {
53
- method: 'PUT',
54
- headers: this._headers(await token),
55
- // @ts-expect-error Node extension? 🤷‍♂️ Required https://github.com/nodejs/node/issues/46221
56
- duplex: 'half',
57
- // Until they figure it out, this seems to work. Alternatively use https://stackoverflow.com/a/76780381/288906
58
- body: readStream,
59
- });
60
- const response = await request.json();
61
- throwIfNotOk(request, response);
62
- return this._waitUploadSuccess(response, maxAwaitInProgressResponseSeconds);
63
- }
64
- async publish(target = 'default', token = this.fetchToken(), deployPercentage = undefined) {
65
- const { extensionId } = this;
66
- const request = await fetch(publishURI({ extensionId, target, deployPercentage }), {
67
- method: 'POST',
68
- headers: this._headers(await token),
69
- });
70
- const response = await request.json();
71
- throwIfNotOk(request, response);
72
- return response;
73
- }
74
- async get(projection = 'DRAFT', token = this.fetchToken()) {
75
- const { extensionId } = this;
76
- const request = await fetch(getURI(extensionId, projection), {
77
- method: 'GET',
78
- headers: this._headers(await token),
79
- });
80
- const response = await request.json();
81
- throwIfNotOk(request, response);
82
- return response;
83
- }
84
- async fetchToken() {
85
- const { clientId, clientSecret, refreshToken } = this;
86
- const json = {
87
- client_id: clientId,
88
- refresh_token: refreshToken,
89
- grant_type: 'refresh_token',
90
- client_secret: clientSecret,
91
- };
92
- if (!clientSecret) {
93
- delete json.client_secret;
94
- }
95
- const request = await fetch(refreshTokenURI, {
96
- method: 'POST',
97
- body: JSON.stringify(json),
98
- headers: {
99
- 'Content-Type': 'application/json',
100
- },
101
- });
102
- const response = await request.json();
103
- throwIfNotOk(request, response);
104
- return response.access_token;
105
- }
106
- async _waitUploadSuccess(response, maxAwaitInProgressResponseSeconds) {
107
- if (response.uploadState !== 'IN_PROGRESS' || maxAwaitInProgressResponseSeconds < retryIntervalSeconds) {
108
- return response;
109
- }
110
- // Wait before checking again
111
- await new Promise(resolve => {
112
- setTimeout(resolve, retryIntervalSeconds * 1000);
113
- });
114
- // Retry fetching the item resource
115
- return this._waitUploadSuccess(await this.get('DRAFT'), maxAwaitInProgressResponseSeconds - retryIntervalSeconds);
116
- }
117
- _headers(token) {
118
- return {
119
- Authorization: `Bearer ${token}`,
120
- 'x-goog-api-version': '2',
121
- };
122
- }
123
- }
124
- export default function chromeWebstoreUpload(options) {
125
- return new APIClient(options);
126
- }