zotero-plugin-scaffold 0.0.4
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/README.md +168 -0
- package/bin/zotero-plugin.mjs +15 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +81 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.js +174 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/lib/build.d.ts +25 -0
- package/dist/lib/build.js +194 -0
- package/dist/lib/create.d.ts +19 -0
- package/dist/lib/create.js +40 -0
- package/dist/lib/lint.d.ts +6 -0
- package/dist/lib/lint.js +9 -0
- package/dist/lib/release.d.ts +255 -0
- package/dist/lib/release.js +165 -0
- package/dist/lib/serve.d.ts +22 -0
- package/dist/lib/serve.js +233 -0
- package/dist/types.d.ts +346 -0
- package/dist/types.js +1 -0
- package/dist/utils/crypto.d.ts +2 -0
- package/dist/utils/crypto.js +23 -0
- package/dist/utils/libBase.d.ts +15 -0
- package/dist/utils/libBase.js +33 -0
- package/dist/utils/log.d.ts +11 -0
- package/dist/utils/log.js +52 -0
- package/dist/utils/process.d.ts +1 -0
- package/dist/utils/process.js +21 -0
- package/dist/utils/string.d.ts +1 -0
- package/dist/utils/string.js +18 -0
- package/docs/.vitepress/.gitkeep +0 -0
- package/docs/index.md +0 -0
- package/package.json +101 -0
- package/types/release-it.d.ts +104 -0
- package/types/web-ext.d.ts +54 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { generateHashSync } from "../utils/crypto.js";
|
|
2
|
+
import { LibBase } from "../utils/libBase.js";
|
|
3
|
+
import { dateFormat } from "../utils/string.js";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { zip } from "compressing";
|
|
6
|
+
import { buildSync } from "esbuild";
|
|
7
|
+
import glob from "fast-glob";
|
|
8
|
+
import fs from "fs-extra";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import replaceInFile from "replace-in-file";
|
|
11
|
+
const { replaceInFileSync } = replaceInFile;
|
|
12
|
+
export default class Build extends LibBase {
|
|
13
|
+
buildTime;
|
|
14
|
+
isPreRelease;
|
|
15
|
+
constructor(config) {
|
|
16
|
+
super(config);
|
|
17
|
+
this.buildTime = "";
|
|
18
|
+
this.isPreRelease = this.version.includes("-");
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Default build runner
|
|
22
|
+
*/
|
|
23
|
+
async run() {
|
|
24
|
+
const t = new Date();
|
|
25
|
+
this.buildTime = dateFormat("YYYY-mm-dd HH:MM:SS", t);
|
|
26
|
+
this.logger.info(`Building version ${chalk.blue(this.version)} to ${chalk.blue(this.config.dist)} at ${chalk.blue(this.buildTime)} in ${chalk.blue(process.env.NODE_ENV)} mode.`);
|
|
27
|
+
fs.emptyDirSync(this.config.dist);
|
|
28
|
+
this.copyAssets();
|
|
29
|
+
this.logger.debug("Preparing manifest...");
|
|
30
|
+
this.makeManifest();
|
|
31
|
+
this.makebootstrap();
|
|
32
|
+
this.logger.debug("Preparing locale files...");
|
|
33
|
+
this.prepareLocaleFiles();
|
|
34
|
+
this.logger.debug("Replacing...");
|
|
35
|
+
this.replaceString();
|
|
36
|
+
this.logger.debug("Running esbuild...");
|
|
37
|
+
this.esbuild();
|
|
38
|
+
this.logger.debug("Running extra builder...");
|
|
39
|
+
await this.config.extraBuilder(this.config);
|
|
40
|
+
this.logger.debug("Addon prepare OK.");
|
|
41
|
+
/**======== build resolved ===========*/
|
|
42
|
+
if (process.env.NODE_ENV === "production") {
|
|
43
|
+
this.logger.debug("Packing Addon...");
|
|
44
|
+
await this.pack();
|
|
45
|
+
this.logger.debug("Preparing update.json...");
|
|
46
|
+
this.makeUpdateJson();
|
|
47
|
+
}
|
|
48
|
+
this.logger.info(`Build finished in ${(new Date().getTime() - t.getTime()) / 1000} s.`);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Copys files in `Config.assets` to `Config.dist`
|
|
52
|
+
*/
|
|
53
|
+
copyAssets() {
|
|
54
|
+
const files = glob.sync(this.config.assets);
|
|
55
|
+
files.forEach((file) => {
|
|
56
|
+
const newPath = `${this.config.dist}/addon/${file.replace(new RegExp(this.config.source.join("|")), "")}`;
|
|
57
|
+
this.logger.trace(`Copy ${file} to ${newPath}`);
|
|
58
|
+
fs.copySync(file, newPath);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
makeManifest() {
|
|
62
|
+
if (!this.config.makeManifest.enable)
|
|
63
|
+
return;
|
|
64
|
+
fs.outputJSONSync(`${this.config.dist}/addon/manifest.json`, this.config.makeManifest.template, { spaces: 2 });
|
|
65
|
+
}
|
|
66
|
+
makebootstrap() {
|
|
67
|
+
if (!this.config.makeBootstrap)
|
|
68
|
+
return;
|
|
69
|
+
fs.copySync(path.join(this.config.pkgAbsolute, "template/default/bootstrap.js"), `${this.config.dist}/addon/bootstrap.js`);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Replace all `placeholder.key` to `placeholder.value` for all files in `dist`
|
|
73
|
+
*/
|
|
74
|
+
replaceString() {
|
|
75
|
+
const replaceFrom = [], replaceTo = [];
|
|
76
|
+
// Config.placeholders has the highest priority
|
|
77
|
+
replaceFrom.push(...Object.keys(this.config.define).map((k) => new RegExp(`__${k}__`, "g")));
|
|
78
|
+
replaceTo.push(...Object.values(this.config.define));
|
|
79
|
+
replaceFrom.push(/__buildTime__/g);
|
|
80
|
+
replaceTo.push(this.buildTime);
|
|
81
|
+
const replaceResult = replaceInFileSync({
|
|
82
|
+
files: this.config.assets.map((asset) => `${this.config.dist}/${asset}`),
|
|
83
|
+
from: replaceFrom,
|
|
84
|
+
to: replaceTo,
|
|
85
|
+
countMatches: true,
|
|
86
|
+
});
|
|
87
|
+
this.logger.debug("Run replace in ", replaceResult
|
|
88
|
+
.filter((f) => f.hasChanged)
|
|
89
|
+
.map((f) => `${f.file} : ${f.numReplacements} / ${f.numMatches}`));
|
|
90
|
+
}
|
|
91
|
+
prepareLocaleFiles() {
|
|
92
|
+
// Prefix Fluent messages in xhtml
|
|
93
|
+
const MessagesInHTML = new Set();
|
|
94
|
+
replaceInFileSync({
|
|
95
|
+
files: [
|
|
96
|
+
`${this.config.dist}/addon/**/*.xhtml`,
|
|
97
|
+
`${this.config.dist}/addon/**/*.html`,
|
|
98
|
+
],
|
|
99
|
+
// @ts-expect-error ReplaceInFileConfig has processor
|
|
100
|
+
processor: (input) => {
|
|
101
|
+
const matchs = [...input.matchAll(/(data-l10n-id)="(\S*)"/g)];
|
|
102
|
+
matchs.map((match) => {
|
|
103
|
+
input = input.replace(match[0], this.config.fluent.prefixFluentMessages == true
|
|
104
|
+
? `${match[1]}="${this.addonRef}-${match[2]}"`
|
|
105
|
+
: match[0]);
|
|
106
|
+
MessagesInHTML.add(match[2]);
|
|
107
|
+
});
|
|
108
|
+
return input;
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
// Walk the sub folders of `build/addon/locale`
|
|
112
|
+
const localeNames = glob
|
|
113
|
+
.sync(`${this.config.dist}/addon/locale/*/`, {})
|
|
114
|
+
.map((locale) => path.basename(locale));
|
|
115
|
+
for (const localeName of localeNames) {
|
|
116
|
+
// rename *.ftl to addonRef-*.ftl
|
|
117
|
+
if (this.config.fluent.prefixLocaleFiles == true) {
|
|
118
|
+
glob
|
|
119
|
+
.sync(`${this.config.dist}/addon/locale/${localeName}/**/*.ftl`, {})
|
|
120
|
+
.forEach((f) => {
|
|
121
|
+
fs.moveSync(f, `${path.dirname(f)}/${this.addonRef}-${path.basename(f)}`);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
// Prefix Fluent messages in each ftl
|
|
125
|
+
const MessageInThisLang = new Set();
|
|
126
|
+
replaceInFileSync({
|
|
127
|
+
files: [`${this.config.dist}/addon/locale/${localeName}/**/*.ftl`],
|
|
128
|
+
// @ts-expect-error ReplaceInFileConfig has processor
|
|
129
|
+
processor: (fltContent) => {
|
|
130
|
+
const lines = fltContent.split("\n");
|
|
131
|
+
const prefixedLines = lines.map((line) => {
|
|
132
|
+
// https://regex101.com/r/lQ9x5p/1
|
|
133
|
+
const match = line.match(/^(?<message>[a-zA-Z]\S*)([ ]*=[ ]*)(?<pattern>.*)$/m);
|
|
134
|
+
if (match && match.groups) {
|
|
135
|
+
MessageInThisLang.add(match.groups.message);
|
|
136
|
+
return this.config.fluent.prefixFluentMessages
|
|
137
|
+
? `${this.addonRef}-${line}`
|
|
138
|
+
: line;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
return line;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
return prefixedLines.join("\n");
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
// If a message in xhtml but not in ftl of current language, log it
|
|
148
|
+
MessagesInHTML.forEach((message) => {
|
|
149
|
+
if (!MessageInThisLang.has(message)) {
|
|
150
|
+
this.logger.error(`${message} don't exist in ${localeName}`);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
esbuild() {
|
|
156
|
+
this.config.esbuildOptions.forEach(async (esbuildOption) => {
|
|
157
|
+
buildSync(esbuildOption);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
makeUpdateJson() {
|
|
161
|
+
fs.writeJsonSync(`${this.config.dist}/update-beta.json`, this.config.makeUpdateJson.template, { spaces: 2 });
|
|
162
|
+
fs.writeJsonSync(`${this.config.dist}/update.json`, this.config.makeUpdateJson.template, { spaces: 2 });
|
|
163
|
+
const updateHash = generateHashSync(path.join(this.config.dist, `${this.xpiName}.xpi`), "sha512");
|
|
164
|
+
const replaceResult = replaceInFileSync({
|
|
165
|
+
files: [
|
|
166
|
+
`${this.config.dist}/update-beta.json`,
|
|
167
|
+
`${this.config.dist}/${this.isPreRelease ? "pass" : "update.json"}`,
|
|
168
|
+
],
|
|
169
|
+
from: [
|
|
170
|
+
/__addonID__/g,
|
|
171
|
+
/__version__/g,
|
|
172
|
+
/__updateLink__/g,
|
|
173
|
+
/__updateHash__/g,
|
|
174
|
+
],
|
|
175
|
+
to: [this.addonID, this.version, this.updateLink, updateHash],
|
|
176
|
+
countMatches: true,
|
|
177
|
+
});
|
|
178
|
+
this.logger.debug(`Prepare Update.json for ${this.isPreRelease
|
|
179
|
+
? "\u001b[31m Prerelease \u001b[0m"
|
|
180
|
+
: "\u001b[32m Release \u001b[0m"}`, replaceResult
|
|
181
|
+
.filter((f) => f.hasChanged)
|
|
182
|
+
.map((f) => `${f.file} : ${f.numReplacements} / ${f.numMatches}`));
|
|
183
|
+
}
|
|
184
|
+
async pack() {
|
|
185
|
+
await zip.compressDir(path.join(this.config.dist, "addon"), path.join(this.config.dist, `${this.xpiName}.xpi`), {
|
|
186
|
+
ignoreBase: true,
|
|
187
|
+
});
|
|
188
|
+
// await webext.cmd.build({
|
|
189
|
+
// sourceDir: `${this.config.dist}/addon`,
|
|
190
|
+
// artifactsDir: this.config.dist,
|
|
191
|
+
// filename: `${this.pkg.name ?? "name"}.xpi`,
|
|
192
|
+
// });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export default class Create {
|
|
2
|
+
run(): Promise<void>;
|
|
3
|
+
prompting(): Promise<{
|
|
4
|
+
plugin: {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
id: string;
|
|
8
|
+
};
|
|
9
|
+
code: {
|
|
10
|
+
namespace: string;
|
|
11
|
+
};
|
|
12
|
+
user: {
|
|
13
|
+
name: string;
|
|
14
|
+
email: string;
|
|
15
|
+
};
|
|
16
|
+
}>;
|
|
17
|
+
downloadTemplate(): void;
|
|
18
|
+
parseTemplate(): void;
|
|
19
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// npx 创建模板
|
|
2
|
+
import { input } from "@inquirer/prompts";
|
|
3
|
+
export default class Create {
|
|
4
|
+
async run() {
|
|
5
|
+
const answers = await this.prompting();
|
|
6
|
+
console.log(answers);
|
|
7
|
+
}
|
|
8
|
+
async prompting() {
|
|
9
|
+
const answers = {
|
|
10
|
+
plugin: {
|
|
11
|
+
name: await input({ message: "What's the name of plugin?" }),
|
|
12
|
+
description: await input({
|
|
13
|
+
message: "What's description of plugin?",
|
|
14
|
+
}),
|
|
15
|
+
id: await input({ message: "What's the ID of plugin?" }),
|
|
16
|
+
},
|
|
17
|
+
code: {
|
|
18
|
+
namespace: await input({
|
|
19
|
+
message: "What's the namespace of plugin?",
|
|
20
|
+
default: "",
|
|
21
|
+
}),
|
|
22
|
+
// package manager select: npm yarn pnpm
|
|
23
|
+
// use typescript?
|
|
24
|
+
// use prettier and lint?
|
|
25
|
+
},
|
|
26
|
+
user: {
|
|
27
|
+
name: await input({ message: "What's your GitHub user ID?" }),
|
|
28
|
+
email: await input({ message: "What's your email?" }),
|
|
29
|
+
},
|
|
30
|
+
// install deps?
|
|
31
|
+
};
|
|
32
|
+
return answers;
|
|
33
|
+
}
|
|
34
|
+
downloadTemplate() {
|
|
35
|
+
//
|
|
36
|
+
}
|
|
37
|
+
parseTemplate() {
|
|
38
|
+
//
|
|
39
|
+
}
|
|
40
|
+
}
|
package/dist/lib/lint.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { Config } from "../types.js";
|
|
2
|
+
import { LibBase } from "../utils/libBase.js";
|
|
3
|
+
import { Octokit } from "@octokit/rest";
|
|
4
|
+
export default class Release extends LibBase {
|
|
5
|
+
isCI: boolean;
|
|
6
|
+
client: Octokit;
|
|
7
|
+
constructor(config: Config);
|
|
8
|
+
/**
|
|
9
|
+
* Runs release
|
|
10
|
+
*
|
|
11
|
+
* if is not CI,bump version, git add (package.json), git commit, git tag, git push;
|
|
12
|
+
* if is CI, do not bump version, do not run git, create release (tag is `v${version}`) and upload xpi,
|
|
13
|
+
* then, create or update release (tag is "release"), update `update.json`.
|
|
14
|
+
*/
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Bumps release
|
|
18
|
+
*
|
|
19
|
+
* release: bump version, run build, git add, git commit, git tag, git push
|
|
20
|
+
*/
|
|
21
|
+
bump(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Create new release and upload XPI to asset
|
|
24
|
+
*/
|
|
25
|
+
uploadXPI(): void;
|
|
26
|
+
getReleaseByTag(tag: string): Promise<{
|
|
27
|
+
url: string;
|
|
28
|
+
html_url: string;
|
|
29
|
+
assets_url: string;
|
|
30
|
+
upload_url: string;
|
|
31
|
+
tarball_url: string | null;
|
|
32
|
+
zipball_url: string | null;
|
|
33
|
+
id: number;
|
|
34
|
+
node_id: string;
|
|
35
|
+
tag_name: string;
|
|
36
|
+
target_commitish: string;
|
|
37
|
+
name: string | null;
|
|
38
|
+
body?: string | null | undefined;
|
|
39
|
+
draft: boolean;
|
|
40
|
+
prerelease: boolean;
|
|
41
|
+
created_at: string;
|
|
42
|
+
published_at: string | null;
|
|
43
|
+
author: {
|
|
44
|
+
name?: string | null | undefined;
|
|
45
|
+
email?: string | null | undefined;
|
|
46
|
+
login: string;
|
|
47
|
+
id: number;
|
|
48
|
+
node_id: string;
|
|
49
|
+
avatar_url: string;
|
|
50
|
+
gravatar_id: string | null;
|
|
51
|
+
url: string;
|
|
52
|
+
html_url: string;
|
|
53
|
+
followers_url: string;
|
|
54
|
+
following_url: string;
|
|
55
|
+
gists_url: string;
|
|
56
|
+
starred_url: string;
|
|
57
|
+
subscriptions_url: string;
|
|
58
|
+
organizations_url: string;
|
|
59
|
+
repos_url: string;
|
|
60
|
+
events_url: string;
|
|
61
|
+
received_events_url: string;
|
|
62
|
+
type: string;
|
|
63
|
+
site_admin: boolean;
|
|
64
|
+
starred_at?: string | undefined;
|
|
65
|
+
};
|
|
66
|
+
assets: {
|
|
67
|
+
url: string;
|
|
68
|
+
browser_download_url: string;
|
|
69
|
+
id: number;
|
|
70
|
+
node_id: string;
|
|
71
|
+
name: string;
|
|
72
|
+
label: string | null;
|
|
73
|
+
state: "uploaded" | "open";
|
|
74
|
+
content_type: string;
|
|
75
|
+
size: number;
|
|
76
|
+
download_count: number;
|
|
77
|
+
created_at: string;
|
|
78
|
+
updated_at: string;
|
|
79
|
+
uploader: {
|
|
80
|
+
name?: string | null | undefined;
|
|
81
|
+
email?: string | null | undefined;
|
|
82
|
+
login: string;
|
|
83
|
+
id: number;
|
|
84
|
+
node_id: string;
|
|
85
|
+
avatar_url: string;
|
|
86
|
+
gravatar_id: string | null;
|
|
87
|
+
url: string;
|
|
88
|
+
html_url: string;
|
|
89
|
+
followers_url: string;
|
|
90
|
+
following_url: string;
|
|
91
|
+
gists_url: string;
|
|
92
|
+
starred_url: string;
|
|
93
|
+
subscriptions_url: string;
|
|
94
|
+
organizations_url: string;
|
|
95
|
+
repos_url: string;
|
|
96
|
+
events_url: string;
|
|
97
|
+
received_events_url: string;
|
|
98
|
+
type: string;
|
|
99
|
+
site_admin: boolean;
|
|
100
|
+
starred_at?: string | undefined;
|
|
101
|
+
} | null;
|
|
102
|
+
}[];
|
|
103
|
+
body_html?: string | undefined;
|
|
104
|
+
body_text?: string | undefined;
|
|
105
|
+
mentions_count?: number | undefined;
|
|
106
|
+
discussion_url?: string | undefined;
|
|
107
|
+
reactions?: {
|
|
108
|
+
url: string;
|
|
109
|
+
total_count: number;
|
|
110
|
+
"+1": number;
|
|
111
|
+
"-1": number;
|
|
112
|
+
laugh: number;
|
|
113
|
+
confused: number;
|
|
114
|
+
heart: number;
|
|
115
|
+
hooray: number;
|
|
116
|
+
eyes: number;
|
|
117
|
+
rocket: number;
|
|
118
|
+
} | undefined;
|
|
119
|
+
} | undefined>;
|
|
120
|
+
creatRelease(options: Parameters<Octokit["repos"]["createRelease"]>[0]): Promise<{
|
|
121
|
+
url: string;
|
|
122
|
+
html_url: string;
|
|
123
|
+
assets_url: string;
|
|
124
|
+
upload_url: string;
|
|
125
|
+
tarball_url: string | null;
|
|
126
|
+
zipball_url: string | null;
|
|
127
|
+
id: number;
|
|
128
|
+
node_id: string;
|
|
129
|
+
tag_name: string;
|
|
130
|
+
target_commitish: string;
|
|
131
|
+
name: string | null;
|
|
132
|
+
body?: string | null | undefined;
|
|
133
|
+
draft: boolean;
|
|
134
|
+
prerelease: boolean;
|
|
135
|
+
created_at: string;
|
|
136
|
+
published_at: string | null;
|
|
137
|
+
author: {
|
|
138
|
+
name?: string | null | undefined;
|
|
139
|
+
email?: string | null | undefined;
|
|
140
|
+
login: string;
|
|
141
|
+
id: number;
|
|
142
|
+
node_id: string;
|
|
143
|
+
avatar_url: string;
|
|
144
|
+
gravatar_id: string | null;
|
|
145
|
+
url: string;
|
|
146
|
+
html_url: string;
|
|
147
|
+
followers_url: string;
|
|
148
|
+
following_url: string;
|
|
149
|
+
gists_url: string;
|
|
150
|
+
starred_url: string;
|
|
151
|
+
subscriptions_url: string;
|
|
152
|
+
organizations_url: string;
|
|
153
|
+
repos_url: string;
|
|
154
|
+
events_url: string;
|
|
155
|
+
received_events_url: string;
|
|
156
|
+
type: string;
|
|
157
|
+
site_admin: boolean;
|
|
158
|
+
starred_at?: string | undefined;
|
|
159
|
+
};
|
|
160
|
+
assets: {
|
|
161
|
+
url: string;
|
|
162
|
+
browser_download_url: string;
|
|
163
|
+
id: number;
|
|
164
|
+
node_id: string;
|
|
165
|
+
name: string;
|
|
166
|
+
label: string | null;
|
|
167
|
+
state: "uploaded" | "open";
|
|
168
|
+
content_type: string;
|
|
169
|
+
size: number;
|
|
170
|
+
download_count: number;
|
|
171
|
+
created_at: string;
|
|
172
|
+
updated_at: string;
|
|
173
|
+
uploader: {
|
|
174
|
+
name?: string | null | undefined;
|
|
175
|
+
email?: string | null | undefined;
|
|
176
|
+
login: string;
|
|
177
|
+
id: number;
|
|
178
|
+
node_id: string;
|
|
179
|
+
avatar_url: string;
|
|
180
|
+
gravatar_id: string | null;
|
|
181
|
+
url: string;
|
|
182
|
+
html_url: string;
|
|
183
|
+
followers_url: string;
|
|
184
|
+
following_url: string;
|
|
185
|
+
gists_url: string;
|
|
186
|
+
starred_url: string;
|
|
187
|
+
subscriptions_url: string;
|
|
188
|
+
organizations_url: string;
|
|
189
|
+
repos_url: string;
|
|
190
|
+
events_url: string;
|
|
191
|
+
received_events_url: string;
|
|
192
|
+
type: string;
|
|
193
|
+
site_admin: boolean;
|
|
194
|
+
starred_at?: string | undefined;
|
|
195
|
+
} | null;
|
|
196
|
+
}[];
|
|
197
|
+
body_html?: string | undefined;
|
|
198
|
+
body_text?: string | undefined;
|
|
199
|
+
mentions_count?: number | undefined;
|
|
200
|
+
discussion_url?: string | undefined;
|
|
201
|
+
reactions?: {
|
|
202
|
+
url: string;
|
|
203
|
+
total_count: number;
|
|
204
|
+
"+1": number;
|
|
205
|
+
"-1": number;
|
|
206
|
+
laugh: number;
|
|
207
|
+
confused: number;
|
|
208
|
+
heart: number;
|
|
209
|
+
hooray: number;
|
|
210
|
+
eyes: number;
|
|
211
|
+
rocket: number;
|
|
212
|
+
} | undefined;
|
|
213
|
+
} | undefined>;
|
|
214
|
+
uploadAsset(releaseID: number, asset: string): Promise<{
|
|
215
|
+
url: string;
|
|
216
|
+
browser_download_url: string;
|
|
217
|
+
id: number;
|
|
218
|
+
node_id: string;
|
|
219
|
+
name: string;
|
|
220
|
+
label: string | null;
|
|
221
|
+
state: "uploaded" | "open";
|
|
222
|
+
content_type: string;
|
|
223
|
+
size: number;
|
|
224
|
+
download_count: number;
|
|
225
|
+
created_at: string;
|
|
226
|
+
updated_at: string;
|
|
227
|
+
uploader: {
|
|
228
|
+
name?: string | null | undefined;
|
|
229
|
+
email?: string | null | undefined;
|
|
230
|
+
login: string;
|
|
231
|
+
id: number;
|
|
232
|
+
node_id: string;
|
|
233
|
+
avatar_url: string;
|
|
234
|
+
gravatar_id: string | null;
|
|
235
|
+
url: string;
|
|
236
|
+
html_url: string;
|
|
237
|
+
followers_url: string;
|
|
238
|
+
following_url: string;
|
|
239
|
+
gists_url: string;
|
|
240
|
+
starred_url: string;
|
|
241
|
+
subscriptions_url: string;
|
|
242
|
+
organizations_url: string;
|
|
243
|
+
repos_url: string;
|
|
244
|
+
events_url: string;
|
|
245
|
+
received_events_url: string;
|
|
246
|
+
type: string;
|
|
247
|
+
site_admin: boolean;
|
|
248
|
+
starred_at?: string | undefined;
|
|
249
|
+
} | null;
|
|
250
|
+
}>;
|
|
251
|
+
uploadUpdateJSON(): Promise<void>;
|
|
252
|
+
getClient(): Octokit;
|
|
253
|
+
get owner(): string;
|
|
254
|
+
get repo(): string;
|
|
255
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { LibBase } from "../utils/libBase.js";
|
|
2
|
+
import { Octokit } from "@octokit/rest";
|
|
3
|
+
import ci from "ci-info";
|
|
4
|
+
import { default as glob } from "fast-glob";
|
|
5
|
+
import fs from "fs-extra";
|
|
6
|
+
import _ from "lodash";
|
|
7
|
+
import mime from "mime";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import releaseIt from "release-it";
|
|
10
|
+
export default class Release extends LibBase {
|
|
11
|
+
isCI;
|
|
12
|
+
client;
|
|
13
|
+
constructor(config) {
|
|
14
|
+
super(config);
|
|
15
|
+
this.isCI = ci.isCI;
|
|
16
|
+
this.client = this.getClient();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Runs release
|
|
20
|
+
*
|
|
21
|
+
* if is not CI,bump version, git add (package.json), git commit, git tag, git push;
|
|
22
|
+
* if is CI, do not bump version, do not run git, create release (tag is `v${version}`) and upload xpi,
|
|
23
|
+
* then, create or update release (tag is "release"), update `update.json`.
|
|
24
|
+
*/
|
|
25
|
+
async run() {
|
|
26
|
+
if (!this.isCI) {
|
|
27
|
+
this.bump();
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
if (glob.globSync(`${this.config.dist}/*.xpi`).length == 0) {
|
|
31
|
+
this.logger.error("No xpi file found, are you sure you have run the build?");
|
|
32
|
+
}
|
|
33
|
+
this.uploadXPI();
|
|
34
|
+
this.uploadUpdateJSON();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Bumps release
|
|
39
|
+
*
|
|
40
|
+
* release: bump version, run build, git add, git commit, git tag, git push
|
|
41
|
+
*/
|
|
42
|
+
bump() {
|
|
43
|
+
// versionBump(this.config.release.bumpp);
|
|
44
|
+
const releaseItConfig = {
|
|
45
|
+
"only-version": true,
|
|
46
|
+
};
|
|
47
|
+
releaseIt(_.defaultsDeep(releaseItConfig, this.config.release.releaseIt));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Create new release and upload XPI to asset
|
|
51
|
+
*/
|
|
52
|
+
uploadXPI() {
|
|
53
|
+
const releaseItConfig = {
|
|
54
|
+
increment: false,
|
|
55
|
+
git: {
|
|
56
|
+
commit: false,
|
|
57
|
+
tag: false,
|
|
58
|
+
push: false,
|
|
59
|
+
},
|
|
60
|
+
github: {
|
|
61
|
+
release: true,
|
|
62
|
+
},
|
|
63
|
+
verbose: 2,
|
|
64
|
+
ci: true,
|
|
65
|
+
};
|
|
66
|
+
releaseIt(_.defaultsDeep(releaseItConfig, this.config.release.releaseIt));
|
|
67
|
+
}
|
|
68
|
+
async getReleaseByTag(tag) {
|
|
69
|
+
return await this.client.repos
|
|
70
|
+
.getReleaseByTag({
|
|
71
|
+
owner: this.owner,
|
|
72
|
+
repo: this.repo,
|
|
73
|
+
tag: tag,
|
|
74
|
+
})
|
|
75
|
+
.then((res) => {
|
|
76
|
+
if (res.status == 200) {
|
|
77
|
+
return res.data;
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
.catch((err) => {
|
|
81
|
+
this.logger.debug(err);
|
|
82
|
+
return undefined;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async creatRelease(options) {
|
|
86
|
+
return await this.client.repos
|
|
87
|
+
.createRelease(options)
|
|
88
|
+
.then((res) => {
|
|
89
|
+
if (res.status == 201) {
|
|
90
|
+
return res.data;
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
.catch((err) => {
|
|
94
|
+
this.logger.debug(err);
|
|
95
|
+
return undefined;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async uploadAsset(releaseID, asset) {
|
|
99
|
+
return await this.client.repos
|
|
100
|
+
.uploadReleaseAsset({
|
|
101
|
+
owner: this.owner,
|
|
102
|
+
repo: this.repo,
|
|
103
|
+
release_id: releaseID,
|
|
104
|
+
data: fs.readFileSync(asset),
|
|
105
|
+
headers: {
|
|
106
|
+
"content-type": mime.getType(asset) || "application/octet-stream",
|
|
107
|
+
"content-length": fs.statSync(asset).size,
|
|
108
|
+
},
|
|
109
|
+
name: path.basename(asset),
|
|
110
|
+
})
|
|
111
|
+
.then((res) => {
|
|
112
|
+
return res.data;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
async uploadUpdateJSON() {
|
|
116
|
+
const assets = ["update.json", "update-beta.json"];
|
|
117
|
+
const release = (await this.getReleaseByTag("release")) ??
|
|
118
|
+
(await this.creatRelease({
|
|
119
|
+
owner: this.owner,
|
|
120
|
+
repo: this.repo,
|
|
121
|
+
tag_name: "release",
|
|
122
|
+
name: "Release Manifest",
|
|
123
|
+
body: `This release is used to host \`update.json\`, please do not delete or modify it! \n Updated in UTC ${new Date().toISOString()} for version ${this.version}`,
|
|
124
|
+
make_latest: "false",
|
|
125
|
+
}));
|
|
126
|
+
if (!release)
|
|
127
|
+
throw new Error("Get or create 'release' failed.");
|
|
128
|
+
const existAssets = await this.client.repos
|
|
129
|
+
.listReleaseAssets({
|
|
130
|
+
owner: this.owner,
|
|
131
|
+
repo: this.repo,
|
|
132
|
+
release_id: release.id,
|
|
133
|
+
})
|
|
134
|
+
.then((res) => {
|
|
135
|
+
return res.data.filter((asset) => assets.includes(asset.name));
|
|
136
|
+
});
|
|
137
|
+
if (existAssets) {
|
|
138
|
+
for (const existAsset of existAssets) {
|
|
139
|
+
await this.client.repos.deleteReleaseAsset({
|
|
140
|
+
owner: this.owner,
|
|
141
|
+
repo: this.repo,
|
|
142
|
+
asset_id: existAsset.id,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (const asset of assets) {
|
|
147
|
+
await this.uploadAsset(release.id, path.join(this.config.dist, asset));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
getClient() {
|
|
151
|
+
if (!process.env.GITHUB_TOKEN)
|
|
152
|
+
throw new Error("No GITHUB_TOKEN.");
|
|
153
|
+
const client = new Octokit({
|
|
154
|
+
auth: process.env.GITHUB_TOKEN,
|
|
155
|
+
userAgent: `zotero-plugin-scaffold/${this.version}`,
|
|
156
|
+
});
|
|
157
|
+
return client;
|
|
158
|
+
}
|
|
159
|
+
get owner() {
|
|
160
|
+
return this.config.define.ghOwner;
|
|
161
|
+
}
|
|
162
|
+
get repo() {
|
|
163
|
+
return this.config.define.ghRepo;
|
|
164
|
+
}
|
|
165
|
+
}
|