@hellpig/anarchy-shared 1.5.1 → 1.6.1

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 CHANGED
@@ -4,3 +4,4 @@
4
4
  - 1.3.0 Vite plugin: emit json
5
5
  - 1.4.0 Json validation utils
6
6
  - 1.5.0 Added proper building and npm packaging
7
+ - 1.6.0 Fixes npm package
package/NOTICE.md CHANGED
@@ -9,4 +9,4 @@ Full attributions and license texts are provided **offline** at:
9
9
 
10
10
  Nothing in this pointer modifies third-party licenses. If there is any conflict between this note and a third-party license, the third-party license controls.
11
11
 
12
- Questions: TBD until market release
12
+ Questions: pnf036+anarchy@gmail.com
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: {{PRIVACY_EMAIL}}, Security: {{SECURITY_EMAIL}}.
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();
@@ -32,7 +32,7 @@ The Project may include or interface with **third-party open-source components**
32
32
 
33
33
  ## Accessibility (EAA and similar)
34
34
 
35
- Where required by applicable law (e.g., the **EU Accessibility Act**), we aim to address **accessibility feedback** for distributed binaries **within reasonable and proportionate limits**. Contact: **TBD until market release**. This section **does not** create service levels or guarantees.
35
+ Where required by applicable law (e.g., the **EU Accessibility Act**), we aim to address **accessibility feedback** for distributed binaries **within reasonable and proportionate limits**. Contact: **pnf036+anarchy@gmail.com**. This section **does not** create service levels or guarantees.
36
36
 
37
37
  ## Security and Support
38
38
 
package/legal/EULA.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # End User License Terms — @hellpig/anarchy-shared
2
2
 
3
- **Effective date:** TBD until market release
3
+ **Effective date:** 11 January 2026
4
4
  This repository is provided under **MIT** (see `LICENSE`).
5
5
 
6
6
  1. **License & Ownership.** The code is licensed, not sold. Copyrights remain with the authors.
@@ -9,7 +9,7 @@ This repository is provided under **MIT** (see `LICENSE`).
9
9
  4. **Third-Party Components (License Precedence).** Dependencies retain their own licenses and notices. **If this text ever conflicts with a third-party license for a specific component, that third-party license governs for that component.** See `NOTICE` / `THIRD_PARTY_LICENSES`.
10
10
  5. **No Warranty / Liability.** Provided **“AS IS”**, **to the extent permitted by law**, without warranties; **statutory consumer rights (if any) are not affected**.
11
11
 
12
- **Accessibility.** Where required by law, we aim to address **accessibility feedback** for officially published artifacts **within reasonable and proportionate limits**. Contact: **TBD until market release**. This section does **not** create service levels or guarantees.
12
+ **Accessibility.** Where required by law, we aim to address **accessibility feedback** for officially published artifacts **within reasonable and proportionate limits**. Contact: **pnf036+anarchy@gmail.com**. This section does **not** create service levels or guarantees.
13
13
 
14
14
  ## Governing Language
15
15
 
package/legal/PRIVACY.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Privacy Notice — @hellpig/anarchy-shared
2
2
 
3
- **Effective date:** TBD until market release
3
+ **Effective date:** 11 January 2026
4
4
 
5
- **Controller:** Sergei Panfilov — Contact: TBD until market release
5
+ **Controller:** Sergei Panfilov — Contact: pnf036+anarchy@gmail.com
6
6
 
7
7
  **Scope / Identification.** This notice applies to the **open-source project** **@hellpig/anarchy-shared** (hosted on or mirrored to **public code hosting platform(s)**). We do **not** operate a consumer service through this repository.
8
8
 
@@ -19,7 +19,7 @@ Copies of public content may also appear in mirrors, forks, caches, archival cop
19
19
 
20
20
  ## 2. Communications
21
21
 
22
- If you email **TBD until market release**, we process your **email address**, **message content**, and any information you provide to respond.
22
+ If you email **pnf036+anarchy@gmail.com**, we process your **email address**, **message content**, and any information you provide to respond.
23
23
 
24
24
  **Legal basis:** our **legitimate interests** in responding to inquiries (or your **consent**, where applicable).
25
25
 
@@ -31,7 +31,7 @@ We may use service providers (e.g., email or CI hosting) as **processors**, boun
31
31
 
32
32
  ## 4. Your Rights
33
33
 
34
- Where applicable, you may request **access**, **rectification**, **erasure**, **restriction**, **objection**, **portability**, and **withdrawal of consent** (for processing based on consent) at **TBD until market release**. You may also contact your local supervisory authority (e.g., the Dutch **Autoriteit Persoonsgegevens**, Brazil’s **ANPD**, Canada’s **OPC**, Australia’s **OAIC**). We may request reasonable information to **verify your identity** before acting on a request and will respond **within timelines required by applicable law**.
34
+ Where applicable, you may request **access**, **rectification**, **erasure**, **restriction**, **objection**, **portability**, and **withdrawal of consent** (for processing based on consent) at **pnf036+anarchy@gmail.com**. You may also contact your local supervisory authority (e.g., the Dutch **Autoriteit Persoonsgegevens**, Brazil’s **ANPD**, Canada’s **OPC**, Australia’s **OAIC**). We may request reasonable information to **verify your identity** before acting on a request and will respond **within timelines required by applicable law**.
35
35
 
36
36
  ## 5. Security
37
37
 
@@ -49,4 +49,4 @@ We may update this notice by committing changes to the repository (the commit hi
49
49
 
50
50
  This **notice** is drafted in English. Translations may be provided for convenience. In markets where local-language versions are required by law for clarity and fairness, the local-language version controls to the extent required by applicable law; otherwise, the English version controls.
51
51
 
52
- **Contact:** TBD until market release
52
+ **Contact:** pnf036+anarchy@gmail.com
package/legal/SECURITY.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Security Policy — @hellpig/anarchy-shared
2
2
 
3
- **Effective date:** TBD until market release
3
+ **Effective date:** 11 January 2026
4
4
 
5
- **Security Contact:** TBD until market release (email preferred for sensitive reports)
5
+ **Security Contact:** pnf036+anarchy_security@gmail.com (email preferred for sensitive reports)
6
6
 
7
7
  ## Scope
8
8
 
@@ -13,7 +13,7 @@ It covers the Project’s **source code** and our **officially published release
13
13
 
14
14
  ## Reporting (CVD)
15
15
 
16
- - **Report:** email **TBD until market release** with steps to reproduce, affected commit/tag, and impact (if known). If possible, include a short patch or mitigation suggestion.
16
+ - **Report:** email **pnf036+anarchy_security@gmail.com** with steps to reproduce, affected commit/tag, and impact (if known). If possible, include a short patch or mitigation suggestion.
17
17
  - **Public disclosure:** please **do not file public issues with exploit details**; coordinate timing with us to allow a fix or mitigation to be available where reasonably possible.
18
18
 
19
19
  ## Handling & Disclosure
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.1",
5
+ "version": "1.6.1",
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": false,
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,8 @@
31
33
  "NOTICE.md",
32
34
  "LICENSE",
33
35
  "README.md",
34
- "dist/"
36
+ "dist/",
37
+ "ScriptUtils/"
35
38
  ],
36
39
  "exports": {
37
40
  "./Utils": {
@@ -50,10 +53,6 @@
50
53
  "types": "./dist/Plugins/index.d.ts",
51
54
  "import": "./dist/Plugins/index.es.js"
52
55
  },
53
- "./assets": {
54
- "types": "./dist/assets/index.d.ts",
55
- "import": "./dist/assets/index.es.js"
56
- },
57
56
  "./assets/*": "./src/assets/*",
58
57
  "./ScriptUtils/*": "./ScriptUtils/*"
59
58
  },