addonova 1.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/LICENSE +674 -0
- package/README.md +2 -0
- package/bin/index.js +22 -0
- package/package.json +42 -0
- package/src/commands/init.js +108 -0
- package/src/index.js +1 -0
- package/src/utils/prompts.js +32 -0
- package/template/config/chrome.js +29 -0
- package/template/config/edge.js +29 -0
- package/template/config/firefox.js +29 -0
- package/template/config/naver.js +29 -0
- package/template/config/opera.js +29 -0
- package/template/config/thunderbird.js +29 -0
- package/template/package.json.tpl +39 -0
- package/template/src/_locales/en.i18n +4 -0
- package/template/src/html/popup.html +15 -0
- package/template/src/js/background.js +1 -0
- package/template/src/js/popup.js +6 -0
- package/template/src/manifest/manifest-firefox.json +29 -0
- package/template/src/manifest/manifest.json +24 -0
- package/template/src/scss/popup.scss +25 -0
- package/template/tasks/build.js +82 -0
- package/template/tasks/bundle-css.js +108 -0
- package/template/tasks/bundle-html.js +91 -0
- package/template/tasks/bundle-js.js +95 -0
- package/template/tasks/bundle-locales.js +139 -0
- package/template/tasks/bundle-manifest.js +54 -0
- package/template/tasks/cli.js +85 -0
- package/template/tasks/copy.js +49 -0
- package/template/tasks/folder.js +25 -0
- package/template/tasks/paths.js +21 -0
- package/template/tasks/task.js +60 -0
- package/template/tasks/translate.js +255 -0
- package/template/tasks/utils.js +134 -0
- package/template/tasks/watch.js +46 -0
- package/template/tasks/zip.js +66 -0
- package/template/tools/json2i18n.js +33 -0
- package/template/tools/translate.js +299 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { build } from 'esbuild';
|
|
3
|
+
import { sassPlugin } from 'esbuild-sass-plugin';
|
|
4
|
+
import {getDestDir} from './paths.js';
|
|
5
|
+
import {readFile, writeFile, getConfig, getAllFiles, log, fileExistsInConfig} from './utils.js';
|
|
6
|
+
import {createTask} from './task.js';
|
|
7
|
+
|
|
8
|
+
const srcSCSSDir = 'src/scss';
|
|
9
|
+
|
|
10
|
+
async function removeTopSourceComment(filePath) {
|
|
11
|
+
const code = await readFile(filePath, 'utf8');
|
|
12
|
+
const newCode = code.replace(/^\/\*[\s\S]*?\*\//, '');
|
|
13
|
+
await writeFile(filePath, newCode);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function esbuildCSS(config, isDebug, platform) {
|
|
17
|
+
let buildResult;
|
|
18
|
+
let outputs;
|
|
19
|
+
const dir = getDestDir({isDebug, platform});
|
|
20
|
+
|
|
21
|
+
for (const [dest, src] of Object.entries(config.entry)) {
|
|
22
|
+
buildResult = await build({
|
|
23
|
+
entryPoints: src,
|
|
24
|
+
outdir: path.join(dir, dest),
|
|
25
|
+
entryNames: config.filename,
|
|
26
|
+
loader: {
|
|
27
|
+
'.scss': 'css'
|
|
28
|
+
},
|
|
29
|
+
plugins: [
|
|
30
|
+
sassPlugin({
|
|
31
|
+
type: 'css',
|
|
32
|
+
sourceMap: config.sourcemap,
|
|
33
|
+
})
|
|
34
|
+
],
|
|
35
|
+
minify: config.minify,
|
|
36
|
+
sourcemap: config.sourcemap,
|
|
37
|
+
metafile: true,
|
|
38
|
+
write: true
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (!isDebug) {
|
|
42
|
+
outputs = Object.keys(buildResult.metafile.outputs);
|
|
43
|
+
for (const file of outputs) {
|
|
44
|
+
if (file.endsWith('.css')) {
|
|
45
|
+
await removeTopSourceComment(file);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function createBundleCSSTask(srcSCSSDir){
|
|
53
|
+
let currentWatchFiles;
|
|
54
|
+
const bundleCSS = async ({platforms, isDebug, logInfo, logWarn}) => {
|
|
55
|
+
for(const platform of platforms){
|
|
56
|
+
const config = (await getConfig(platform));
|
|
57
|
+
const scssConfig = config.scss;
|
|
58
|
+
if (scssConfig){
|
|
59
|
+
await esbuildCSS(scssConfig, isDebug, platform);
|
|
60
|
+
if (logInfo) log.ok(`Bundling CSS for ${platform}...`);
|
|
61
|
+
} else {
|
|
62
|
+
if(logWarn) log.warn(`No SCSS config found for ${platform}, skipping CSS bundling.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const onChange = async (changedFiles, watcher, platforms, isDebug) => {
|
|
68
|
+
for(const platform of platforms){
|
|
69
|
+
const config = await getConfig(platform);
|
|
70
|
+
if (config.scss){
|
|
71
|
+
const exists = await fileExistsInConfig(
|
|
72
|
+
config.scss.entry,
|
|
73
|
+
changedFiles[0]
|
|
74
|
+
);
|
|
75
|
+
if (exists){
|
|
76
|
+
const newConfig = {
|
|
77
|
+
scss: {
|
|
78
|
+
entry: exists,
|
|
79
|
+
filename: config.scss.filename,
|
|
80
|
+
minify: config.scss.minify,
|
|
81
|
+
sourcemap: config.scss.sourcemap
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
await esbuildCSS(newConfig.scss, isDebug, platform);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const newWatchFiles = await getAllFiles(srcSCSSDir);
|
|
91
|
+
watcher.unwatch(
|
|
92
|
+
currentWatchFiles.filter((oldFile) => !newWatchFiles.includes(oldFile))
|
|
93
|
+
);
|
|
94
|
+
watcher.add(
|
|
95
|
+
newWatchFiles.filter((newFile) => currentWatchFiles.includes(newFile))
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return createTask(
|
|
99
|
+
'bundle CSS',
|
|
100
|
+
bundleCSS,
|
|
101
|
+
).addWatcher(
|
|
102
|
+
async () => {
|
|
103
|
+
currentWatchFiles = await getAllFiles(srcSCSSDir);
|
|
104
|
+
return currentWatchFiles;
|
|
105
|
+
}, onChange);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export default createBundleCSSTask(srcSCSSDir);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { build } from 'esbuild';
|
|
3
|
+
import { htmlPlugin } from '@craftamap/esbuild-plugin-html';
|
|
4
|
+
import {getDestDir} from './paths.js';
|
|
5
|
+
import {readFile, writeFile, getConfig, getAllFiles, log, fileExistsInConfig} from './utils.js';
|
|
6
|
+
import {createTask} from './task.js';
|
|
7
|
+
|
|
8
|
+
const srcHTMLDir = 'src/html';
|
|
9
|
+
|
|
10
|
+
async function removeTopSourceComment(filePath) {
|
|
11
|
+
const code = await readFile(filePath, 'utf8');
|
|
12
|
+
const newCode = code.replace(/^\s*<!--[\s\S]*?-->\s*/, '');
|
|
13
|
+
await writeFile(filePath, newCode);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function esbuildHTML(config, isDebug, platform){
|
|
17
|
+
let buildResult, outputs;
|
|
18
|
+
const dir = getDestDir({isDebug, platform});
|
|
19
|
+
for (const [dest, src] of Object.entries(config.entry)) {
|
|
20
|
+
buildResult = await build({
|
|
21
|
+
entryPoints: src,
|
|
22
|
+
outdir: path.join(dir, dest),
|
|
23
|
+
plugins: [htmlPlugin()],
|
|
24
|
+
loader: {
|
|
25
|
+
'.html': 'file' // 🔥 Important
|
|
26
|
+
},
|
|
27
|
+
entryNames: config.filename,
|
|
28
|
+
assetNames: config.filename,
|
|
29
|
+
chunkNames: config.filename,
|
|
30
|
+
metafile: true,
|
|
31
|
+
write: true
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (!isDebug) {
|
|
35
|
+
outputs = Object.keys(buildResult.metafile.outputs);
|
|
36
|
+
for (const file of outputs) {
|
|
37
|
+
if (file.endsWith('.html')) {
|
|
38
|
+
removeTopSourceComment(file);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createBundleHTMLTask(srcHTMLDir){
|
|
47
|
+
let currentWatchFiles;
|
|
48
|
+
const bundleHTML = async ({platforms, isDebug, logInfo, logWarn}) => {
|
|
49
|
+
for(const platform of platforms){
|
|
50
|
+
const config = (await getConfig(platform));
|
|
51
|
+
if(config.html){
|
|
52
|
+
await esbuildHTML(config.html, isDebug, platform);
|
|
53
|
+
if (logInfo) log.ok(`Bundling HTML for ${platform}...`);
|
|
54
|
+
} else{
|
|
55
|
+
if(logWarn) log.warn(`No HTML config found for ${platform}, skipping HTML bundling.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const onChange = async (changedFiles, watcher, platforms, isDebug) => {
|
|
61
|
+
for(const platform of platforms){
|
|
62
|
+
const config = (await getConfig(platform));
|
|
63
|
+
|
|
64
|
+
if (!config.html) continue;
|
|
65
|
+
const exists = await fileExistsInConfig(
|
|
66
|
+
config.html.entry,
|
|
67
|
+
changedFiles[0]
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
if (exists){
|
|
71
|
+
const newConfig = {
|
|
72
|
+
html: {
|
|
73
|
+
entry: exists,
|
|
74
|
+
filename: config.html.filename
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
await esbuildHTML(newConfig.html, isDebug, platform);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return createTask(
|
|
82
|
+
'bundle HTML',
|
|
83
|
+
bundleHTML,
|
|
84
|
+
).addWatcher(
|
|
85
|
+
async () => {
|
|
86
|
+
currentWatchFiles = await getAllFiles(srcHTMLDir);
|
|
87
|
+
return currentWatchFiles;
|
|
88
|
+
}, onChange);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export default createBundleHTMLTask(srcHTMLDir);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { build } from 'esbuild';
|
|
3
|
+
import {getDestDir} from './paths.js';
|
|
4
|
+
import {readFile, writeFile, getConfig, getAllFiles, log, fileExistsInConfig} from './utils.js';
|
|
5
|
+
import {createTask} from './task.js';
|
|
6
|
+
|
|
7
|
+
const srcJSDir = 'src/js';
|
|
8
|
+
|
|
9
|
+
async function removeTopSourceComment(filePath) {
|
|
10
|
+
const code = await readFile(filePath, 'utf8');
|
|
11
|
+
const newCode = code.replace(/^\/\/.*\n/, '');
|
|
12
|
+
await writeFile(filePath, newCode);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function esbuildJS(config, isDebug, platform){
|
|
16
|
+
// console.log(`Starting JS build for ${config}...`);
|
|
17
|
+
let buildResult, outputs;
|
|
18
|
+
const dir = getDestDir({isDebug, platform});
|
|
19
|
+
for (const [dest, src] of Object.entries(config.entry)) {
|
|
20
|
+
buildResult = await build({
|
|
21
|
+
entryPoints: src,
|
|
22
|
+
bundle: false,
|
|
23
|
+
outdir: path.join(dir, dest),
|
|
24
|
+
entryNames: config.filename,
|
|
25
|
+
loader: {
|
|
26
|
+
'.js': 'js'
|
|
27
|
+
},
|
|
28
|
+
minify: !isDebug ? config.minify : false,
|
|
29
|
+
sourcemap: config.sourcemap,
|
|
30
|
+
drop: !isDebug ? ['console', 'debugger'] : [],
|
|
31
|
+
metafile: true,
|
|
32
|
+
write: true
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (!isDebug) {
|
|
36
|
+
outputs = Object.keys(buildResult.metafile.outputs);
|
|
37
|
+
for (const file of outputs) {
|
|
38
|
+
if (file.endsWith('.js')) {
|
|
39
|
+
removeTopSourceComment(file);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createBundleJSTask(srcJSDir){
|
|
48
|
+
let currentWatchFiles;
|
|
49
|
+
const bundleJS = async ({platforms, isDebug, logInfo}) => {
|
|
50
|
+
for(const platform of platforms){
|
|
51
|
+
const config = /** @type {any} */ (await getConfig(platform));
|
|
52
|
+
if (config.js) {
|
|
53
|
+
if (logInfo) log.ok(`Bundling JS for ${platform}...`);
|
|
54
|
+
await esbuildJS(config.js, isDebug, platform);
|
|
55
|
+
} else {
|
|
56
|
+
if(logInfo) log.warn(`No JS config found for ${platform}, skipping JS bundling.`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const onChange = async (changedFiles, watcher, platforms, isDebug) => {
|
|
62
|
+
for (const platform of platforms) {
|
|
63
|
+
const config = await getConfig(platform);
|
|
64
|
+
|
|
65
|
+
if (!config.js) continue;
|
|
66
|
+
|
|
67
|
+
const exists = await fileExistsInConfig(
|
|
68
|
+
config.js.entry,
|
|
69
|
+
changedFiles.join(', ')
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
if (exists) {
|
|
73
|
+
const newConfig = {
|
|
74
|
+
js: {
|
|
75
|
+
entry: exists,
|
|
76
|
+
filename: config.js.filename,
|
|
77
|
+
minify: config.js.minify,
|
|
78
|
+
sourcemap: config.js.sourcemap
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
await esbuildJS(newConfig.js, isDebug, platform);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return createTask(
|
|
87
|
+
'bundle JS',
|
|
88
|
+
bundleJS,
|
|
89
|
+
).addWatcher(
|
|
90
|
+
async () => {
|
|
91
|
+
currentWatchFiles = await getAllFiles(srcJSDir);
|
|
92
|
+
return currentWatchFiles;
|
|
93
|
+
}, onChange);
|
|
94
|
+
}
|
|
95
|
+
export default createBundleJSTask(srcJSDir);
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import {getDestDir, absolutePath} from './paths.js';
|
|
5
|
+
import {readFile, writeFile, getAllFiles, getConfig} from './utils.js';
|
|
6
|
+
import {createTask} from './task.js';
|
|
7
|
+
|
|
8
|
+
const srcLocalesDir = 'src/_locales';
|
|
9
|
+
|
|
10
|
+
async function writeFiles(data, fileName, {platform, isDebug}){
|
|
11
|
+
const locale = fileName.substring(0, fileName.lastIndexOf('.')).replace('-', '_');
|
|
12
|
+
const getOutputPath = (dir) => `${dir}/_locales/${locale}/messages.json`;
|
|
13
|
+
const dir = getDestDir({isDebug, platform});
|
|
14
|
+
await writeFile(getOutputPath(dir), data);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function localeFileToJson(filePath) {
|
|
18
|
+
const isfile = await readFile(filePath);
|
|
19
|
+
const file = isfile.replace(/^#.*?$/gm, '');
|
|
20
|
+
|
|
21
|
+
const messages = {};
|
|
22
|
+
const regex = /@([a-z0-9_]+)/ig;
|
|
23
|
+
let match;
|
|
24
|
+
|
|
25
|
+
while ((match = regex.exec(file))) {
|
|
26
|
+
const messageName = match[1];
|
|
27
|
+
const messageStart = match.index + match[0].length;
|
|
28
|
+
let messageEnd = file.indexOf('@', messageStart);
|
|
29
|
+
|
|
30
|
+
if (messageEnd < 0) {
|
|
31
|
+
messageEnd = file.length;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const messageContent = file
|
|
35
|
+
.substring(messageStart, messageEnd)
|
|
36
|
+
.replace(/\n/g, ' ') // remove line breaks
|
|
37
|
+
.replace(/\s+/g, ' ') // normalize extra spaces
|
|
38
|
+
.trim();
|
|
39
|
+
|
|
40
|
+
messages[messageName] = {
|
|
41
|
+
message: messageContent,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return messages;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function mergeLocale(localesDir, code) {
|
|
49
|
+
let result = {};
|
|
50
|
+
|
|
51
|
+
const walk = async (dir) => {
|
|
52
|
+
const dirFiles = [];
|
|
53
|
+
const dirDirs = [];
|
|
54
|
+
const paths = await fs.readdir(dir);
|
|
55
|
+
for (const path of paths) {
|
|
56
|
+
const stat = await fs.stat(`${dir}/${path}`);
|
|
57
|
+
if (stat.isDirectory()) {
|
|
58
|
+
dirDirs.push(path);
|
|
59
|
+
} else {
|
|
60
|
+
dirFiles.push(path);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const localeFiles = dirFiles.filter((f) => f.split('.').at(-2) === code);
|
|
64
|
+
for (const localeFile of localeFiles) {
|
|
65
|
+
const messages = await localeFileToJson(`${dir}/${localeFile}`);
|
|
66
|
+
result = {...result, ...messages};
|
|
67
|
+
}
|
|
68
|
+
for (const folder of dirDirs) {
|
|
69
|
+
await walk(`${dir}/${folder}`);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
await walk(localesDir);
|
|
74
|
+
return JSON.stringify(result, null, 4);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function bundleLocales(srcLocalesDir, {platforms, isDebug, logInfo}) {
|
|
78
|
+
const absoluteSrcLocalesDir = absolutePath(srcLocalesDir);
|
|
79
|
+
const list = await fs.readdir(absoluteSrcLocalesDir);
|
|
80
|
+
|
|
81
|
+
for (const name of list) {
|
|
82
|
+
if (!name.endsWith('.i18n')) {
|
|
83
|
+
if (logInfo) log.ok(`Skipping non-locale file ${name} in ${srcLocalesDir}.`);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const code = (name.split('.').at(-2));
|
|
87
|
+
const locale = await mergeLocale(absoluteSrcLocalesDir, code);
|
|
88
|
+
const fileName = name.substring(name.lastIndexOf('/') + 1);
|
|
89
|
+
for (const platform of platforms) {
|
|
90
|
+
const config = (await getConfig(platform));
|
|
91
|
+
if (config && Object.keys(config).length != 0){
|
|
92
|
+
if(config.locales){
|
|
93
|
+
if(config.locales.includes(code)){
|
|
94
|
+
await writeFiles(locale, fileName, {platform, isDebug});
|
|
95
|
+
}
|
|
96
|
+
} else{
|
|
97
|
+
await writeFiles(locale, fileName, {platform, isDebug});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (logInfo) log.ok(`Bundled locale ${code} for platforms: ${platforms.join(', ')}.`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function createBundleLocalesTask(srcLocalesDir) {
|
|
107
|
+
const onChange = async (changedFiles, watcher, platforms, isDebug) => {
|
|
108
|
+
const localesSrcDir = absolutePath(srcLocalesDir);
|
|
109
|
+
for (const file of changedFiles) {
|
|
110
|
+
const fileName = file.substring(file.lastIndexOf(path.sep) + 1);
|
|
111
|
+
const code = (fileName.split('.').at(-2));
|
|
112
|
+
const locale = await mergeLocale(localesSrcDir, code);
|
|
113
|
+
for (const platform of platforms) {
|
|
114
|
+
const config = (await getConfig(platform));
|
|
115
|
+
if (config && Object.keys(config).length != 0){
|
|
116
|
+
if(config.locales){
|
|
117
|
+
if(config.locales.includes(code)){
|
|
118
|
+
await writeFiles(locale, fileName, {platform, isDebug});
|
|
119
|
+
}
|
|
120
|
+
} else{
|
|
121
|
+
await writeFiles(locale, fileName, {platform, isDebug});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return createTask(
|
|
128
|
+
'bundle locales',
|
|
129
|
+
(options) => bundleLocales(srcLocalesDir, options),
|
|
130
|
+
).addWatcher(
|
|
131
|
+
async () => {
|
|
132
|
+
const currentWatchFiles = await getAllFiles(srcLocalesDir);
|
|
133
|
+
return currentWatchFiles;
|
|
134
|
+
},
|
|
135
|
+
onChange,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export default createBundleLocalesTask(srcLocalesDir);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {getDestDir, absolutePath} from './paths.js';
|
|
2
|
+
import {createTask} from './task.js';
|
|
3
|
+
import {readJSON, writeJSON, getAllFiles, pathExists, getConfig} from './utils.js';
|
|
4
|
+
|
|
5
|
+
const srcManifestDir = 'src/manifest';
|
|
6
|
+
|
|
7
|
+
async function patchManifest(srcManifestDir, platform, isDebug, isWatch, isTest, version) {
|
|
8
|
+
const manifestPath = `${srcManifestDir}/manifest-${platform}.json`;
|
|
9
|
+
const existsManifest = await pathExists(manifestPath);
|
|
10
|
+
const patched = await readJSON(existsManifest ? manifestPath : absolutePath(`${srcManifestDir}/manifest.json`));
|
|
11
|
+
if (isDebug) {
|
|
12
|
+
patched.version = '1';
|
|
13
|
+
patched.description = `Debug build, platform: ${platform}, watch: ${isWatch ? 'yes' : 'no'}.`;
|
|
14
|
+
} else {
|
|
15
|
+
const packageJSON = await readJSON(absolutePath("package.json"));
|
|
16
|
+
patched.version = version || packageJSON.version;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if(platform == "naver"){
|
|
20
|
+
patched.default_locale = 'ko';
|
|
21
|
+
}
|
|
22
|
+
return patched;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async function bundleManifest(srcManifestDir, {platforms, isWatch, isDebug, isTest, logInfo, version}) {
|
|
27
|
+
for (const platform of platforms) {
|
|
28
|
+
const config = await getConfig(platform);
|
|
29
|
+
if (config && Object.keys(config).length != 0){
|
|
30
|
+
const manifest = await patchManifest(srcManifestDir, platform, isDebug, isWatch, isTest, version);
|
|
31
|
+
const destDir = getDestDir({isDebug, platform});
|
|
32
|
+
await writeJSON(`${destDir}/manifest.json`, manifest);
|
|
33
|
+
if (logInfo) log.ok(`Bundled manifest for platform ${platform}.`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
export function bundleManifestTask(srcManifestDir){
|
|
39
|
+
const onChange = async (changedFiles, watcher, platforms, isDebug) => {
|
|
40
|
+
await bundleManifest(srcManifestDir, {platforms, isWatch: true, isDebug, isTest: false, logInfo: false, version: null});
|
|
41
|
+
}
|
|
42
|
+
return createTask(
|
|
43
|
+
'bundle-manifest',
|
|
44
|
+
(options) => bundleManifest(srcManifestDir, options),
|
|
45
|
+
).addWatcher(
|
|
46
|
+
async () => {
|
|
47
|
+
const currentWatchFiles = await getAllFiles(srcManifestDir);
|
|
48
|
+
return currentWatchFiles;
|
|
49
|
+
},
|
|
50
|
+
onChange,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default bundleManifestTask(srcManifestDir);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {join} from 'node:path';
|
|
2
|
+
import {fileURLToPath} from 'node:url';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import {fork} from 'node:child_process';
|
|
5
|
+
import {log} from './utils.js';
|
|
6
|
+
|
|
7
|
+
function printHelp() {
|
|
8
|
+
console.log([
|
|
9
|
+
'Volume booster build utility',
|
|
10
|
+
'Usage: npm run build -- [options]',
|
|
11
|
+
'',
|
|
12
|
+
'To narrow down the list of build targets (for efficiency):',
|
|
13
|
+
' --all Build for all platforms',
|
|
14
|
+
' --chrome Google Chrome (MV3)',
|
|
15
|
+
' --edge Microsoft Edge',
|
|
16
|
+
' --firefox Mozilla Firefox',
|
|
17
|
+
' --opera Opera Browser',
|
|
18
|
+
' --naver Naver Whale',
|
|
19
|
+
' --thunderbird Mozilla Thunderbird',
|
|
20
|
+
'',
|
|
21
|
+
'Other parameters:',
|
|
22
|
+
' --release Build release version (default)',
|
|
23
|
+
' --debug Build debug version',
|
|
24
|
+
' --watch Watch for changes and rebuild automatically',
|
|
25
|
+
' --log-info Log info messages',
|
|
26
|
+
' --log-warn Log warning messages',
|
|
27
|
+
' --test Build test version (for testing in development environment)',
|
|
28
|
+
' --version=1.2.3 Append version to output file name (e.g. extension-name-chrome-1.2.3.zip)',
|
|
29
|
+
' -h, --help Show this help message',
|
|
30
|
+
].join('\n'));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const __filename = join(fileURLToPath(import.meta.url), '../build.js');
|
|
34
|
+
|
|
35
|
+
async function executeChildProcess(args) {
|
|
36
|
+
const child = fork(__filename, args);
|
|
37
|
+
process.on('SIGINT', () => {
|
|
38
|
+
child.kill('SIGKILL');
|
|
39
|
+
process.exit(130);
|
|
40
|
+
});
|
|
41
|
+
return new Promise((resolve, reject) => child.on('error', reject).on('close', resolve));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function validateArguments(args) {
|
|
45
|
+
const validationErrors = [];
|
|
46
|
+
const validFlags = [
|
|
47
|
+
'--all',
|
|
48
|
+
'--chrome',
|
|
49
|
+
'--edge',
|
|
50
|
+
'--naver',
|
|
51
|
+
'--opera',
|
|
52
|
+
'--firefox',
|
|
53
|
+
'--thunderbird',
|
|
54
|
+
'--release',
|
|
55
|
+
'--debug',
|
|
56
|
+
'--watch',
|
|
57
|
+
'--log-info',
|
|
58
|
+
'--log-warn',
|
|
59
|
+
'--test',
|
|
60
|
+
'--version=*',
|
|
61
|
+
'--help',
|
|
62
|
+
'-h'
|
|
63
|
+
];
|
|
64
|
+
const invalidFlags = args.filter((flag) => !validFlags.includes(flag));
|
|
65
|
+
invalidFlags.forEach((flag) => validationErrors.push(`Invalid flag ${flag}`));
|
|
66
|
+
return validationErrors;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function run() {
|
|
70
|
+
const args = process.argv.slice(3);
|
|
71
|
+
const shouldPrintHelp = args.length === 0 || process.argv[2] !== 'build' || args.includes('-h') || args.includes('--help');
|
|
72
|
+
if (shouldPrintHelp) {
|
|
73
|
+
printHelp();
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
const validationErrors = validateArguments(args);
|
|
77
|
+
if (validationErrors.length > 0) {
|
|
78
|
+
validationErrors.forEach(log.error);
|
|
79
|
+
log.error('❌ No target platform specified.');
|
|
80
|
+
printHelp();
|
|
81
|
+
process.exit(130);
|
|
82
|
+
}
|
|
83
|
+
await executeChildProcess(args);
|
|
84
|
+
}
|
|
85
|
+
run();
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
|
|
2
|
+
import {getDestDir} from './paths.js';
|
|
3
|
+
import {copyFile, getAllFiles, getConfig, log} from './utils.js';
|
|
4
|
+
import {createTask} from './task.js';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { mkdir } from 'node:fs/promises';
|
|
7
|
+
|
|
8
|
+
export function createCopyTask() {
|
|
9
|
+
const paths = [];
|
|
10
|
+
const assetsCopy = async ({platforms, isDebug, logInfo, logWarn}) => {
|
|
11
|
+
for(const platform of platforms){
|
|
12
|
+
const config = await getConfig(platform);
|
|
13
|
+
if (config.assets) {
|
|
14
|
+
for (const [dest, sources] of Object.entries(config.assets.entry)) {
|
|
15
|
+
const srcList = Array.isArray(sources) ? sources : [sources];
|
|
16
|
+
|
|
17
|
+
for (const src of srcList) {
|
|
18
|
+
const files = await getAllFiles(src);
|
|
19
|
+
|
|
20
|
+
for (const file of files) {
|
|
21
|
+
const destDir = path.join(getDestDir({isDebug, platform}), dest);
|
|
22
|
+
await mkdir(destDir, { recursive: true });
|
|
23
|
+
|
|
24
|
+
const destFile = path.join(destDir, path.basename(file));
|
|
25
|
+
await copyFile(file, destFile);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (logInfo) log.ok(`Copying assets for ${platform}...`);
|
|
30
|
+
} else{
|
|
31
|
+
if(logWarn) log.warn(`No assets config found for ${platform}, skipping assets copy.`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
const onChange = async (changedFiles, watcher, platforms, isDebug) => {
|
|
37
|
+
await assetsCopy({platforms, isDebug, logInfo: false, logWarn: false})
|
|
38
|
+
}
|
|
39
|
+
return createTask(
|
|
40
|
+
'assets copy',
|
|
41
|
+
assetsCopy,
|
|
42
|
+
).addWatcher(
|
|
43
|
+
paths,
|
|
44
|
+
onChange,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
export default createCopyTask();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import {createTask} from './task.js';
|
|
3
|
+
import {getDestDir} from './paths.js';
|
|
4
|
+
import {createFolder, removeFolder, log, getConfig} from './utils.js';
|
|
5
|
+
|
|
6
|
+
export function createFolderTask() {
|
|
7
|
+
const folder = async ({platforms, isDebug, logInfo}) => {
|
|
8
|
+
process.env.NODE_ENV = isDebug ? 'development' : 'production';
|
|
9
|
+
for(const platform of platforms){
|
|
10
|
+
const config = await getConfig(platform);
|
|
11
|
+
if (config && Object.keys(config).length != 0){
|
|
12
|
+
await removeFolder(getDestDir({isDebug, platform}));
|
|
13
|
+
logInfo ? log.ok(`Folder Removed: ${getDestDir({isDebug, platform})}`) : null;
|
|
14
|
+
|
|
15
|
+
await createFolder(getDestDir({isDebug, platform}));
|
|
16
|
+
logInfo ? log.ok(`Folder Created: ${getDestDir({isDebug, platform})}`) : null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
return createTask(
|
|
21
|
+
'Environment Create',
|
|
22
|
+
folder,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
export default createFolderTask();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {createRequire} from 'node:module';
|
|
2
|
+
import {dirname, join} from 'node:path';
|
|
3
|
+
|
|
4
|
+
let rootDir = dirname(createRequire(import.meta.url).resolve('../../package.json'));
|
|
5
|
+
|
|
6
|
+
export const absolutePath = (path) => {
|
|
7
|
+
return join(rootDir, path);
|
|
8
|
+
};
|
|
9
|
+
export function setRootDir(dir) {
|
|
10
|
+
rootDir = dir;
|
|
11
|
+
}
|
|
12
|
+
export function getDestDir({isDebug, platform}) {
|
|
13
|
+
const buildTypeDir = `build/${isDebug ? 'debug':'release'}`;
|
|
14
|
+
return `${buildTypeDir}/${platform}`;
|
|
15
|
+
}
|
|
16
|
+
export default {
|
|
17
|
+
getDestDir,
|
|
18
|
+
rootDir,
|
|
19
|
+
absolutePath,
|
|
20
|
+
setRootDir,
|
|
21
|
+
};
|