chrome-webstore-upload 4.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 +4 -7
- package/index.d.ts +0 -22
- package/index.js +0 -133
- package/zip-dir.d.ts +0 -1
- package/zip-dir.js +0 -17
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,13 +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",
|
|
28
|
-
"zip-dir.js",
|
|
29
|
-
"zip-dir.d.ts"
|
|
26
|
+
"distribution"
|
|
30
27
|
],
|
|
31
28
|
"scripts": {
|
|
32
29
|
"prebundle": "dot-json test/extension/manifest.json version $(utc-version)",
|
package/index.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { type ReadStream } from 'node:fs';
|
|
2
|
-
import type { APIClientOptions, ItemResource, PublishResponse } from './types.js';
|
|
3
|
-
export declare const refreshTokenURI = "https://www.googleapis.com/oauth2/v4/token";
|
|
4
|
-
export type { APIClientOptions, ItemResource, PublishResponse } from './types.js';
|
|
5
|
-
export { CWSError } from './errors.js';
|
|
6
|
-
declare class APIClient {
|
|
7
|
-
extensionId: string;
|
|
8
|
-
clientId: string;
|
|
9
|
-
refreshToken: string;
|
|
10
|
-
clientSecret: string | undefined;
|
|
11
|
-
constructor(options: APIClientOptions);
|
|
12
|
-
uploadExisting(streamOrPath: ReadStream | ReadableStream | string, token?: string | Promise<string>, maxAwaitInProgressResponseSeconds?: number): Promise<ItemResource>;
|
|
13
|
-
publish(target?: string, token?: string | Promise<string>, deployPercentage?: number | undefined): Promise<PublishResponse>;
|
|
14
|
-
get(projection?: string, token?: string | Promise<string>): Promise<ItemResource>;
|
|
15
|
-
fetchToken(): Promise<string>;
|
|
16
|
-
_waitUploadSuccess(response: ItemResource, maxAwaitInProgressResponseSeconds: number): Promise<ItemResource>;
|
|
17
|
-
_headers(token: string): {
|
|
18
|
-
Authorization: string;
|
|
19
|
-
'x-goog-api-version': string;
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
export default function chromeWebstoreUpload(options: APIClientOptions): APIClient;
|
package/index.js
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
// API documentation:
|
|
2
|
-
// https://developer.chrome.com/docs/webstore/api
|
|
3
|
-
// https://developer.chrome.com/docs/webstore/using-api
|
|
4
|
-
import fs from 'node:fs';
|
|
5
|
-
import { throwIfNotOk } from './errors.js';
|
|
6
|
-
import zipStreamFromDirectory from './zip-dir.js';
|
|
7
|
-
const rootURI = 'https://www.googleapis.com';
|
|
8
|
-
export const refreshTokenURI = 'https://www.googleapis.com/oauth2/v4/token';
|
|
9
|
-
const uploadExistingURI = (id) => `${rootURI}/upload/chromewebstore/v1.1/items/${id}`;
|
|
10
|
-
const publishURI = ({ extensionId, target = 'default', deployPercentage }) => {
|
|
11
|
-
const url = new URL(`${rootURI}/chromewebstore/v1.1/items/${extensionId}/publish`);
|
|
12
|
-
url.searchParams.set('publishTarget', target);
|
|
13
|
-
if (deployPercentage !== undefined) {
|
|
14
|
-
url.searchParams.set('deployPercentage', String(deployPercentage));
|
|
15
|
-
}
|
|
16
|
-
return url.href;
|
|
17
|
-
};
|
|
18
|
-
const getURI = (id, projection) => `${rootURI}/chromewebstore/v1.1/items/${id}?projection=${projection}`;
|
|
19
|
-
const requiredFields = ['extensionId', 'clientId', 'refreshToken'];
|
|
20
|
-
const retryIntervalSeconds = 2;
|
|
21
|
-
async function getStreamFromPath(filepath) {
|
|
22
|
-
const stats = await fs.promises.stat(filepath);
|
|
23
|
-
return stats.isFile()
|
|
24
|
-
? fs.createReadStream(filepath)
|
|
25
|
-
: zipStreamFromDirectory(filepath);
|
|
26
|
-
}
|
|
27
|
-
export { CWSError } from './errors.js';
|
|
28
|
-
class APIClient {
|
|
29
|
-
extensionId;
|
|
30
|
-
clientId;
|
|
31
|
-
refreshToken;
|
|
32
|
-
clientSecret;
|
|
33
|
-
constructor(options) {
|
|
34
|
-
if (typeof fetch !== 'function') {
|
|
35
|
-
throw new TypeError('`chrome-webstore-upload` requires Node.js 18.17 or newer because it relies on the global `fetch` function.');
|
|
36
|
-
}
|
|
37
|
-
if (typeof options !== 'object') {
|
|
38
|
-
throw new TypeError('The options object is required');
|
|
39
|
-
}
|
|
40
|
-
for (const field of requiredFields) {
|
|
41
|
-
if (!options[field]) {
|
|
42
|
-
throw new Error(`Option "${field}" is required`);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
this.extensionId = options.extensionId;
|
|
46
|
-
this.clientId = options.clientId;
|
|
47
|
-
this.refreshToken = options.refreshToken;
|
|
48
|
-
this.clientSecret = options.clientSecret;
|
|
49
|
-
}
|
|
50
|
-
async uploadExisting(streamOrPath, token = this.fetchToken(), maxAwaitInProgressResponseSeconds = 0) {
|
|
51
|
-
if (!streamOrPath) {
|
|
52
|
-
throw new Error('Read stream missing');
|
|
53
|
-
}
|
|
54
|
-
// Convert string path (file or directory) to stream
|
|
55
|
-
const readStream = typeof streamOrPath === 'string'
|
|
56
|
-
? await getStreamFromPath(streamOrPath)
|
|
57
|
-
: streamOrPath;
|
|
58
|
-
const { extensionId } = this;
|
|
59
|
-
const request = await fetch(uploadExistingURI(extensionId), {
|
|
60
|
-
method: 'PUT',
|
|
61
|
-
headers: this._headers(await token),
|
|
62
|
-
// @ts-expect-error Node extension? 🤷♂️ Required https://github.com/nodejs/node/issues/46221
|
|
63
|
-
duplex: 'half',
|
|
64
|
-
// Until they figure it out, this seems to work. Alternatively use https://stackoverflow.com/a/76780381/288906
|
|
65
|
-
body: readStream,
|
|
66
|
-
});
|
|
67
|
-
const response = await request.json();
|
|
68
|
-
throwIfNotOk(request, response);
|
|
69
|
-
return this._waitUploadSuccess(response, maxAwaitInProgressResponseSeconds);
|
|
70
|
-
}
|
|
71
|
-
async publish(target = 'default', token = this.fetchToken(), deployPercentage = undefined) {
|
|
72
|
-
const { extensionId } = this;
|
|
73
|
-
const request = await fetch(publishURI({ extensionId, target, deployPercentage }), {
|
|
74
|
-
method: 'POST',
|
|
75
|
-
headers: this._headers(await token),
|
|
76
|
-
});
|
|
77
|
-
const response = await request.json();
|
|
78
|
-
throwIfNotOk(request, response);
|
|
79
|
-
return response;
|
|
80
|
-
}
|
|
81
|
-
async get(projection = 'DRAFT', token = this.fetchToken()) {
|
|
82
|
-
const { extensionId } = this;
|
|
83
|
-
const request = await fetch(getURI(extensionId, projection), {
|
|
84
|
-
method: 'GET',
|
|
85
|
-
headers: this._headers(await token),
|
|
86
|
-
});
|
|
87
|
-
const response = await request.json();
|
|
88
|
-
throwIfNotOk(request, response);
|
|
89
|
-
return response;
|
|
90
|
-
}
|
|
91
|
-
async fetchToken() {
|
|
92
|
-
const { clientId, clientSecret, refreshToken } = this;
|
|
93
|
-
const json = {
|
|
94
|
-
client_id: clientId,
|
|
95
|
-
refresh_token: refreshToken,
|
|
96
|
-
grant_type: 'refresh_token',
|
|
97
|
-
client_secret: clientSecret,
|
|
98
|
-
};
|
|
99
|
-
if (!clientSecret) {
|
|
100
|
-
delete json.client_secret;
|
|
101
|
-
}
|
|
102
|
-
const request = await fetch(refreshTokenURI, {
|
|
103
|
-
method: 'POST',
|
|
104
|
-
body: JSON.stringify(json),
|
|
105
|
-
headers: {
|
|
106
|
-
'Content-Type': 'application/json',
|
|
107
|
-
},
|
|
108
|
-
});
|
|
109
|
-
const response = await request.json();
|
|
110
|
-
throwIfNotOk(request, response);
|
|
111
|
-
return response.access_token;
|
|
112
|
-
}
|
|
113
|
-
async _waitUploadSuccess(response, maxAwaitInProgressResponseSeconds) {
|
|
114
|
-
if (response.uploadState !== 'IN_PROGRESS' || maxAwaitInProgressResponseSeconds < retryIntervalSeconds) {
|
|
115
|
-
return response;
|
|
116
|
-
}
|
|
117
|
-
// Wait before checking again
|
|
118
|
-
await new Promise(resolve => {
|
|
119
|
-
setTimeout(resolve, retryIntervalSeconds * 1000);
|
|
120
|
-
});
|
|
121
|
-
// Retry fetching the item resource
|
|
122
|
-
return this._waitUploadSuccess(await this.get('DRAFT'), maxAwaitInProgressResponseSeconds - retryIntervalSeconds);
|
|
123
|
-
}
|
|
124
|
-
_headers(token) {
|
|
125
|
-
return {
|
|
126
|
-
Authorization: `Bearer ${token}`,
|
|
127
|
-
'x-goog-api-version': '2',
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
export default function chromeWebstoreUpload(options) {
|
|
132
|
-
return new APIClient(options);
|
|
133
|
-
}
|
package/zip-dir.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default function zipStreamFromDirectory(directory: string): Promise<NodeJS.ReadableStream>;
|
package/zip-dir.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { readdir } from 'node:fs/promises';
|
|
2
|
-
import { basename, join } from 'node:path';
|
|
3
|
-
import { isNotJunk } from 'junk';
|
|
4
|
-
import yazl from 'yazl';
|
|
5
|
-
export default async function zipStreamFromDirectory(directory) {
|
|
6
|
-
const allFiles = await readdir(directory, { recursive: true });
|
|
7
|
-
const files = allFiles.filter(file => isNotJunk(basename(file)));
|
|
8
|
-
if (!files.includes('manifest.json')) {
|
|
9
|
-
throw new Error(`manifest.json was not found in ${directory}`);
|
|
10
|
-
}
|
|
11
|
-
const zip = new yazl.ZipFile();
|
|
12
|
-
for (const file of files) {
|
|
13
|
-
zip.addFile(join(directory, file), file);
|
|
14
|
-
}
|
|
15
|
-
zip.end();
|
|
16
|
-
return zip.outputStream;
|
|
17
|
-
}
|