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.
- package/package.json +10 -6
- package/readme.md +18 -1
- package/index.d.ts +0 -42
- 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.
|
|
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
|
-
"
|
|
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": ">=
|
|
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
|
|
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
|
-
}
|