@hellpig/anarchy-shared 1.5.2 → 1.6.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/CHANGELOG.md +1 -0
- package/README.md +1 -1
- package/ScriptUtils/CheckMinify.sh +74 -0
- package/ScriptUtils/CopyDir.js +24 -0
- package/ScriptUtils/CopyFiles.js +40 -0
- package/ScriptUtils/EnvUtils.js +55 -0
- package/ScriptUtils/GenerateVersionsFile.js +43 -0
- package/ScriptUtils/InjectMetadata.js +37 -0
- package/ScriptUtils/MakeIcoFromPng.js +139 -0
- package/ScriptUtils/ModeUtils.js +42 -0
- package/ScriptUtils/PostprocessScreenshots.js +37 -0
- package/ScriptUtils/RenameFile.js +23 -0
- package/ScriptUtils/SanitizeAssets.js +120 -0
- package/package.json +7 -7
- /package/{dist → src}/assets/_constants.scss +0 -0
- /package/{dist → src}/assets/_utils.scss +0 -0
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -31,4 +31,4 @@ SOFTWARE.
|
|
|
31
31
|
See local files `LICENSE`, `CHANGELOG`, `NOTICE` (pointer), and files in `./legal`:
|
|
32
32
|
`DISCLAIMER`, `EULA`, `PRIVACY`, `SECURITY`, `NOTICE` (full), `THIRD_PARTY_LICENSES`.
|
|
33
33
|
|
|
34
|
-
Contacts — Privacy:
|
|
34
|
+
Contacts — Privacy: pnf036+anarchy@gmail.com, Security: pnf036+anarchy_security@gmail.com.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
JS_FILE="$1"
|
|
4
|
+
MAP_FILE="$2"
|
|
5
|
+
GZ_FILE="${JS_FILE}.gz"
|
|
6
|
+
BR_FILE="${JS_FILE}.br"
|
|
7
|
+
|
|
8
|
+
if [[ -z "$JS_FILE" || -z "$MAP_FILE" ]]; then
|
|
9
|
+
echo "❌ Usage: ./CheckMinify.sh path/to/file.js path/to/file.js.map"
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
if [[ ! -f "$JS_FILE" ]]; then
|
|
14
|
+
echo "❌ JS file not found: $JS_FILE"
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
if [[ ! -f "$MAP_FILE" ]]; then
|
|
19
|
+
echo "❌ Map file not found: $MAP_FILE"
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
#LINES=$(wc -l < "$JS_FILE")
|
|
24
|
+
#if [[ "$LINES" -gt 50 ]]; then
|
|
25
|
+
# echo "❌ Too many lines in JS file ($LINES). Probably not minified."
|
|
26
|
+
# exit 1
|
|
27
|
+
#else
|
|
28
|
+
# echo "✅ JS file appears minified ($LINES lines)"
|
|
29
|
+
#fi
|
|
30
|
+
|
|
31
|
+
if [[ -f "$GZ_FILE" ]]; then
|
|
32
|
+
gunzip -c "$GZ_FILE" | cmp -s - "$JS_FILE"
|
|
33
|
+
if [[ $? -ne 0 ]]; then
|
|
34
|
+
echo "❌ Gzip file doesn't match JS file"
|
|
35
|
+
exit 1
|
|
36
|
+
else
|
|
37
|
+
echo "✅ Gzip file matches"
|
|
38
|
+
fi
|
|
39
|
+
else
|
|
40
|
+
echo "⚠️ Gzip file not found: $GZ_FILE"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
if [[ -f "$BR_FILE" ]]; then
|
|
44
|
+
brotli -d -c "$BR_FILE" | cmp -s - "$JS_FILE"
|
|
45
|
+
if [[ $? -ne 0 ]]; then
|
|
46
|
+
echo "❌ Brotli file doesn't match JS file"
|
|
47
|
+
exit 1
|
|
48
|
+
else
|
|
49
|
+
echo "✅ Brotli file matches"
|
|
50
|
+
fi
|
|
51
|
+
else
|
|
52
|
+
echo "⚠️ Brotli file not found: $BR_FILE"
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
echo "🔍 Checking sourcemap integrity..."
|
|
56
|
+
|
|
57
|
+
TMP_JSON=$(mktemp -t sourcemapXXXXXX.json)
|
|
58
|
+
|
|
59
|
+
SOURCEMAP_ERR=$(npx --yes source-map-explorer "$JS_FILE" "$MAP_FILE" --json "$TMP_JSON" 2>&1)
|
|
60
|
+
echo "📄 Sourcemap analysis written to: $TMP_JSON"
|
|
61
|
+
|
|
62
|
+
if [[ $? -eq 0 && -s "$TMP_JSON" ]]; then
|
|
63
|
+
echo "✅ Sourcemap check passed: $JS_FILE"
|
|
64
|
+
rm "$TMP_JSON"
|
|
65
|
+
else
|
|
66
|
+
echo "❌ Failed to analyze sourcemap: $JS_FILE"
|
|
67
|
+
echo "🧾 stderr:"
|
|
68
|
+
echo "$SOURCEMAP_ERR"
|
|
69
|
+
[[ -f "$TMP_JSON" ]] && cat "$TMP_JSON"
|
|
70
|
+
rm -f "$TMP_JSON"
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
echo "🎉 All checks passed"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { copy } from 'fs-extra';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
const fromArg = args.find((arg) => arg.startsWith('from='));
|
|
6
|
+
const toArg = args.find((arg) => arg.startsWith('to='));
|
|
7
|
+
|
|
8
|
+
const fromPath = resolve(process.cwd(), fromArg.split('=')[1]);
|
|
9
|
+
const toPath = resolve(process.cwd(), toArg.split('=')[1]);
|
|
10
|
+
|
|
11
|
+
console.log(`Copying files...\nFrom: ${fromPath}\nTo: ${toPath}`);
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
await copy(fromPath, toPath, {
|
|
15
|
+
overwrite: true,
|
|
16
|
+
errorOnExist: false,
|
|
17
|
+
recursive: true
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
console.log('✅ Copy completed.');
|
|
21
|
+
} catch (err) {
|
|
22
|
+
console.error('Copy failed:', err);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { copyFile, mkdir } from 'fs/promises';
|
|
2
|
+
import { resolve, dirname } from 'path';
|
|
3
|
+
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
|
|
6
|
+
// last arg must be the destination (e.g. to=dist-app/draco)
|
|
7
|
+
const toArg = args.find((arg) => arg.startsWith('to='));
|
|
8
|
+
if (!toArg) {
|
|
9
|
+
console.error('❌ Missing argument: to=<destination folder>');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const toDir = resolve(process.cwd(), toArg.split('=')[1]);
|
|
14
|
+
|
|
15
|
+
// all other args treated as relative file paths to copy
|
|
16
|
+
const filePaths = args.filter((arg) => !arg.startsWith('to='));
|
|
17
|
+
if (filePaths.length === 0) {
|
|
18
|
+
console.error('❌ No files to copy provided.');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
console.log(`Copying files to: ${toDir}`);
|
|
23
|
+
console.log(filePaths.map((f) => ` - ${f}`).join('\n'));
|
|
24
|
+
|
|
25
|
+
(async () => {
|
|
26
|
+
try {
|
|
27
|
+
for (const relativePath of filePaths) {
|
|
28
|
+
const src = resolve(process.cwd(), relativePath);
|
|
29
|
+
const dest = resolve(toDir, relativePath.split('/').pop());
|
|
30
|
+
|
|
31
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
32
|
+
await copyFile(src, dest);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log('✅ Files copied successfully.');
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.error('❌ Copy failed:', err);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
})();
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import { config as dotenvConfig } from 'dotenv';
|
|
4
|
+
import { expand as dotenvExpand } from 'dotenv-expand';
|
|
5
|
+
|
|
6
|
+
export function buildEnvChain(mode) {
|
|
7
|
+
const chain = ['.env', '.env.local'];
|
|
8
|
+
const tokens = String(mode).split('.').filter(Boolean);
|
|
9
|
+
const first = tokens[0]?.toLowerCase();
|
|
10
|
+
const firstAlias = first === 'dev' ? 'development' : first === 'prod' ? 'production' : undefined;
|
|
11
|
+
|
|
12
|
+
const seqs = [tokens];
|
|
13
|
+
if (firstAlias && firstAlias !== first) seqs.push([firstAlias, ...tokens.slice(1)]);
|
|
14
|
+
|
|
15
|
+
for (const seq of seqs) {
|
|
16
|
+
const accum = [];
|
|
17
|
+
for (const p of seq) {
|
|
18
|
+
accum.push(p);
|
|
19
|
+
const key = accum.join('.');
|
|
20
|
+
chain.push(`.env.${key}`);
|
|
21
|
+
chain.push(`.env.${key}.local`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return Array.from(new Set(chain));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function loadModeEnv(mode, cwd = process.cwd()) {
|
|
29
|
+
const loadEnvFile = (file) => {
|
|
30
|
+
const full = path.resolve(cwd, file);
|
|
31
|
+
if (!fs.existsSync(full)) return false;
|
|
32
|
+
const result = dotenvConfig({ path: full, override: true });
|
|
33
|
+
if (result.parsed) dotenvExpand({ parsed: result.parsed });
|
|
34
|
+
return true;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const chain = buildEnvChain(mode);
|
|
38
|
+
for (const file of chain) loadEnvFile(file);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function parseListEnv(val) {
|
|
42
|
+
if (!val) return [];
|
|
43
|
+
return String(val)
|
|
44
|
+
.split(',')
|
|
45
|
+
.map((s) => s.trim())
|
|
46
|
+
.filter(Boolean);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function parseBoolEnv(val, defaultValue = false) {
|
|
50
|
+
if (val == null) return defaultValue;
|
|
51
|
+
const s = String(val).trim().toLowerCase();
|
|
52
|
+
if (s === '1' || s === 'true' || s === 'yes' || s === 'on') return true;
|
|
53
|
+
if (s === '0' || s === 'false' || s === 'no' || s === 'off') return false;
|
|
54
|
+
return defaultValue;
|
|
55
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { writeFileSync, existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { resolve, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
const cwd = process.cwd();
|
|
6
|
+
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
const pkgArg = args.find((arg) => arg.startsWith('--pkg='));
|
|
9
|
+
const outArg = args.find((arg) => arg.startsWith('--out='));
|
|
10
|
+
|
|
11
|
+
const pkgPath = pkgArg ? resolve(cwd, pkgArg.split('=')[1]) : resolve(cwd, 'package.json');
|
|
12
|
+
|
|
13
|
+
const outPath = outArg ? resolve(cwd, outArg.split('=')[1]) : resolve(cwd, './versions.ts');
|
|
14
|
+
|
|
15
|
+
if (!existsSync(pkgPath)) {
|
|
16
|
+
console.error(`❌ package.json not found at: ${pkgPath}`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const raw = readFileSync(pkgPath, 'utf8');
|
|
21
|
+
let version;
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const pkg = JSON.parse(raw);
|
|
25
|
+
version = pkg.version;
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.error(`❌ Failed to parse package.json:`, err);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!version) {
|
|
32
|
+
console.error(`❌ "version" field not found in ${pkgPath}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const content = `// This file is auto-generated by GenerateVersionsFile.js
|
|
37
|
+
// Do not edit manually. Run the script to update.
|
|
38
|
+
|
|
39
|
+
export const packageJsonVersion = '${version}';\n`;
|
|
40
|
+
writeFileSync(outPath, content, 'utf8');
|
|
41
|
+
|
|
42
|
+
console.log(`✅ version.ts generated at: ${outPath}`);
|
|
43
|
+
console.log(`📦 version: ${version}`);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import encode from 'png-chunks-encode';
|
|
5
|
+
import extract from 'png-chunks-extract';
|
|
6
|
+
|
|
7
|
+
//THis script meant to be used for testing of cleaning metadata
|
|
8
|
+
|
|
9
|
+
const inputPath = path.resolve(process.cwd(), './test.png');
|
|
10
|
+
const outputPath = path.resolve(process.cwd(), './test.png');
|
|
11
|
+
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
13
|
+
export function createTextChunk(keyword, text) {
|
|
14
|
+
// eslint-disable-next-line spellcheck/spell-checker
|
|
15
|
+
const keywordBuffer = Buffer.from(keyword, 'latin1');
|
|
16
|
+
const nullByte = Buffer.from([0x00]);
|
|
17
|
+
// eslint-disable-next-line spellcheck/spell-checker
|
|
18
|
+
const textBuffer = Buffer.from(text, 'latin1');
|
|
19
|
+
const data = Buffer.concat([keywordBuffer, nullByte, textBuffer]);
|
|
20
|
+
return {
|
|
21
|
+
name: 'tEXt',
|
|
22
|
+
data
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const pngBuffer = fs.readFileSync(inputPath);
|
|
27
|
+
|
|
28
|
+
const chunks = extract(pngBuffer);
|
|
29
|
+
|
|
30
|
+
const myTextChunk = createTextChunk('Author', 'Someone (to remove)');
|
|
31
|
+
|
|
32
|
+
chunks.splice(1, 0, myTextChunk);
|
|
33
|
+
|
|
34
|
+
const newBuffer = Buffer.from(encode(chunks));
|
|
35
|
+
fs.writeFileSync(outputPath, newBuffer);
|
|
36
|
+
|
|
37
|
+
console.log('✅ PNG with metadata saved to with-meta.png');
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Creates .ico file from one or more PNGs, with optional resizing.
|
|
2
|
+
// Usage:
|
|
3
|
+
// node scripts/MakeIcoFromPng.js --in assets/icon-1024.png --out assets/icon.ico
|
|
4
|
+
// node scripts/MakeIcoFromPng.js --in assets/256.png,assets/128.png --out assets/app.ico
|
|
5
|
+
// node scripts/MakeIcoFromPng.js --in assets/pngs --out assets/icon.ico
|
|
6
|
+
// node scripts/MakeIcoFromPng.js --in assets/icon-1024.png --out assets/icon.ico --size 256
|
|
7
|
+
//
|
|
8
|
+
// Notes:
|
|
9
|
+
// - If --size is provided, all inputs are resized to NxN (requires dev-dep 'sharp').
|
|
10
|
+
// - If --size is omitted, inputs are used as-is (no resizing).
|
|
11
|
+
// - png-to-ico builds a valid ICO from given PNG buffers.
|
|
12
|
+
|
|
13
|
+
import { readdir, readFile, stat, writeFile } from 'node:fs/promises';
|
|
14
|
+
import { extname, resolve } from 'node:path';
|
|
15
|
+
import process from 'node:process';
|
|
16
|
+
import pngToIco from 'png-to-ico';
|
|
17
|
+
import sharp from 'sharp';
|
|
18
|
+
|
|
19
|
+
function printHelp() {
|
|
20
|
+
console.log(
|
|
21
|
+
`mk-ico — build a .ico from PNG(s)
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
--in, -i Path to a PNG file. Can be repeated or comma-separated.
|
|
25
|
+
If a directory is passed, all *.png files in it are used (non-recursive).
|
|
26
|
+
--out, -o Output .ico path (required).
|
|
27
|
+
--size, -s Optional square size in px (e.g. 256). If omitted, images are not resized.
|
|
28
|
+
--help, -h Show this help.
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
node scripts/MakeIcoFromPng.js --in assets/icon-1024.png --out assets/icon.ico
|
|
32
|
+
node scripts/MakeIcoFromPng.js -i assets/256.png,assets/128.png -o assets/icon.ico
|
|
33
|
+
node scripts/MakeIcoFromPng.js -i assets/pngs -o assets/icon.ico
|
|
34
|
+
node scripts/MakeIcoFromPng.js -i assets/icon-1024.png -o assets/icon.ico -s 256
|
|
35
|
+
`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseArgs(argv) {
|
|
40
|
+
const args = { inputs: [], out: null, help: false, size: null };
|
|
41
|
+
for (let i = 0; i < argv.length; i++) {
|
|
42
|
+
const a = argv[i];
|
|
43
|
+
if (a === '--help' || a === '-h') {
|
|
44
|
+
args.help = true;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (a === '--out' || a === '-o') {
|
|
48
|
+
args.out = argv[++i];
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (a === '--in' || a === '-i') {
|
|
52
|
+
const val = argv[++i];
|
|
53
|
+
if (val) args.inputs.push(...val.split(','));
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (a === '--size' || a === '-s') {
|
|
57
|
+
const val = argv[++i];
|
|
58
|
+
if (val && /^\d+$/.test(val)) args.size = Number(val);
|
|
59
|
+
else throw new Error(`--size expects a positive integer, got: ${val ?? '(missing)'}`);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return args;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function listPngsFromDir(dirPath) {
|
|
67
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
68
|
+
return entries.filter((e) => e.isFile() && extname(e.name).toLowerCase() === '.png').map((e) => resolve(dirPath, e.name));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function resolveInputs(maybePaths) {
|
|
72
|
+
const result = [];
|
|
73
|
+
for (const p of maybePaths) {
|
|
74
|
+
const abs = resolve(p);
|
|
75
|
+
let st;
|
|
76
|
+
try {
|
|
77
|
+
st = await stat(abs);
|
|
78
|
+
} catch {
|
|
79
|
+
throw new Error(`Input path does not exist: ${abs}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (st.isDirectory()) {
|
|
83
|
+
const pngs = await listPngsFromDir(abs);
|
|
84
|
+
if (pngs.length === 0) throw new Error(`Directory has no PNGs: ${abs}`);
|
|
85
|
+
result.push(...pngs);
|
|
86
|
+
} else if (st.isFile()) {
|
|
87
|
+
if (extname(abs).toLowerCase() !== '.png') throw new Error(`Input is not a .png file: ${abs}`);
|
|
88
|
+
result.push(abs);
|
|
89
|
+
} else {
|
|
90
|
+
throw new Error(`Unsupported input type (not file/dir): ${abs}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// de-dup, keep order
|
|
94
|
+
return [...new Set(result)];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function maybeResize(buf, size) {
|
|
98
|
+
if (!size) return buf;
|
|
99
|
+
if (!(Number.isInteger(size) && size > 0 && size <= 4096)) {
|
|
100
|
+
throw new Error(`--size must be an integer in range 1..4096, got ${size}`);
|
|
101
|
+
}
|
|
102
|
+
return sharp(buf).resize(size, size, { fit: 'contain', withoutEnlargement: false }).png().toBuffer();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function main() {
|
|
106
|
+
const { inputs, out, help, size } = parseArgs(process.argv.slice(2));
|
|
107
|
+
if (help || inputs.length === 0 || !out) {
|
|
108
|
+
printHelp();
|
|
109
|
+
if (help) return;
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const inFiles = await resolveInputs(inputs);
|
|
114
|
+
if (inFiles.length === 0) throw new Error('No PNG inputs resolved.');
|
|
115
|
+
|
|
116
|
+
// Read PNGs, optional resize
|
|
117
|
+
const bufs = await Promise.all(
|
|
118
|
+
inFiles.map(async (p) => {
|
|
119
|
+
const b = await readFile(p);
|
|
120
|
+
return maybeResize(b, size);
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const icoBuf = await pngToIco(bufs);
|
|
126
|
+
const outPath = resolve(out);
|
|
127
|
+
await writeFile(outPath, icoBuf);
|
|
128
|
+
const sizeMsg = size ? ` (resized to ${size}x${size})` : '';
|
|
129
|
+
console.log(`✔ Wrote ICO: ${outPath} from ${inFiles.length} PNG file(s)${sizeMsg}.`);
|
|
130
|
+
} catch (e) {
|
|
131
|
+
console.error('✖ Failed to build ICO:', e?.message || e);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
main().catch((err) => {
|
|
137
|
+
console.error('✖ Error:', err?.message || err);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Small helpers to parse and normalize build/run modes across scripts.
|
|
2
|
+
|
|
3
|
+
// Extracts mode from argv supporting "--mode <val>" and "--mode=<val>"
|
|
4
|
+
export function parseModeArg(argv = []) {
|
|
5
|
+
const idx = argv.findIndex((a) => a === '--mode');
|
|
6
|
+
if (idx !== -1 && argv[idx + 1]) return argv[idx + 1];
|
|
7
|
+
const eq = argv.find((a) => a.startsWith('--mode='));
|
|
8
|
+
if (eq) return eq.slice('--mode='.length);
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Normalizes common synonyms to canonical values and classifies composite modes.
|
|
13
|
+
// dev -> development, prod -> production
|
|
14
|
+
// development.* -> development, production.* -> production
|
|
15
|
+
export function normalizeMode(input) {
|
|
16
|
+
if (!input) return 'production';
|
|
17
|
+
const s = String(input).trim().toLowerCase();
|
|
18
|
+
if (s === 'prod' || s === 'production' || s.startsWith('production.')) return 'production';
|
|
19
|
+
if (s === 'dev' || s === 'development' || s.startsWith('development.') || s.startsWith('dev.')) return 'development';
|
|
20
|
+
return s;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Resolves the effective mode from argv and environment WITHOUT normalizing,
|
|
24
|
+
// so Vite receives the full composite value (e.g., production.mac.arm64).
|
|
25
|
+
// Priority: argv -> MODE -> npm_config_mode -> NODE_ENV -> default
|
|
26
|
+
export function resolveMode(argv = [], env = process.env) {
|
|
27
|
+
const argMode = parseModeArg(argv);
|
|
28
|
+
const envMode = env.MODE;
|
|
29
|
+
const npmMode = env.npm_config_mode;
|
|
30
|
+
const nodeEnv = env.NODE_ENV;
|
|
31
|
+
return argMode || envMode || npmMode || nodeEnv || 'production';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Detects a dry-run flag in argv.
|
|
35
|
+
export function parseDryRunArg(argv = []) {
|
|
36
|
+
return argv.some((a) => a === '--dry-run' || a === '--dryrun' || a === '--dry');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Resolves whether to run in dry-run mode from argv and environment.
|
|
40
|
+
export function resolveDryRun(argv = [], env = process.env) {
|
|
41
|
+
return env.DRY_RUN === '1' || parseDryRunArg(argv);
|
|
42
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import sharp from 'sharp';
|
|
4
|
+
|
|
5
|
+
const SCREENSHOT_DIR = path.resolve(process.cwd(), './src');
|
|
6
|
+
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
8
|
+
async function processPng(filePath) {
|
|
9
|
+
const outputPath = filePath;
|
|
10
|
+
const buffer = await sharp(filePath)
|
|
11
|
+
.png({ force: true }) // 👈 без .withMetadata()
|
|
12
|
+
.toBuffer();
|
|
13
|
+
await fs.writeFile(outputPath, buffer);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
17
|
+
async function processAllPngs(dir) {
|
|
18
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
19
|
+
|
|
20
|
+
// eslint-disable-next-line functional/no-loop-statements
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
const fullPath = path.join(dir, entry.name);
|
|
23
|
+
if (entry.isDirectory()) {
|
|
24
|
+
await processAllPngs(fullPath);
|
|
25
|
+
} else if (entry.name.toLowerCase().endsWith('.png')) {
|
|
26
|
+
await processPng(fullPath);
|
|
27
|
+
console.log(`🧼 Cleaned metadata: ${fullPath}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
processAllPngs(SCREENSHOT_DIR)
|
|
33
|
+
.then(() => console.log('✅ Screenshot metadata cleanup complete'))
|
|
34
|
+
.catch((err) => {
|
|
35
|
+
console.error('❌ Failed to clean screenshots:', err);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
const [, , inputPath, outputPath] = process.argv;
|
|
7
|
+
|
|
8
|
+
if (!inputPath || !outputPath) {
|
|
9
|
+
console.error('❌ Usage: node rename-preload.js <input.js> <output.mjs>');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const resolvedInput = path.resolve(inputPath);
|
|
15
|
+
const resolvedOutput = path.resolve(outputPath);
|
|
16
|
+
|
|
17
|
+
await fs.rename(resolvedInput, resolvedOutput);
|
|
18
|
+
|
|
19
|
+
console.log(`✅ Renamed:\n ${resolvedInput} →\n ${resolvedOutput}`);
|
|
20
|
+
} catch (err) {
|
|
21
|
+
console.error('❌ Rename failed:', err);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { NodeIO } from '@gltf-transform/core';
|
|
2
|
+
import { exec } from 'child_process';
|
|
3
|
+
import fg from 'fast-glob';
|
|
4
|
+
import { promises as fs } from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import sharp from 'sharp';
|
|
7
|
+
import { optimize as optimizeSvg } from 'svgo';
|
|
8
|
+
// eslint-disable-next-line spellcheck/spell-checker
|
|
9
|
+
import { promisify } from 'util';
|
|
10
|
+
|
|
11
|
+
// eslint-disable-next-line spellcheck/spell-checker
|
|
12
|
+
const execAsync = promisify(exec);
|
|
13
|
+
|
|
14
|
+
// Allow passing target directory as CLI arg; default to ./public
|
|
15
|
+
const cliArg = process.argv[2];
|
|
16
|
+
if (cliArg === '--help' || cliArg === '-h') {
|
|
17
|
+
console.log('Usage: node SanitizeAssets.js [targetDir]\nDefault targetDir is ./public');
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
const TARGET_DIR = path.resolve(process.cwd(), cliArg || './public');
|
|
21
|
+
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
23
|
+
async function cleanImage(filePath) {
|
|
24
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
25
|
+
const image = sharp(filePath);
|
|
26
|
+
const buffer = ext === '.png' ? await image.png({ force: true }).toBuffer() : await image.jpeg({ force: true }).toBuffer();
|
|
27
|
+
await fs.writeFile(filePath, buffer);
|
|
28
|
+
console.log(`🧼 Cleaned ${ext.toUpperCase()}: ${filePath}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
32
|
+
async function cleanSvg(filePath) {
|
|
33
|
+
const original = await fs.readFile(filePath, 'utf-8');
|
|
34
|
+
const result = optimizeSvg(original, {
|
|
35
|
+
multipass: true,
|
|
36
|
+
plugins: ['removeMetadata', 'removeTitle', 'removeDesc']
|
|
37
|
+
});
|
|
38
|
+
await fs.writeFile(filePath, result.data);
|
|
39
|
+
console.log(`🧼 Optimized SVG: ${filePath}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
43
|
+
async function cleanMp3(filePath) {
|
|
44
|
+
const tmp = filePath + '.cleaned.mp3';
|
|
45
|
+
await execAsync(`ffmpeg -i "${filePath}" -map_metadata -1 -y "${tmp}"`);
|
|
46
|
+
await fs.rename(tmp, filePath);
|
|
47
|
+
console.log(`🧼 Stripped metadata from MP3: ${filePath}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
51
|
+
function sanitizeDocument(doc) {
|
|
52
|
+
const root = doc.getRoot();
|
|
53
|
+
|
|
54
|
+
// eslint-disable-next-line functional/immutable-data
|
|
55
|
+
root.getAsset().generator = undefined;
|
|
56
|
+
|
|
57
|
+
[
|
|
58
|
+
root.listAccessors(),
|
|
59
|
+
root.listAnimations(),
|
|
60
|
+
root.listBuffers(),
|
|
61
|
+
// root.listBufferViews(),
|
|
62
|
+
root.listCameras(),
|
|
63
|
+
// root.listImages(),
|
|
64
|
+
root.listMaterials(),
|
|
65
|
+
root.listMeshes(),
|
|
66
|
+
root.listNodes(),
|
|
67
|
+
// root.listSamplers(),
|
|
68
|
+
root.listScenes(),
|
|
69
|
+
root.listSkins(),
|
|
70
|
+
root.listTextures()
|
|
71
|
+
]
|
|
72
|
+
.flat()
|
|
73
|
+
.forEach((item) => {
|
|
74
|
+
item.setExtras(undefined);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// eslint-disable-next-line functional/no-loop-statements
|
|
78
|
+
for (const extension of root.listExtensions()) {
|
|
79
|
+
// eslint-disable-next-line spellcheck/spell-checker
|
|
80
|
+
root.unregisterExtension(extension);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
85
|
+
async function cleanGlb(filePath) {
|
|
86
|
+
try {
|
|
87
|
+
const io = new NodeIO();
|
|
88
|
+
const doc = await io.read(filePath);
|
|
89
|
+
sanitizeDocument(doc);
|
|
90
|
+
await io.write(filePath, doc);
|
|
91
|
+
console.log(`🧼 Cleaned GLB metadata: ${filePath}`);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
console.warn(`⚠️ Failed to clean GLB: ${filePath}`, err.message);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
98
|
+
async function sanitizeAssets() {
|
|
99
|
+
const files = await fg(['**/*.{png,jpg,jpeg,svg,mp3,ogg,glb,gltf}'], {
|
|
100
|
+
cwd: TARGET_DIR,
|
|
101
|
+
absolute: true
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// eslint-disable-next-line functional/no-loop-statements
|
|
105
|
+
for (const file of files) {
|
|
106
|
+
const ext = path.extname(file).toLowerCase();
|
|
107
|
+
try {
|
|
108
|
+
if (['.png', '.jpg', '.jpeg'].includes(ext)) await cleanImage(file);
|
|
109
|
+
else if (ext === '.svg') await cleanSvg(file);
|
|
110
|
+
else if (['.mp3', '.ogg'].includes(ext)) await cleanMp3(file);
|
|
111
|
+
else if (['.glb'].includes(ext)) await cleanGlb(file);
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.warn(`⚠️ Failed to clean ${file}:`, err.message);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
console.log('✅ Done: all assets sanitized');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
sanitizeAssets();
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@hellpig/anarchy-shared",
|
|
3
3
|
"author": "S. Panfilov",
|
|
4
4
|
"private": false,
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.6.2",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
"publishConfig": {
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
|
-
"sideEffects":
|
|
19
|
+
"sideEffects": [
|
|
20
|
+
"**/*.scss"
|
|
21
|
+
],
|
|
20
22
|
"license": "MIT",
|
|
21
23
|
"description": "Anarchy-shared – a library with shared utilities and scripts for Anarchy Engine.",
|
|
22
24
|
"keywords": [
|
|
@@ -31,7 +33,9 @@
|
|
|
31
33
|
"NOTICE.md",
|
|
32
34
|
"LICENSE",
|
|
33
35
|
"README.md",
|
|
34
|
-
"dist/"
|
|
36
|
+
"dist/",
|
|
37
|
+
"ScriptUtils/",
|
|
38
|
+
"src/assets/"
|
|
35
39
|
],
|
|
36
40
|
"exports": {
|
|
37
41
|
"./Utils": {
|
|
@@ -50,10 +54,6 @@
|
|
|
50
54
|
"types": "./dist/Plugins/index.d.ts",
|
|
51
55
|
"import": "./dist/Plugins/index.es.js"
|
|
52
56
|
},
|
|
53
|
-
"./assets": {
|
|
54
|
-
"types": "./dist/assets/index.d.ts",
|
|
55
|
-
"import": "./dist/assets/index.es.js"
|
|
56
|
-
},
|
|
57
57
|
"./assets/*": "./src/assets/*",
|
|
58
58
|
"./ScriptUtils/*": "./ScriptUtils/*"
|
|
59
59
|
},
|
|
File without changes
|
|
File without changes
|