chrome-webstore-upload 4.0.0-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 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
- 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
- };
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(readStream: ReadStream | ReadableStream, token?: string | Promise<string>, maxAwaitInProgressResponseSeconds?: number): Promise<ItemResource>;
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 throwIfNotOk(request, response) {
19
- if (!request.ok) {
20
- const error = new Error(request.statusText ?? 'Unknown error');
21
- error.response = response;
22
- throw error;
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(readStream, token = this.fetchToken(), maxAwaitInProgressResponseSeconds = 0) {
48
- if (!readStream) {
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": "4.0.0-0",
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": ">=18"
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 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/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
+ }