chrome-webstore-upload 3.2.0 → 4.0.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.
- package/index.d.ts +4 -24
- package/index.js +15 -8
- package/package.json +10 -3
- package/readme.md +18 -1
- package/zip-dir.d.ts +1 -0
- package/zip-dir.js +17 -0
package/index.d.ts
CHANGED
|
@@ -1,34 +1,15 @@
|
|
|
1
1
|
import { type ReadStream } from 'node:fs';
|
|
2
|
+
import type { APIClientOptions, ItemResource, PublishResponse } from './types.js';
|
|
2
3
|
export declare const refreshTokenURI = "https://www.googleapis.com/oauth2/v4/token";
|
|
3
|
-
export type APIClientOptions
|
|
4
|
-
|
|
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
|
-
};
|
|
4
|
+
export type { APIClientOptions, ItemResource, PublishResponse } from './types.js';
|
|
5
|
+
export { CWSError } from './errors.js';
|
|
25
6
|
declare class APIClient {
|
|
26
7
|
extensionId: string;
|
|
27
8
|
clientId: string;
|
|
28
9
|
refreshToken: string;
|
|
29
10
|
clientSecret: string | undefined;
|
|
30
11
|
constructor(options: APIClientOptions);
|
|
31
|
-
uploadExisting(
|
|
12
|
+
uploadExisting(streamOrPath: ReadStream | ReadableStream | string, token?: string | Promise<string>, maxAwaitInProgressResponseSeconds?: number): Promise<ItemResource>;
|
|
32
13
|
publish(target?: string, token?: string | Promise<string>, deployPercentage?: number | undefined): Promise<PublishResponse>;
|
|
33
14
|
get(projection?: string, token?: string | Promise<string>): Promise<ItemResource>;
|
|
34
15
|
fetchToken(): Promise<string>;
|
|
@@ -39,4 +20,3 @@ declare class APIClient {
|
|
|
39
20
|
};
|
|
40
21
|
}
|
|
41
22
|
export default function chromeWebstoreUpload(options: APIClientOptions): APIClient;
|
|
42
|
-
export {};
|
package/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// API documentation:
|
|
2
2
|
// https://developer.chrome.com/docs/webstore/api
|
|
3
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';
|
|
4
7
|
const rootURI = 'https://www.googleapis.com';
|
|
5
8
|
export const refreshTokenURI = 'https://www.googleapis.com/oauth2/v4/token';
|
|
6
9
|
const uploadExistingURI = (id) => `${rootURI}/upload/chromewebstore/v1.1/items/${id}`;
|
|
@@ -15,13 +18,13 @@ const publishURI = ({ extensionId, target = 'default', deployPercentage }) => {
|
|
|
15
18
|
const getURI = (id, projection) => `${rootURI}/chromewebstore/v1.1/items/${id}?projection=${projection}`;
|
|
16
19
|
const requiredFields = ['extensionId', 'clientId', 'refreshToken'];
|
|
17
20
|
const retryIntervalSeconds = 2;
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
21
|
+
async function getStreamFromPath(filepath) {
|
|
22
|
+
const stats = await fs.promises.stat(filepath);
|
|
23
|
+
return stats.isFile()
|
|
24
|
+
? fs.createReadStream(filepath)
|
|
25
|
+
: zipStreamFromDirectory(filepath);
|
|
24
26
|
}
|
|
27
|
+
export { CWSError } from './errors.js';
|
|
25
28
|
class APIClient {
|
|
26
29
|
extensionId;
|
|
27
30
|
clientId;
|
|
@@ -44,10 +47,14 @@ class APIClient {
|
|
|
44
47
|
this.refreshToken = options.refreshToken;
|
|
45
48
|
this.clientSecret = options.clientSecret;
|
|
46
49
|
}
|
|
47
|
-
async uploadExisting(
|
|
48
|
-
if (!
|
|
50
|
+
async uploadExisting(streamOrPath, token = this.fetchToken(), maxAwaitInProgressResponseSeconds = 0) {
|
|
51
|
+
if (!streamOrPath) {
|
|
49
52
|
throw new Error('Read stream missing');
|
|
50
53
|
}
|
|
54
|
+
// Convert string path (file or directory) to stream
|
|
55
|
+
const readStream = typeof streamOrPath === 'string'
|
|
56
|
+
? await getStreamFromPath(streamOrPath)
|
|
57
|
+
: streamOrPath;
|
|
51
58
|
const { extensionId } = this;
|
|
52
59
|
const request = await fetch(uploadExistingURI(extensionId), {
|
|
53
60
|
method: 'PUT',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-webstore-upload",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Upload Chrome Extensions to the Chrome Web Store",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chrome",
|
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
"types": "./index.d.ts",
|
|
25
25
|
"files": [
|
|
26
26
|
"index.js",
|
|
27
|
-
"index.d.ts"
|
|
27
|
+
"index.d.ts",
|
|
28
|
+
"zip-dir.js",
|
|
29
|
+
"zip-dir.d.ts"
|
|
28
30
|
],
|
|
29
31
|
"scripts": {
|
|
30
32
|
"prebundle": "dot-json test/extension/manifest.json version $(utc-version)",
|
|
@@ -63,6 +65,7 @@
|
|
|
63
65
|
},
|
|
64
66
|
"devDependencies": {
|
|
65
67
|
"@sindresorhus/tsconfig": "^5.0.0",
|
|
68
|
+
"@types/yazl": "^3.3.0",
|
|
66
69
|
"dot-json": "^1.3.0",
|
|
67
70
|
"fetch-mock": "^9.11.0",
|
|
68
71
|
"node-fetch": "^2.7.0",
|
|
@@ -72,9 +75,13 @@
|
|
|
72
75
|
"xo": "^0.58.0"
|
|
73
76
|
},
|
|
74
77
|
"engines": {
|
|
75
|
-
"node": ">=
|
|
78
|
+
"node": ">=20"
|
|
76
79
|
},
|
|
77
80
|
"webExt": {
|
|
78
81
|
"sourceDir": "test/extension"
|
|
82
|
+
},
|
|
83
|
+
"dependencies": {
|
|
84
|
+
"junk": "^4.0.1",
|
|
85
|
+
"yazl": "^3.3.1"
|
|
79
86
|
}
|
|
80
87
|
}
|
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/zip-dir.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function zipStreamFromDirectory(directory: string): Promise<NodeJS.ReadableStream>;
|
package/zip-dir.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
}
|