new publishing flow - s3 compatible

This commit is contained in:
nicwands
2026-04-06 15:48:43 -04:00
parent 5e633d9dd6
commit 921c4a29a7
10 changed files with 518 additions and 357 deletions

140
bin/uploadArtifacts.js Normal file
View File

@@ -0,0 +1,140 @@
import fs from 'fs'
import path from 'path'
import { execSync } from 'child_process'
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
import dotenv from 'dotenv'
dotenv.config()
const DIST_DIR = path.resolve('dist')
const BUCKET = 'dist'
const client = new S3Client({
region: 'us-east-1',
endpoint: 'https://s3.takerofnotes.com',
forcePathStyle: true,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
},
})
function getContentType(file) {
if (file.endsWith('.yml')) return 'text/yaml'
if (file.endsWith('.json')) return 'application/json'
if (file.endsWith('.AppImage')) return 'application/octet-stream'
if (file.endsWith('.deb')) return 'application/vnd.debian.binary-package'
if (file.endsWith('.exe'))
return 'application/vnd.microsoft.portable-executable'
if (file.endsWith('.dmg')) return 'application/x-apple-diskimage'
return 'application/octet-stream'
}
function getCurrentTag() {
try {
const tag = execSync('git describe --tags --exact-match 2>/dev/null', {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
}).trim()
return tag
} catch {
return null
}
}
function isGitTagDirty() {
try {
const status = execSync('git status --porcelain', {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
}).trim()
return status.length > 0
} catch {
return true
}
}
async function uploadFile(filePath, platform, version) {
const fileStream = fs.createReadStream(filePath)
const fileName = path.basename(filePath)
const key = `${platform}/${version}/${fileName}`
console.log(`Uploading ${fileName}${key}`)
await client.send(
new PutObjectCommand({
Bucket: BUCKET,
Key: key,
Body: fileStream,
ContentType: getContentType(fileName),
}),
)
}
async function main() {
const platform = process.argv[2]
const version = process.argv[3]
if (!platform || !version) {
console.error('Usage: node uploadArtifacts.js <platform> <version>')
console.error('Example: node uploadArtifacts.js win 1.0.0')
process.exit(1)
}
const currentTag = getCurrentTag()
if (!currentTag) {
console.log(
'No git tag found. Skipping upload (artifacts are only uploaded on tagged commits).',
)
return
}
if (currentTag !== version && !currentTag.startsWith(version)) {
console.log(
`Git tag (${currentTag}) does not match version (${version}). Skipping upload.`,
)
return
}
if (isGitTagDirty()) {
console.log('Git working directory is dirty. Skipping upload.')
return
}
console.log(
`Uploading artifacts for ${platform} v${version} (tag: ${currentTag})`,
)
if (!fs.existsSync(DIST_DIR)) {
throw new Error('dist directory not found. Run build first.')
}
const files = fs.readdirSync(DIST_DIR)
const uploadTargets = files.filter((file) => {
return (
file.endsWith('.yml') ||
file.endsWith('.AppImage') ||
file.endsWith('.deb') ||
file.endsWith('.exe') ||
file.endsWith('.dmg')
)
})
if (uploadTargets.length === 0) {
console.log('No artifacts found to upload.')
return
}
for (const file of uploadTargets) {
const fullPath = path.join(DIST_DIR, file)
await uploadFile(fullPath, platform, version)
}
console.log('Upload complete.')
}
main().catch((err) => {
console.error(err)
process.exit(1)
})

View File

@@ -30,14 +30,11 @@ dmg:
linux:
target:
- AppImage
- snap
# - snap
- deb
maintainer: electronjs.org
category: Utility
appImage:
artifactName: ${name}-${version}.${ext}
npmRebuild: false
publish:
provider: s3
bucket: dist
endpoint: https://s3.takerofnotes.com
publish: null

View File

@@ -1,6 +1,6 @@
import "dotenv/config";
import { electronApp, optimizer, is } from "@electron-toolkit/utils";
import { app, ipcMain, BrowserWindow, shell } from "electron";
import { app, ipcMain, BrowserWindow, dialog, shell } from "electron";
import filesystemPlugin from "@takerofnotes/plugin-filesystem";
import supabasePlugin from "@takerofnotes/plugin-supabase";
import s3Plugin from "@takerofnotes/plugin-s3";
@@ -577,6 +577,12 @@ app.whenReady().then(async () => {
);
}
});
ipcMain.handle("open-directory-dialog", async () => {
const result = await dialog.showOpenDialog({
properties: ["openDirectory"]
});
return result.canceled ? null : result.filePaths[0];
});
electronApp.setAppUserModelId("com.electron");
app.on("browser-window-created", (_, window) => {
optimizer.watchWindowShortcuts(window);

View File

@@ -38,6 +38,9 @@ const api = {
},
moveClosed: () => {
ipcRenderer.invoke("move-closed");
},
openDirectoryDialog: () => {
return ipcRenderer.invoke("open-directory-dialog");
}
};
if (process.contextIsolated) {

635
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -19,10 +19,9 @@
"build": "electron-vite build",
"build:web": "vite build",
"postinstall": "electron-builder install-app-deps",
"build:unpack": "npm run build && electron-builder --dir",
"build:win": "npm run build && electron-builder --win",
"build:mac": "npm run build && electron-builder --mac",
"build:linux": "npm run build && electron-builder --linux"
"build:win": "npm run build && electron-builder --win && node bin/uploadArtifacts.js win $npm_package_version",
"build:mac": "npm run build && electron-builder --mac && node bin/uploadArtifacts.js mac $npm_package_version",
"build:linux": "npm run build && electron-builder --linux && node bin/uploadArtifacts.js linux $npm_package_version"
},
"dependencies": {
"@capacitor/android": "^8.3.0",
@@ -64,6 +63,7 @@
"vue-router": "^5.0.3"
},
"devDependencies": {
"@aws-sdk/client-s3": "^3.1025.0",
"@capacitor/cli": "^8.3.0",
"@vitejs/plugin-vue": "^6.0.2",
"electron": "^39.2.6",

View File

@@ -1,6 +1,6 @@
import 'dotenv/config'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import { app, shell, BrowserWindow, ipcMain } from 'electron'
import { app, shell, BrowserWindow, ipcMain, dialog } from 'electron'
import filesystemPlugin from '@takerofnotes/plugin-filesystem'
import supabasePlugin from '@takerofnotes/plugin-supabase'
import s3Plugin from '@takerofnotes/plugin-s3'
@@ -137,6 +137,13 @@ app.whenReady().then(async () => {
}
})
ipcMain.handle('open-directory-dialog', async () => {
const result = await dialog.showOpenDialog({
properties: ['openDirectory'],
})
return result.canceled ? null : result.filePaths[0]
})
electronApp.setAppUserModelId('com.electron')
app.on('browser-window-created', (_, window) => {

View File

@@ -44,6 +44,9 @@ const api = {
moveClosed: () => {
ipcRenderer.invoke('move-closed')
},
openDirectoryDialog: () => {
return ipcRenderer.invoke('open-directory-dialog')
},
}
if (process.contextIsolated) {

View File

@@ -1,40 +1,58 @@
<template>
<div class="preferences-directory-input">
<input v-model="model" type="text" />
<button @click="openDirectoryPicker">Browse</button>
<label :for="key"> {{ label }}{{ required ? '*' : '' }} </label>
<div class="input-row">
<input
ref="fileInput"
type="file"
webkitdirectory
style="display: none"
@change="handleDirectoryChange"
v-model="model"
:id="key"
type="text"
:placeholder="default"
:required="required"
readonly
/>
<button @click="openDirectoryPicker" type="button">Browse</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps({
label: String,
key: String,
required: Boolean,
default: String,
})
const model = defineModel()
const fileInput = ref(null)
function openDirectoryPicker() {
fileInput.value?.click()
}
function handleDirectoryChange(event) {
const files = event.target.files
if (files.length > 0) {
const path = files[0].webkitRelativePath || files[0].path
const directoryPath = path.split('/').slice(0, -1).join('/')
model.value = directoryPath || files[0].path
const openDirectoryPicker = async () => {
const path = await window.api.openDirectoryDialog()
if (path) {
model.value = path
}
}
</script>
<style lang="scss">
.preferences-directory-input {
label {
display: block;
margin-bottom: 6px;
}
.input-row {
display: flex;
gap: 8px;
}
input {
flex: 1;
border: 1px solid var(--grey-100);
border-radius: 0.2em;
padding: 0.2em 0.5em;
}
button {
padding: 0.2em 0.5em;
}
}
</style>

View File

@@ -4,7 +4,7 @@
<preferences-text-input
v-model.trim="config.encryptionKey"
type="text"
type="password"
label="Encryption Key"
key="encryptionKey"
required