This commit is contained in:
netwworkmastered 2025-03-08 23:50:12 +00:00
commit 7a639a2c93
18 changed files with 3114 additions and 0 deletions

10
.editorconfig Normal file
View file

@ -0,0 +1,10 @@
# top-most EditorConfig file
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = tab
indent_size = 4
tab_width = 4

3
.eslintignore Normal file
View file

@ -0,0 +1,3 @@
node_modules/
main.js

23
.eslintrc Normal file
View file

@ -0,0 +1,23 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"env": { "node": true },
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
"@typescript-eslint/ban-ts-comment": "off",
"no-prototype-builtins": "off",
"@typescript-eslint/no-empty-function": "off"
}
}

22
.gitignore vendored Normal file
View file

@ -0,0 +1,22 @@
# vscode
.vscode
# Intellij
*.iml
.idea
# npm
node_modules
# Don't include the compiled main.js file in the repo.
# They should be uploaded to GitHub releases instead.
main.js
# Exclude sourcemaps
*.map
# obsidian
data.json
# Exclude macOS Finder (System Explorer) View States
.DS_Store

1
.npmrc Normal file
View file

@ -0,0 +1 @@
tag-version-prefix=""

5
LICENSE Normal file
View file

@ -0,0 +1,5 @@
Copyright (C) 2020-2025 by Dynalist Inc.
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

36
README.md Normal file
View file

@ -0,0 +1,36 @@
# Obsidian File Compressor
This plugin is designed to take large files and then compress them. Can typically do most file types. If you are compressing something other than a MD(MarkDown) please copy it so you have a backup. When a file is in a compressed state if its not human-readable or a plugin manages it then it may have issues.
## Installation
There are two ways to install this plugin which are as follows:
1. Install it from the obsidian community plugins tab.
2. Get the latest main.js from github releases and drag it into your vault's ".obsidian/plugins/compressor" folder. The "compressor" folder may not be there. If not then you should be able to create one. If you are on linux the .obsidian folder may be hidden, depending on your operating system it will be different. You should be able to look up how to view hidden folders. Or optionally you can cd into it.
## How to use
This plugin is made to need as little interaction as possible. All you need to do it create a new ctxt file(compressed text) and then open it up. Then it will do the compression for you.
If you wish to turn a file to a ctxt you can press the button with 2 arrows which will then convert the file. If you do use this then you will need to minimise and re open obsidian. Or click on the taskbar and back. This is an unknown bug.
This plugin does support markdown and uses the same editor as markdown files.
## What are the differences like?
The differences cannot be defined. But the bigger the file is the more it should be able to do. At the start it will not compress as it will not be any smaller.
## Buttons
This plugin comes with two buttons.
> **Create CTXT** - creates a new compressed file.
> **Convert to CTXT** - converts the currently open file to a ctxt file.
## Settings
> **Print Results** - Each time a compressed file is saved it will show the compression ratio(percentage of file size)
> **Show File Size** - This setting shows the currently opened/editing file's size in the status bar.
If there are any issues or requests, make sure to create a request!

49
esbuild.config.mjs Normal file
View file

@ -0,0 +1,49 @@
import esbuild from "esbuild";
import process from "process";
import builtins from "builtin-modules";
const banner =
`/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
`;
const prod = (process.argv[2] === "production");
const context = await esbuild.context({
banner: {
js: banner,
},
entryPoints: ["main.ts"],
bundle: true,
external: [
"obsidian",
"electron",
"@codemirror/autocomplete",
"@codemirror/collab",
"@codemirror/commands",
"@codemirror/language",
"@codemirror/lint",
"@codemirror/search",
"@codemirror/state",
"@codemirror/view",
"@lezer/common",
"@lezer/highlight",
"@lezer/lr",
...builtins],
format: "cjs",
target: "es2018",
logLevel: "info",
sourcemap: prod ? false : "inline",
treeShaking: true,
outfile: "main.js",
minify: prod,
});
if (prod) {
await context.rebuild();
process.exit(0);
} else {
await context.watch();
}

351
main.ts Normal file
View file

@ -0,0 +1,351 @@
import { App, MarkdownView, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
import { inflate, deflate } from "./util/pakoMinified"
import { BB } from "./util/BB"
import { encodeSafe, decodeSafe } from "./util/runlength"
interface compressorSettingsData {
// mySetting: string;
PrintResult: boolean;
FileSize: boolean;
LeaveRawLinks: boolean;
}
const DEFAULT_SETTINGS: compressorSettingsData = {
// mySetting: 'default',
PrintResult: true,
FileSize: true,
LeaveRawLinks: true
}
var globalLeafs: any[] = []
var NoticePool: Notice[] = []
var statusBarItemEl2: (undefined | HTMLElement);
export default class compressorPlugin extends Plugin {
settings: compressorSettingsData;
async onload() {
var loadTime = new Date().getTime()
this.registerExtensions(["ctxt"], "ctxt")
this.registerView("ctxt",
(leaf) => {
var hook = new MarkdownView(leaf)
var f = setInterval(() => {
var file = hook.file || this.app.workspace.getActiveFile()
if (file) {
clearInterval(f)
if (file && file.extension == "ctxt") {
//attempt to stop obsidian changing contents to compressed
hook.unload()
hook.canAcceptExtension("")
hook.editor.refresh = () => { }
globalLeafs.push(["ctxt", file.path, hook])
this.app.vault.read(file).then((data) => {
if (file && data && (!data.includes("\n") && !data.includes(" "))) {
new Notice("Attempting to load.")
setTimeout(() => {
var decompress = null
try {
decompress = decompressfile(data)
} catch (err) { console.log(err) }
if (decompress) {
hook.setViewData(decompress, true)
//attempt to stop obsidian changing contents to compressed
hook.unload()
hook.editor.refresh = () => { }
}
}, 50)
}
})
}
}
}, 10)
return hook
}
)
this.app.workspace.on("file-open", (file) => {
if (file && file.extension == "ctxt" && globalLeafs.length <= 0 && (new Date().getTime() - loadTime) < 1000) {
this.app.workspace.getMostRecentLeaf()?.openFile(file)
}
if (file) {
this.app.vault.read(file).then((data) => {
if (statusBarItemEl2 && this.settings.FileSize) statusBarItemEl2.setText(data.length + "B")
})
}
})
this.registerEvent(this.app.vault.on('modify', (file) => {
var file2 = this.app.vault.getFileByPath(file.path)
if (file2 && file2.extension == "ctxt") {
this.app.vault.read(file2).then((data) => {
if (statusBarItemEl2 && this.settings.FileSize) statusBarItemEl2.setText(data.length + "B")
if (file2 && data && (data.includes("\n") || data.includes(" "))) {
var compress = null
try {
NoticePool.forEach((n) => {
if (n) n.hide()
})
compress = compressfile(data, false, this.settings)
} catch (err) { console.log(err) }
if (compress) {
globalLeafs.forEach((leaf) => {
console.log(leaf)
if (leaf[0] == "ctxt" && leaf[2] == file.path && leaf[3]) {
// leaf[3].editor.setLine("")
leaf[3].unload()
}
})
this.app.vault.modify(file2, compress)
globalLeafs.forEach((leaf) => {
if (leaf[0] == "ctxt" && leaf[2] == file.path && leaf[3]) {
// leaf[3].editor.setLine("")
leaf[3].setViewData(data, true)
}
})
console.log(file.name + " has been saved")
}
}
})
} else if (file2) {
this.app.vault.read(file2).then((data) => {
if (statusBarItemEl2 && this.settings.FileSize) statusBarItemEl2.setText(data.length + "B")
})
}
}));
await this.loadSettings();
// const ribbonIconEl = this.addRibbonIcon('sheets-in-box', 'Compress/Decompress', (evt: MouseEvent) => {
// this.startAction()
// });
const ribbonIconEl2 = this.addRibbonIcon('checkmark', 'Create new ctxt', (evt: MouseEvent) => {
var filen = `newctxtfile${(new Date().getTime().toString().substring(10))}${Math.floor(Math.random() * 500)}.ctxt`
this.app.vault.create("./" + filen, "Write away!")//.then((filex) => {
// setTimeout(()=>{
// var file = this.app.vault.getFileByPath(filen)
// if (file) {
// console.log(file, this.app.workspace.getMostRecentLeaf())
// this.app.workspace.getMostRecentLeaf()?.openFile(file)
// }
// },100)
//filex null, file null, pain null
// })
});
const ribbonIconEl3 = this.addRibbonIcon('up-and-down-arrows', 'Convert currently opened file', (evt: MouseEvent) => {
if (confirm("Are you sure you want to convert")) { //will this work? electron confirms dont exit. Oh it does
var file = this.app.workspace.getActiveFile()
console.log(file)
if (file) {
if (file.extension == "ctxt") {
this.app.vault.read(file).then((data) => {
var raw = data
try {
var dc = decompressfile(data, true)
if (dc) raw = dc
} catch (_) {
new Notice("Failed to decompress, plainText?")
}
if (raw && file) {
this.app.vault.rename(file, file.path.substring(0, file.path.length - (file.extension.length + 1)) + ".md").then(() => {
if (raw && file) {
this.app.vault.modify(file, raw)
}
})
// this.app.vault.create(file.path+".md",raw).then(()=>{
// if(file) {
// this.app.vault.delete(file,false).then(()=>new Notice("Success!"))
// }
// })
} else {
new Notice("Failed.")
}
})
} else {
this.app.vault.read(file).then((data) => {
var raw = data
try {
var dc = compressfile(data, true)
if (dc) raw = dc
} catch (_) {
new Notice("Failed to compress.")
}
if (raw && file) {
this.app.vault.rename(file, file.path.substring(0, file.path.length - (file.extension.length + 1)) + ".ctxt").then(() => {
if (raw && file) {
this.app.vault.modify(file, raw).then(() => {
setTimeout(() => { if (file) this.app.workspace.getMostRecentLeaf()?.openFile(file); }, 100)
setTimeout(() => { if (file) this.app.workspace.getMostRecentLeaf()?.openFile(file) }, 300)
setTimeout(() => alert("Complete. The cursor will be invisible, press off of obsidian and back."), 500)
// setTimeout(() => { if (file) this.app.workspace.getMostRecentLeaf()?.detach() }, 300)
})
}
})
} else {
new Notice("Failed.")
}
})
}
}
}
});
ribbonIconEl2.addClass('compressor-class');
ribbonIconEl3.addClass('compressor-class');
//set elsewhere
statusBarItemEl2 = this.addStatusBarItem();
statusBarItemEl2.setText("");
statusBarItemEl2.title = "File Size"
this.addCommand({
id: 'createctxt',
name: 'Create a new ctxt file in root',
callback: () => {
var filen = `newctxtfile${(new Date().getTime().toString().substring(10))}${Math.floor(Math.random() * 500)}.ctxt`
this.app.vault.create("./" + filen, "Write away!")
}
});
this.addSettingTab(new compressorSettings(this.app, this));
// this.registerInterval(window.setInterval(() => {
// var file = this.app.workspace.getActiveFile()
// if (file != null) {
// this.app.vault.read(file).then((data) => {
// if (!data.includes("\n") && /[A-z0-9\-_]/gm.test(data) && !data.includes(" ")) {
// var bb = new BB()
// bb.G(data)
// var precont = []
// while (true) {
// var now = bb.RU()
// if (now >= 0) {
// precont[precont.length] = now
// } else break
// }
// var cont = new Uint8Array(precont)
// if (cont && cont.length > 0) {
// var decomp = inflate(cont)
// if (typeof (decomp) == "object") {
// if (pane) pane.innerHTML += `<div id="AEDTCOMPDNEOUTSNTTIS!" style="position: absolute;left:0%;right:0%;width:100%;height:100%;background-color:rgba(20,20,20,0.6);z-index: 99999999999;">Content Compressed.</div>`
// }
// }
// }
// })
// const pane = document.querySelector("body > div.app-container > div.horizontal-main-container > div > div.workspace-split.mod-vertical.mod-root > div > div.workspace-tab-container > div.workspace-leaf.mod-active > div > div.view-content > div.markdown-source-view.cm-s-obsidian.mod-cm6.node-insert-event.is-readable-line-width.is-live-preview.is-folding.show-properties > div")
// if (document.getElementById("AEDTCOMPDNEOUTSNTTIS!")) document.getElementById("AEDTCOMPDNEOUTSNTTIS!")?.remove()
// }
// }, 100));
//<div style="position: absolute;left:0%;right:0%;width:100%;height:100%;background-color:rgba(20,20,20,0.6);z-index: 99999999999;">Content Compressed.</div>
}
onunload() {
alert("In order to view CTXT files you will need to re-enable the plugin. If you are deleting the plugin, please convert your files back before.")
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
}
}
function compressfile(data: string, notice?: boolean, settings?: compressorSettingsData) {
const comp = deflate(data)
var inf = inflate(comp)
if (inf && new TextDecoder().decode(inf) != data) {
if (notice) new Notice("Revert is corrupted.", 3000)
throw new Error("Revert not same,")
}
if (comp) {
var bb = new BB()
comp.forEach((byte) => {
// console.log(byte)
bb.WU(byte)
})
// var linkblk = ""
// ;[...data.matchAll(/\[\[[^\]]*\]\]/gm)].forEach((match) => {
// console.log(match)
// linkblk += match[0]
// })
var encd = encodeSafe(bb.G())
if (encd) {
var content = `DATABLK` + encd
if (content && content.length < data.length) {
if (notice) new Notice("Success!")
if (settings && settings.PrintResult) {
NoticePool.push(new Notice(`Compression:\nratio:${((content.length / data.length) * 100).toFixed(1)}%`))
}
return content
} else {
if (notice) new Notice("File isnt smaller.")
}
} else {
new Notice("Encoder failure.")
}
}
}
function decompressfile(data: string, notice?: boolean) {
var bb = new BB()
bb.F(decodeSafe(data.substring(data.indexOf("DATABLK") + 7)))
var precont: number[] = []
while (true) {
var now = bb.RU()
if (!isNaN(now)) {
precont[precont.length] = now
} else break
}
var cont = new Uint8Array(precont)
if (cont && cont.length > 0) {
var decomp = inflate(cont)
if (typeof (decomp) == "object") {
decomp = new TextDecoder().decode(decomp)
if (notice) new Notice("Success!")
return decomp
} else {
if (notice) new Notice("Failed.")
}
} else {
if (notice) new Notice("Couldnt Decompress.")
}
}
class compressorSettings extends PluginSettingTab {
plugin: compressorPlugin;
constructor(app: App, plugin: compressorPlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const { containerEl } = this;
containerEl.empty();
new Setting(containerEl)
.setName('Print the compression results')
.setDesc('Everytime you\'re in a ctxt file and it gets saved itll then print out the compression ratio and byte save.')
.addToggle(bool => bool
.setValue(this.plugin.settings.PrintResult)
.onChange(async (value) => {
this.plugin.settings.PrintResult = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Show the file storage size in the statusbar')
.setDesc('The amount of storage a file is taking up will be shown in the status bar of the open file.')
.addToggle(bool => bool
.setValue(this.plugin.settings.FileSize)
.onChange(async (value) => {
this.plugin.settings.FileSize = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('NOTICE:')
.setDesc('Unfortunetly the graph view will not maintain links. The graph cannot read the compressed data. Nor if the links are raw')
}
}

11
manifest.json Normal file
View file

@ -0,0 +1,11 @@
{
"id": "compress",
"name": "File Compressor",
"version": "1.0.0",
"minAppVersion": "0.15.0",
"description": "This plugin will add the .ctxt file type which will allow you to compress notes to a smaller version.\nNOTE: If you create a link in a compressed file it will NOT show in graph view.\nWhen using this plugin data loss is possible, we've tried to minimise it. But theres always a change. If you do experiance data loss we are not liable. But you may create an issue request with the raw data(do not provide personal info).",
"author": "networkmastered",
"authorUrl": "https://github.com/networkmastered",
"fundingUrl": "https://github.com/networkmastered",
"isDesktopOnly": false
}

2397
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

24
package.json Normal file
View file

@ -0,0 +1,24 @@
{
"name": "compress",
"version": "1.0.0",
"description": "This plugin will add the .ctxt file type which will allow you to compress notes to a smaller version.\nNOTE: If you create a link in a compressed file it will NOT show in graph view.\nWhen using this plugin data loss is possible, we've tried to minimise it. But theres always a change. If you do experiance data loss we are not liable. But you may create an issue request with the raw data(do not provide personal info).",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json"
},
"keywords": [],
"author": "networkmastered",
"license": "MIT",
"devDependencies": {
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0",
"esbuild": "0.17.3",
"obsidian": "latest",
"tslib": "2.4.0",
"typescript": "4.7.4"
}
}

24
tsconfig.json Normal file
View file

@ -0,0 +1,24 @@
{
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES6",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"strictNullChecks": true,
"lib": [
"DOM",
"ES5",
"ES6",
"ES7"
]
},
"include": [
"**/*.ts"
]
}

65
util/BB.js Normal file
View file

@ -0,0 +1,65 @@
export class BB {
constructor() {
this.PT = 0
this.BB = []
this.NT = { "0": "A", "1": "B", "2": "C", "3": "D", "4": "E", "5": "F", "6": "G", "7": "H", "8": "I", "9": "J", "10": "K", "11": "L", "12": "M", "13": "N", "14": "O", "15": "P", "16": "Q", "17": "R", "18": "S", "19": "T", "20": "U", "21": "V", "22": "W", "23": "X", "24": "Y", "25": "Z", "26": "a", "27": "b", "28": "c", "29": "d", "30": "e", "31": "f", "32": "g", "33": "h", "34": "i", "35": "j", "36": "k", "37": "l", "38": "m", "39": "n", "40": "o", "41": "p", "42": "q", "43": "r", "44": "s", "45": "t", "46": "u", "47": "v", "48": "w", "49": "x", "50": "y", "51": "z", "52": "0", "53": "1", "54": "2", "55": "3", "56": "4", "57": "5", "58": "6", "59": "7", "60": "8", "61": "9", "62": "-", "63": "_" }
this.BT = { "0": 52, "1": 53, "2": 54, "3": 55, "4": 56, "5": 57, "6": 58, "7": 59, "8": 60, "9": 61, "A": 0, "B": 1, "C": 2, "D": 3, "E": 4, "F": 5, "G": 6, "H": 7, "I": 8, "J": 9, "K": 10, "L": 11, "M": 12, "N": 13, "O": 14, "P": 15, "Q": 16, "R": 17, "S": 18, "T": 19, "U": 20, "V": 21, "W": 22, "X": 23, "Y": 24, "Z": 25, "a": 26, "b": 27, "c": 28, "d": 29, "e": 30, "f": 31, "g": 32, "h": 33, "i": 34, "j": 35, "k": 36, "l": 37, "m": 38, "n": 39, "o": 40, "p": 41, "q": 42, "r": 43, "s": 44, "t": 45, "u": 46, "v": 47, "w": 48, "x": 49, "y": 50, "z": 51, "-": 62, "_": 63 }
this.PO = {}
this.gp()
}
gp() { for (let i = 0; i <= 64; i++) { this.PO[i] = Math.pow(2, i) } }
F(str) {
for (let i = 0; i < str.length; i++) {
let ch = this.BT[str[i]]
for (let j = 0; j < 6; j++) {
this.PT++
this.BB[this.PT] = ch % 2
ch = Math.floor(ch / 2)
}
}
}
G() {
let str = ""
let accum = 0
let pow = 0
for (let i = 1; i <= Math.ceil(this.BB.length / 6) * 6; i++) {
accum += this.PO[pow] * (this.BB[i] || 0)
pow++
if (pow >= 6) {
str += this.NT[accum]
accum = 0
pow = 0
}
}
return str
}
WU(value) {
this.BB[this.PT + 1] = value % 2
value = Math.floor(value / 2)
this.BB[this.PT + 2] = value % 2
value = Math.floor(value / 2)
this.BB[this.PT + 3] = value % 2
value = Math.floor(value / 2)
this.BB[this.PT + 4] = value % 2
value = Math.floor(value / 2)
this.BB[this.PT + 5] = value % 2
value = Math.floor(value / 2)
this.BB[this.PT + 6] = value % 2
value = Math.floor(value / 2)
this.BB[this.PT + 7] = value % 2
value = Math.floor(value / 2)
this.BB[this.PT + 8] = value % 2
value = Math.floor(value / 2)
this.PT += 8
for (let i = 0; i < 8; i++) {
this.PT++
this.BB[this.PT] = value % 2
value = Math.floor(value / 2)
}
}
RU() {
let r = this.BB[this.PT+1] * this.PO[0] + this.BB[this.PT + 2] * this.PO[1] + this.BB[this.PT + 3] * this.PO[2] + this.BB[this.PT + 4] * this.PO[3] + this.BB[this.PT + 5] * this.PO[4] + this.BB[this.PT + 6] * this.PO[5] + this.BB[this.PT + 7] * this.PO[6] + this.BB[this.PT + 8] * this.PO[7]
this.PT += 8
return r
}
}

2
util/pakoMinified.js Normal file

File diff suppressed because one or more lines are too long

74
util/runlength.js Normal file
View file

@ -0,0 +1,74 @@
//input is b64 so doesnt contain specials.
const charMap = [
'á',
'é',
'í',
'ó',
'ú',
'ü',
'ñ',
'ç',
'¿',
'¡'
]
//En
function ENum(num) {
let encoded = ''
for (let digit of num.toString()) {
encoded += charMap[parseInt(digit)]
}
return encoded
}
//De
function DNum(encoded) {
let decoded = ''
for (let char of encoded) {
decoded += charMap.indexOf(char)
}
return decoded
}
//V2
export function encodeSafe(str) {
var count = 0
var typ = ""
var out = ""
str.split("").forEach((char) => {
if (char != typ && typ != "") {
if (count > 2) {
out += ENum(count) + typ
} else {
out += typ.repeat(count)
}
count = 0
}
typ = char
count++
})
if (typ != "" && count > 0) {
if (count > 2) {
out += ENum(count) + typ
} else {
out += typ.repeat(count)
}
}
if (decodeSafe(out) == str) {
return out
}
}
export function decodeSafe(str) {
var count = ""
var out = ""
str.split("").forEach((char) => {
if (charMap.includes(char)) {
count += char
} else {
if (count == "") {
out += char
} else {
out += char.repeat(DNum(count))
count = ""
}
}
})
return out
}

14
version-bump.mjs Normal file
View file

@ -0,0 +1,14 @@
import { readFileSync, writeFileSync } from "fs";
const targetVersion = process.env.npm_package_version;
// read minAppVersion from manifest.json and bump version to target version
let manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
const { minAppVersion } = manifest;
manifest.version = targetVersion;
writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t"));
// update versions.json with target version and minAppVersion from manifest.json
let versions = JSON.parse(readFileSync("versions.json", "utf8"));
versions[targetVersion] = minAppVersion;
writeFileSync("versions.json", JSON.stringify(versions, null, "\t"));

3
versions.json Normal file
View file

@ -0,0 +1,3 @@
{
"1.0.0": "0.15.0"
}