zotero-plugin-scaffold 0.2.0 → 0.2.2

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/dist/cli.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import process from 'node:process';
2
2
  import { Command } from '@commander-js/extra-typings';
3
- import { E as ExitSignals, l as logger, C as Config, a as Build, S as Serve, T as Test, R as Release } from './shared/zotero-plugin-scaffold.DoUCzsKc.mjs';
3
+ import { E as ExitSignals, l as logger, C as Config, B as Build, S as Serve, T as Test, R as Release } from './shared/zotero-plugin-scaffold.V3TH4au2.mjs';
4
4
  import { readFile, writeFile } from 'node:fs/promises';
5
5
  import { pathExists, ensureFile } from 'fs-extra';
6
6
  import tinyUpdateNotifier from 'tiny-update-notifier';
@@ -18,8 +18,10 @@ import 'node:path';
18
18
  import 'adm-zip';
19
19
  import 'esbuild';
20
20
  import 'node:crypto';
21
+ import '@swc/core';
21
22
  import 'node:child_process';
22
23
  import 'bumpp';
24
+ import 'octokit';
23
25
  import 'chokidar';
24
26
  import 'node:net';
25
27
  import 'node:buffer';
@@ -30,7 +32,7 @@ import 'xvfb-ts';
30
32
 
31
33
  const name = "zotero-plugin-scaffold";
32
34
  const type = "module";
33
- const version = "0.2.0";
35
+ const version = "0.2.2";
34
36
  const packageManager = "pnpm@9.15.4";
35
37
  const description = "A scaffold for Zotero plugin development.";
36
38
  const author = "northword";
@@ -98,14 +100,15 @@ const peerDependenciesMeta = {
98
100
  }
99
101
  };
100
102
  const dependencies = {
101
- "@commander-js/extra-typings": "^13.0.0",
103
+ "@commander-js/extra-typings": "^13.1.0",
104
+ "@swc/core": "^1.10.12",
102
105
  "adm-zip": "^0.5.16",
103
- bumpp: "^9.10.2",
106
+ bumpp: "^10.0.1",
104
107
  c12: "^2.0.1",
105
108
  chalk: "^5.4.1",
106
109
  chokidar: "^4.0.3",
107
110
  commander: "^13.1.0",
108
- "es-toolkit": "^1.31.0",
111
+ "es-toolkit": "^1.32.0",
109
112
  esbuild: "^0.24.2",
110
113
  "fs-extra": "^11.3.0",
111
114
  hookable: "^5.5.3",
package/dist/index.d.mts CHANGED
@@ -295,6 +295,14 @@ interface BuildConfig {
295
295
  * @default true
296
296
  */
297
297
  prefixFluentMessages: boolean;
298
+ /**
299
+ * Messages to ignore.
300
+ *
301
+ * 需要忽略的 Fluent Messages。
302
+ *
303
+ * @default []
304
+ */
305
+ ignore: string[];
298
306
  };
299
307
  prefs: {
300
308
  /**
@@ -637,12 +645,6 @@ interface ReleaseConfig {
637
645
  comment: boolean;
638
646
  releaseNote: (ctx: Context) => string;
639
647
  };
640
- /**
641
- * Release to Gitee
642
- *
643
- * @todo Not implemented yet
644
- */
645
- gitee: ReleaseConfig["github"];
646
648
  hooks: Partial<ReleaseHooks>;
647
649
  }
648
650
  interface ReleaseHooks {
package/dist/index.d.ts CHANGED
@@ -295,6 +295,14 @@ interface BuildConfig {
295
295
  * @default true
296
296
  */
297
297
  prefixFluentMessages: boolean;
298
+ /**
299
+ * Messages to ignore.
300
+ *
301
+ * 需要忽略的 Fluent Messages。
302
+ *
303
+ * @default []
304
+ */
305
+ ignore: string[];
298
306
  };
299
307
  prefs: {
300
308
  /**
@@ -637,12 +645,6 @@ interface ReleaseConfig {
637
645
  comment: boolean;
638
646
  releaseNote: (ctx: Context) => string;
639
647
  };
640
- /**
641
- * Release to Gitee
642
- *
643
- * @todo Not implemented yet
644
- */
645
- gitee: ReleaseConfig["github"];
646
648
  hooks: Partial<ReleaseHooks>;
647
649
  }
648
650
  interface ReleaseHooks {
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { a as Build, C as Config, R as Release, S as Serve, T as Test, d as defineConfig } from './shared/zotero-plugin-scaffold.DoUCzsKc.mjs';
1
+ export { B as Build, C as Config, R as Release, S as Serve, T as Test, d as defineConfig } from './shared/zotero-plugin-scaffold.V3TH4au2.mjs';
2
2
  import 'c12';
3
3
  import 'es-toolkit';
4
4
  import 'fs-extra/esm';
@@ -15,8 +15,10 @@ import 'node:path';
15
15
  import 'adm-zip';
16
16
  import 'esbuild';
17
17
  import 'node:crypto';
18
+ import '@swc/core';
18
19
  import 'node:child_process';
19
20
  import 'bumpp';
21
+ import 'octokit';
20
22
  import 'chokidar';
21
23
  import 'node:net';
22
24
  import 'node:buffer';
@@ -8,14 +8,16 @@ import chalk from 'chalk';
8
8
  import { isDebug, isCI, isWindows, isMacOS, isLinux } from 'std-env';
9
9
  import { p as parseRepoUrl, d as dateFormat, t as template, b as toArray, a as replaceInFile } from './zotero-plugin-scaffold.v8dJZZ-v.mjs';
10
10
  import { createReadStream, existsSync } from 'node:fs';
11
- import { readFile, writeFile } from 'node:fs/promises';
12
- import { basename, dirname, join, resolve } from 'node:path';
11
+ import { readFile, writeFile, stat } from 'node:fs/promises';
12
+ import { basename, dirname, join, extname, resolve } from 'node:path';
13
13
  import AdmZip from 'adm-zip';
14
14
  import { build } from 'esbuild';
15
- import { glob } from 'tinyglobby';
15
+ import { glob, globSync } from 'tinyglobby';
16
16
  import { createHash } from 'node:crypto';
17
+ import { parseSync } from '@swc/core';
17
18
  import { execSync, spawn } from 'node:child_process';
18
19
  import { versionBump, ProgressEvent } from 'bumpp';
20
+ import { Octokit } from 'octokit';
19
21
  import chokidar from 'chokidar';
20
22
  import net from 'node:net';
21
23
  import { Buffer } from 'node:buffer';
@@ -161,7 +163,6 @@ function resolveConfig(config) {
161
163
  config.xpiDownloadLink = template(config.xpiDownloadLink, templateData);
162
164
  config.build.define = mapValues(config.build.define, (v) => template(v, templateData));
163
165
  config.release.github.repository = template(config.release.github.repository, templateData);
164
- config.release.gitee.repository = template(config.release.gitee.repository, templateData);
165
166
  const hooks = createHooks();
166
167
  hooks.addHooks(config.build.hooks);
167
168
  hooks.addHooks(config.server.hooks);
@@ -191,7 +192,8 @@ const defaultConfig = {
191
192
  define: {},
192
193
  fluent: {
193
194
  prefixFluentMessages: true,
194
- prefixLocaleFiles: true
195
+ prefixLocaleFiles: true,
196
+ ignore: []
195
197
  },
196
198
  prefs: {
197
199
  prefix: "",
@@ -250,15 +252,6 @@ const defaultConfig = {
250
252
  return ctx.release.changelog;
251
253
  }
252
254
  },
253
- gitee: {
254
- enable: "false",
255
- repository: "{{owner}}/{{repo}}",
256
- updater: "release",
257
- comment: false,
258
- releaseNote: (ctx) => {
259
- return ctx.release.changelog;
260
- }
261
- },
262
255
  hooks: {}
263
256
  },
264
257
  test: {
@@ -302,15 +295,50 @@ class PrefsManager {
302
295
  constructor(namespace) {
303
296
  this.namespace = namespace;
304
297
  }
305
- parse(content) {
306
- const _map = {};
307
- const prefPattern = /^(pref|user_pref)\s*\(\s*["']([^"']+)["']\s*,\s*(.+)\s*\)\s*;?$/gm;
308
- const matches = content.matchAll(prefPattern);
298
+ /**
299
+ * Parse Method 3 - Using AST
300
+ */
301
+ parse(content2) {
302
+ const _map2 = {};
303
+ const ast = parseSync(content2, { syntax: "ecmascript" });
304
+ for (const node of ast.body) {
305
+ if (node.type !== "ExpressionStatement" || node.expression.type !== "CallExpression" || node.expression.callee.type !== "Identifier" || node.expression.callee.value !== this.namespace || node.expression.arguments.length !== 2) {
306
+ throw new Error("Invalid prefs.js file.");
307
+ }
308
+ if (node.expression.arguments[0].expression.type !== "StringLiteral") {
309
+ throw new Error("Invalid prefs.js file - unsupported key type.");
310
+ }
311
+ if (node.expression.arguments[1].expression.type !== "StringLiteral" && node.expression.arguments[1].expression.type !== "NumericLiteral" && node.expression.arguments[1].expression.type !== "BooleanLiteral") {
312
+ throw new Error("Invalid prefs.js file - unsupported value type.");
313
+ }
314
+ const key = node.expression.arguments[0].expression.value.trim();
315
+ const value = node.expression.arguments[1].expression.value;
316
+ _map2[key] = value;
317
+ }
318
+ return _map2;
319
+ }
320
+ /**
321
+ * Parse Method 1 - Using RegExp
322
+ * @deprecated
323
+ */
324
+ parseByRegExp(content2) {
325
+ const _map2 = {};
326
+ const prefPattern = /^(pref|user_pref)\s*\(\s*["']([^"']+)["']\s*,\s*(.+)\s*,?\s*\)\s*;?$/gm;
327
+ const matches = content2.matchAll(prefPattern);
309
328
  for (const match of matches) {
310
329
  const key = match[2].trim();
311
330
  const value = match[3].trim();
312
- _map[key] = this.cleanValue(value);
331
+ _map2[key] = this.cleanValue(value);
313
332
  }
333
+ return _map2;
334
+ }
335
+ /**
336
+ * Parse Method 2 - Using eval
337
+ * @deprecated
338
+ */
339
+ parseByEval(content) {
340
+ const _map = {};
341
+ eval(content);
314
342
  return _map;
315
343
  }
316
344
  cleanValue(value) {
@@ -332,13 +360,13 @@ class PrefsManager {
332
360
  }).join("\n");
333
361
  }
334
362
  async read(path) {
335
- const content = await readFile(path, "utf-8");
336
- const map = this.parse(content);
363
+ const content2 = await readFile(path, "utf-8");
364
+ const map = this.parse(content2);
337
365
  this.setPrefs(map);
338
366
  }
339
367
  async write(path) {
340
- const content = this.render();
341
- await outputFile(path, content, "utf-8");
368
+ const content2 = this.render();
369
+ await outputFile(path, content2, "utf-8");
342
370
  logger.debug("The prefs.js has been modified.");
343
371
  }
344
372
  setPref(key, value) {
@@ -365,18 +393,18 @@ class PrefsManager {
365
393
  }
366
394
  getPrefsWithPrefix(prefix) {
367
395
  const _prefs = {};
368
- for (const pref in this.prefs) {
369
- if (pref.startsWith(prefix))
370
- _prefs[pref] = this.prefs[pref];
396
+ for (const pref2 in this.prefs) {
397
+ if (pref2.startsWith(prefix))
398
+ _prefs[pref2] = this.prefs[pref2];
371
399
  else
372
- _prefs[`${prefix}.${pref}`] = this.prefs[pref];
400
+ _prefs[`${prefix}.${pref2}`] = this.prefs[pref2];
373
401
  }
374
402
  return _prefs;
375
403
  }
376
404
  getPrefsWithoutPrefix(prefix) {
377
405
  const _prefs = {};
378
- for (const pref in this.prefs) {
379
- _prefs[pref.replace(`${prefix}.`, "")] = this.prefs[pref];
406
+ for (const pref2 in this.prefs) {
407
+ _prefs[pref2.replace(`${prefix}.`, "")] = this.prefs[pref2];
380
408
  }
381
409
  return _prefs;
382
410
  }
@@ -512,6 +540,7 @@ class Build extends Base {
512
540
  }
513
541
  async prepareLocaleFiles() {
514
542
  const { dist, namespace, build } = this.ctx;
543
+ const ignores = toArray(build.fluent.ignore);
515
544
  const FTL_MESSAGE_PATTERN = /^(?<message>[a-z]\S*)( *= *)(?<pattern>.*)$/gim;
516
545
  const HTML_DATAI10NID_PATTERN = new RegExp(`(data-l10n-id)="((?!${namespace})\\S*)"`, "g");
517
546
  const localePaths = await glob(`${dist}/addon/locale/*`, { onlyDirectories: true });
@@ -552,6 +581,10 @@ class Build extends Base {
552
581
  const matches = [...htmlContent.matchAll(HTML_DATAI10NID_PATTERN)];
553
582
  for (const match of matches) {
554
583
  const [matched, attrKey, attrVal] = match;
584
+ if (ignores.includes(attrVal)) {
585
+ this.logger.debug(`HTML data-i10n-id ${attrVal} is in ignore list, skip to namespace`);
586
+ continue;
587
+ }
555
588
  if (!allMessages.has(attrVal)) {
556
589
  this.logger.warn(`HTML data-i10n-id '${chalk.blue(attrVal)}' in ${chalk.gray(htmlPath)} do not exist in any FTL message, skip to namespace it.`);
557
590
  continue;
@@ -565,6 +598,8 @@ class Build extends Base {
565
598
  await writeFile(htmlPath, htmlContent);
566
599
  }));
567
600
  messagesInHTML.forEach((messageInHTML) => {
601
+ if (ignores.includes(messageInHTML))
602
+ return;
568
603
  const missingLocales = [...messagesByLocale.entries()].filter(([_, messages]) => !messages.has(messageInHTML)).map(([locale]) => locale);
569
604
  if (missingLocales.length > 0) {
570
605
  this.logger.warn(`HTML data-l10n-id '${chalk.blue(messageInHTML)}' is missing in locales: ${missingLocales.join(", ")}.`);
@@ -734,6 +769,177 @@ class Bump extends Base {
734
769
  }
735
770
  }
736
771
 
772
+ const mimeTypes = {
773
+ json: "application/json",
774
+ xpi: "application/x-xpinstall"
775
+ };
776
+ function getMimeTypeByFileName(filename) {
777
+ const ext = extname(filename);
778
+ for (const type in mimeTypes) {
779
+ if (ext === `.${type}`)
780
+ return mimeTypes[type];
781
+ }
782
+ return undefined;
783
+ }
784
+
785
+ class ReleaseBase extends Base {
786
+ isCI;
787
+ constructor(ctx) {
788
+ super(ctx);
789
+ this.isCI = isCI;
790
+ }
791
+ checkFiles() {
792
+ const { dist } = this.ctx;
793
+ if (globSync(`${dist}/*.xpi`).length === 0) {
794
+ throw new Error("No xpi file found, are you sure you have run build?");
795
+ }
796
+ }
797
+ }
798
+
799
+ class GitHub extends ReleaseBase {
800
+ client;
801
+ constructor(ctx) {
802
+ super(ctx);
803
+ this.client = this.getClient();
804
+ }
805
+ async run() {
806
+ this.checkFiles();
807
+ this.logger.info("Uploading XPI to GitHub...");
808
+ await this.uploadXPI();
809
+ this.logger.info("Refreshing update manifest...");
810
+ await this.refreshUpdateManifest();
811
+ return this.ctx;
812
+ }
813
+ /**
814
+ * Create new release and upload XPI to asset
815
+ */
816
+ async uploadXPI() {
817
+ const { version, dist, xpiName } = this.ctx;
818
+ const release = await this.createRelease({
819
+ ...this.remote,
820
+ tag_name: this.ctx.release.bumpp.tag.toString().replaceAll("%s", version),
821
+ name: `Release v${version}`,
822
+ body: await this.getChangelog(),
823
+ prerelease: version.includes("-"),
824
+ make_latest: "true"
825
+ });
826
+ if (!release)
827
+ throw new Error("Create release failed!");
828
+ this.logger.debug("Uploading xpi asset...");
829
+ await this.uploadAsset(release.id, join(dist, `${xpiName}.xpi`));
830
+ }
831
+ async getReleaseByTag(tag) {
832
+ return await this.client.rest.repos.getReleaseByTag({
833
+ ...this.remote,
834
+ tag
835
+ }).catch((e) => {
836
+ this.logger.debug(`Release with tag ${tag} not found. ${e}`);
837
+ return undefined;
838
+ }).then((res) => {
839
+ if (res && res.status === 200) {
840
+ this.logger.debug(`Found release with tag "${tag}", id=${res.data.id}.`);
841
+ return res.data;
842
+ }
843
+ });
844
+ }
845
+ async createRelease(options) {
846
+ this.logger.debug("Creating release...", options);
847
+ return await this.client.rest.repos.createRelease(options).catch((e) => {
848
+ this.logger.error(e);
849
+ throw new Error("Create release failed.");
850
+ }).then((res) => {
851
+ if (res.status === 201) {
852
+ this.logger.debug(`Create release "${res.data.tag_name}" success, id: ${res.data.id}.`);
853
+ return res.data;
854
+ }
855
+ });
856
+ }
857
+ async uploadAsset(releaseID, asset) {
858
+ this.logger.debug(`Uploading ${asset} to release ${releaseID}`);
859
+ return await this.client.rest.repos.uploadReleaseAsset({
860
+ ...this.remote,
861
+ release_id: releaseID,
862
+ data: await readFile(asset),
863
+ headers: {
864
+ "content-type": getMimeTypeByFileName(asset) || "application/octet-stream",
865
+ "content-length": (await stat(asset)).size
866
+ },
867
+ name: basename(asset)
868
+ }).then((res) => {
869
+ this.logger.debug(`Upload "${res.data.name}" success, assetId: ${res.data.id}`);
870
+ return res.data;
871
+ });
872
+ }
873
+ async refreshUpdateManifest() {
874
+ const updater = this.ctx.release.github.updater;
875
+ if (!updater) {
876
+ this.logger.debug(`Skip refresh update.json because release.github.updater = false`);
877
+ return;
878
+ }
879
+ const { dist, version } = this.ctx;
880
+ const assets = (await glob(`${dist}/update*.json`)).map((p) => basename(p));
881
+ const release = await this.getReleaseByTag(updater) ?? await this.createRelease({
882
+ ...this.remote,
883
+ tag_name: updater,
884
+ prerelease: true,
885
+ make_latest: "false"
886
+ });
887
+ if (!release)
888
+ throw new Error("Get or create 'release' failed.");
889
+ const existAssets = await this.client.rest.repos.listReleaseAssets({
890
+ ...this.remote,
891
+ release_id: release.id
892
+ }).then((res) => {
893
+ return res.data.filter((asset) => assets.includes(asset.name));
894
+ });
895
+ if (existAssets) {
896
+ for (const existAsset of existAssets) {
897
+ if (assets.includes(existAsset.name)) {
898
+ this.logger.debug(`Delete existed asset ${existAsset.name} in release ${updater}`);
899
+ await this.client.rest.repos.deleteReleaseAsset({
900
+ ...this.remote,
901
+ asset_id: existAsset.id
902
+ });
903
+ }
904
+ }
905
+ }
906
+ for (const asset of assets) {
907
+ await this.uploadAsset(release.id, join(dist, asset));
908
+ }
909
+ await this.client.rest.repos.updateRelease({
910
+ ...this.remote,
911
+ release_id: release.id,
912
+ name: "Release Manifest",
913
+ body: `This release is used to host \`update.json\`, please do not delete or modify it!
914
+ Updated in UTC ${(/* @__PURE__ */ new Date()).toISOString()} for version ${version}`,
915
+ prerelease: true,
916
+ make_latest: "false"
917
+ });
918
+ }
919
+ async getChangelog() {
920
+ const { release } = this.ctx;
921
+ const { github } = release;
922
+ const { releaseNote } = github;
923
+ return releaseNote(this.ctx);
924
+ }
925
+ getClient() {
926
+ if (!process.env.GITHUB_TOKEN)
927
+ throw new Error("No GITHUB_TOKEN.");
928
+ const client = new Octokit({
929
+ auth: process.env.GITHUB_TOKEN,
930
+ userAgent: "zotero-plugin-scaffold"
931
+ });
932
+ return client;
933
+ }
934
+ get remote() {
935
+ const [owner, repo] = this.ctx.release.github.repository.split("/");
936
+ return {
937
+ owner,
938
+ repo
939
+ };
940
+ }
941
+ }
942
+
737
943
  class Release extends Base {
738
944
  constructor(ctx) {
739
945
  super(ctx);
@@ -758,8 +964,7 @@ class Release extends Base {
758
964
  }
759
965
  const isBumpNeeded = this.ctx.release.bumpp.release !== version;
760
966
  const isGitHubEnabled = this.isEnabled(release.github.enable);
761
- const isGiteeEnabled = this.isEnabled(release.gitee.enable);
762
- const isPublishNeeded = isGitHubEnabled || isGiteeEnabled;
967
+ const isPublishNeeded = isGitHubEnabled;
763
968
  if (isPublishNeeded && !release.bumpp.execute) {
764
969
  this.logger.warn(`The current release needs to run the build after bumping the version number, please configure the build script in 'config.release.bumpp.execute'${isBumpNeeded ? "" : " or run build before run release"}.`);
765
970
  this.ctx.release.bumpp.execute ||= "npm run build";
@@ -770,7 +975,6 @@ class Release extends Base {
770
975
  await this.ctx.hooks.callHook("release:push", this.ctx);
771
976
  this.ctx.release.changelog = await this.getChangelog();
772
977
  if (isGitHubEnabled) {
773
- const { default: GitHub } = await import('../chunks/github.mjs');
774
978
  await new GitHub(this.ctx).run();
775
979
  }
776
980
  await this.ctx.hooks.callHook("release:done", this.ctx);
@@ -2318,4 +2522,4 @@ const Config = {
2318
2522
  loadConfig
2319
2523
  };
2320
2524
 
2321
- export { Base as B, Config as C, ExitSignals as E, Release as R, Serve as S, Test as T, Build as a, defineConfig as d, logger as l };
2525
+ export { Build as B, Config as C, ExitSignals as E, Release as R, Serve as S, Test as T, defineConfig as d, logger as l };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zotero-plugin-scaffold",
3
3
  "type": "module",
4
- "version": "0.2.0",
4
+ "version": "0.2.2",
5
5
  "description": "A scaffold for Zotero plugin development.",
6
6
  "author": "northword",
7
7
  "license": "AGPL-3.0-or-later",
@@ -63,14 +63,15 @@
63
63
  }
64
64
  },
65
65
  "dependencies": {
66
- "@commander-js/extra-typings": "^13.0.0",
66
+ "@commander-js/extra-typings": "^13.1.0",
67
+ "@swc/core": "^1.10.12",
67
68
  "adm-zip": "^0.5.16",
68
- "bumpp": "^9.10.2",
69
+ "bumpp": "^10.0.1",
69
70
  "c12": "^2.0.1",
70
71
  "chalk": "^5.4.1",
71
72
  "chokidar": "^4.0.3",
72
73
  "commander": "^13.1.0",
73
- "es-toolkit": "^1.31.0",
74
+ "es-toolkit": "^1.32.0",
74
75
  "esbuild": "^0.24.2",
75
76
  "fs-extra": "^11.3.0",
76
77
  "hookable": "^5.5.3",
@@ -1,200 +0,0 @@
1
- import { readFile, stat } from 'node:fs/promises';
2
- import { extname, join, basename } from 'node:path';
3
- import process from 'node:process';
4
- import { Octokit } from 'octokit';
5
- import { globSync, glob } from 'tinyglobby';
6
- import { isCI } from 'std-env';
7
- import { B as Base } from '../shared/zotero-plugin-scaffold.DoUCzsKc.mjs';
8
- import 'c12';
9
- import 'es-toolkit';
10
- import 'fs-extra/esm';
11
- import 'hookable';
12
- import 'node:readline';
13
- import 'chalk';
14
- import '../shared/zotero-plugin-scaffold.v8dJZZ-v.mjs';
15
- import 'node:fs';
16
- import 'adm-zip';
17
- import 'esbuild';
18
- import 'node:crypto';
19
- import 'node:child_process';
20
- import 'bumpp';
21
- import 'chokidar';
22
- import 'node:net';
23
- import 'node:buffer';
24
- import 'node:domain';
25
- import 'node:events';
26
- import 'node:http';
27
- import 'xvfb-ts';
28
-
29
- const mimeTypes = {
30
- "application/json": ["json"],
31
- "application/x-xpinstall": ["xpi"]
32
- };
33
- function getMimeTypeByFileName(filename) {
34
- const ext = extname(filename);
35
- for (const type in mimeTypes) {
36
- if (mimeTypes[type].includes(ext))
37
- return type;
38
- }
39
- return undefined;
40
- }
41
-
42
- class ReleaseBase extends Base {
43
- isCI;
44
- constructor(ctx) {
45
- super(ctx);
46
- this.isCI = isCI;
47
- }
48
- checkFiles() {
49
- const { dist } = this.ctx;
50
- if (globSync(`${dist}/*.xpi`).length === 0) {
51
- throw new Error("No xpi file found, are you sure you have run build?");
52
- }
53
- }
54
- }
55
-
56
- class GitHub extends ReleaseBase {
57
- client;
58
- constructor(ctx) {
59
- super(ctx);
60
- this.client = this.getClient();
61
- }
62
- async run() {
63
- this.checkFiles();
64
- this.logger.info("Uploading XPI to GitHub...");
65
- await this.uploadXPI();
66
- this.logger.info("Refreshing update manifest...");
67
- await this.refreshUpdateManifest();
68
- return this.ctx;
69
- }
70
- /**
71
- * Create new release and upload XPI to asset
72
- */
73
- async uploadXPI() {
74
- const { version, dist, xpiName } = this.ctx;
75
- const release = await this.createRelease({
76
- ...this.remote,
77
- tag_name: this.ctx.release.bumpp.tag.toString().replaceAll("%s", version),
78
- name: `Release v${version}`,
79
- body: await this.getChangelog(),
80
- prerelease: version.includes("-"),
81
- make_latest: "true"
82
- });
83
- if (!release)
84
- throw new Error("Create release failed!");
85
- this.logger.debug("Uploading xpi asset...");
86
- await this.uploadAsset(release.id, join(dist, `${xpiName}.xpi`));
87
- }
88
- async getReleaseByTag(tag) {
89
- return await this.client.rest.repos.getReleaseByTag({
90
- ...this.remote,
91
- tag
92
- }).catch((e) => {
93
- this.logger.debug(`Release with tag ${tag} not found. ${e}`);
94
- return undefined;
95
- }).then((res) => {
96
- if (res && res.status === 200) {
97
- this.logger.debug(`Found release with tag "${tag}", id=${res.data.id}.`);
98
- return res.data;
99
- }
100
- });
101
- }
102
- async createRelease(options) {
103
- this.logger.debug("Creating release...", options);
104
- return await this.client.rest.repos.createRelease(options).catch((e) => {
105
- this.logger.error(e);
106
- throw new Error("Create release failed.");
107
- }).then((res) => {
108
- if (res.status === 201) {
109
- this.logger.debug(`Create release "${res.data.tag_name}" success, id: ${res.data.id}.`);
110
- return res.data;
111
- }
112
- });
113
- }
114
- async uploadAsset(releaseID, asset) {
115
- this.logger.debug(`Uploading ${asset} to release ${releaseID}`);
116
- return await this.client.rest.repos.uploadReleaseAsset({
117
- ...this.remote,
118
- release_id: releaseID,
119
- data: await readFile(asset),
120
- headers: {
121
- "content-type": getMimeTypeByFileName(asset) || "application/octet-stream",
122
- "content-length": (await stat(asset)).size
123
- },
124
- name: basename(asset)
125
- }).then((res) => {
126
- this.logger.debug(`Upload "${res.data.name}" success, assetId: ${res.data.id}`);
127
- return res.data;
128
- });
129
- }
130
- async refreshUpdateManifest() {
131
- const updater = this.ctx.release.github.updater;
132
- if (!updater) {
133
- this.logger.debug(`Skip refresh update.json because release.github.updater = false`);
134
- return;
135
- }
136
- const { dist, version } = this.ctx;
137
- const assets = (await glob(`${dist}/update*.json`)).map((p) => basename(p));
138
- const release = await this.getReleaseByTag(updater) ?? await this.createRelease({
139
- ...this.remote,
140
- tag_name: updater,
141
- prerelease: true,
142
- make_latest: "false"
143
- });
144
- if (!release)
145
- throw new Error("Get or create 'release' failed.");
146
- const existAssets = await this.client.rest.repos.listReleaseAssets({
147
- ...this.remote,
148
- release_id: release.id
149
- }).then((res) => {
150
- return res.data.filter((asset) => assets.includes(asset.name));
151
- });
152
- if (existAssets) {
153
- for (const existAsset of existAssets) {
154
- if (assets.includes(existAsset.name)) {
155
- this.logger.debug(`Delete existed asset ${existAsset.name} in release ${updater}`);
156
- await this.client.rest.repos.deleteReleaseAsset({
157
- ...this.remote,
158
- asset_id: existAsset.id
159
- });
160
- }
161
- }
162
- }
163
- for (const asset of assets) {
164
- await this.uploadAsset(release.id, join(dist, asset));
165
- }
166
- await this.client.rest.repos.updateRelease({
167
- ...this.remote,
168
- release_id: release.id,
169
- name: "Release Manifest",
170
- body: `This release is used to host \`update.json\`, please do not delete or modify it!
171
- Updated in UTC ${(/* @__PURE__ */ new Date()).toISOString()} for version ${version}`,
172
- prerelease: true,
173
- make_latest: "false"
174
- });
175
- }
176
- async getChangelog() {
177
- const { release } = this.ctx;
178
- const { github } = release;
179
- const { releaseNote } = github;
180
- return releaseNote(this.ctx);
181
- }
182
- getClient() {
183
- if (!process.env.GITHUB_TOKEN)
184
- throw new Error("No GITHUB_TOKEN.");
185
- const client = new Octokit({
186
- auth: process.env.GITHUB_TOKEN,
187
- userAgent: "zotero-plugin-scaffold"
188
- });
189
- return client;
190
- }
191
- get remote() {
192
- const [owner, repo] = this.ctx.release.github.repository.split("/");
193
- return {
194
- owner,
195
- repo
196
- };
197
- }
198
- }
199
-
200
- export { GitHub as default };