Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a5cc191fe | ||
|
|
817af0e9ba | ||
|
|
3a4f1eed68 | ||
|
|
c1154a58d5 | ||
|
|
727df78b53 | ||
|
|
ba656eb679 |
@@ -37,3 +37,105 @@ jobs:
|
||||
tag_name: ${{ github.ref_name }}
|
||||
name: gSheets-Rate-Assistant ${{ github.ref_name }}
|
||||
files: dist/*.exe
|
||||
|
||||
- name: Mirror release to GitHub
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }}
|
||||
RELEASE_GITHUB_REPO: ${{ secrets.RELEASE_GITHUB_REPO }}
|
||||
run: |
|
||||
python - <<'PY'
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
token = os.environ.get("GITHUB_TOKEN", "")
|
||||
repository = os.environ.get("RELEASE_GITHUB_REPO", "")
|
||||
|
||||
if not token or not repository:
|
||||
print("Skipping GitHub mirror because RELEASE_GITHUB_REPO or RELEASE_GITHUB_TOKEN is missing.")
|
||||
raise SystemExit(0)
|
||||
|
||||
tag = os.environ["BUILD_VERSION"]
|
||||
release_name = f"gSheets-Rate-Assistant {tag}"
|
||||
asset_path = next((os.path.join("dist", name) for name in os.listdir("dist") if name.endswith(".exe")), None)
|
||||
|
||||
if asset_path is None:
|
||||
raise SystemExit("No Windows executable found in dist/")
|
||||
|
||||
def request_json(url, method="GET", payload=None, headers=None):
|
||||
request_headers = {
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Accept": "application/vnd.github+json",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
}
|
||||
if headers:
|
||||
request_headers.update(headers)
|
||||
|
||||
data = None if payload is None else json.dumps(payload).encode("utf-8")
|
||||
if data is not None:
|
||||
request_headers["Content-Type"] = "application/json"
|
||||
|
||||
request = urllib.request.Request(url, data=data, method=method, headers=request_headers)
|
||||
try:
|
||||
with urllib.request.urlopen(request) as response:
|
||||
body = response.read().decode("utf-8")
|
||||
return json.loads(body) if body else {}
|
||||
except urllib.error.HTTPError as error:
|
||||
message = error.read().decode("utf-8", errors="replace")
|
||||
print(message, file=sys.stderr)
|
||||
raise
|
||||
|
||||
release_url = f"https://api.github.com/repos/{repository}/releases/tags/{urllib.parse.quote(tag)}"
|
||||
release = None
|
||||
|
||||
try:
|
||||
release = request_json(release_url)
|
||||
except urllib.error.HTTPError as error:
|
||||
if error.code != 404:
|
||||
raise
|
||||
|
||||
payload = {
|
||||
"tag_name": tag,
|
||||
"name": release_name,
|
||||
"draft": False,
|
||||
"prerelease": False,
|
||||
"generate_release_notes": False,
|
||||
}
|
||||
|
||||
if release is None:
|
||||
release = request_json(f"https://api.github.com/repos/{repository}/releases", method="POST", payload=payload)
|
||||
else:
|
||||
release = request_json(release["url"], method="PATCH", payload=payload)
|
||||
|
||||
asset_name = os.path.basename(asset_path)
|
||||
for asset in release.get("assets", []):
|
||||
if asset.get("name") == asset_name:
|
||||
request_json(asset["url"], method="DELETE")
|
||||
|
||||
upload_url = release["upload_url"].split("{", 1)[0]
|
||||
with open(asset_path, "rb") as asset_file:
|
||||
asset_data = asset_file.read()
|
||||
|
||||
upload_request = urllib.request.Request(
|
||||
f"{upload_url}?name={urllib.parse.quote(asset_name)}",
|
||||
data=asset_data,
|
||||
method="POST",
|
||||
headers={
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Accept": "application/vnd.github+json",
|
||||
"Content-Type": "application/octet-stream",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
},
|
||||
)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(upload_request) as response:
|
||||
response.read()
|
||||
except urllib.error.HTTPError as error:
|
||||
message = error.read().decode("utf-8", errors="replace")
|
||||
print(message, file=sys.stderr)
|
||||
raise
|
||||
PY
|
||||
|
||||
@@ -9,6 +9,7 @@ dist/
|
||||
# Credentials and config (do NOT commit secrets)
|
||||
credentials.json
|
||||
credentials-*.json
|
||||
serviceAccounts/
|
||||
config.json
|
||||
config-*.json
|
||||
.env
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "google-sheets-rate-assistant",
|
||||
"version": "2.1.0",
|
||||
"version": "2.1.9",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "google-sheets-rate-assistant",
|
||||
"version": "2.1.0",
|
||||
"version": "2.1.9",
|
||||
"dependencies": {
|
||||
"csv-stringify": "^6.0.0",
|
||||
"googleapis": "^173.0.0",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "google-sheets-rate-assistant",
|
||||
"version": "2.1.6",
|
||||
"version": "2.1.9",
|
||||
"description": "Pull Google Sheets data and export to CSV with configurable schedules and rate limiting.",
|
||||
"main": "index.js",
|
||||
"bin": "index.js",
|
||||
|
||||
@@ -63,18 +63,6 @@ If `config.json` is missing, the app creates a starter file next to the executab
|
||||
|
||||
If you choose the wizard, it will ask for the auth method, credential count, document count, and sheet names. It will not ask for output directories or file names.
|
||||
|
||||
## Windows Packaging
|
||||
|
||||
To build a Windows executable from source, run:
|
||||
|
||||
```bash
|
||||
npm run build:win
|
||||
```
|
||||
|
||||
That creates `dist/gSheets-Rate-Assistant-v2.0.0.exe` using the current version in the filename. For distribution, ship the exe alongside a `config.json` and any service account credential files, or set `GSA_CONFIG` to point at a different config file.
|
||||
|
||||
Gitea now builds only on version tags like `v1.1.1` through the workflow in `.gitea/workflows/build.yml`.
|
||||
|
||||
## AI Disclosure
|
||||
|
||||
A large chunk of the project was coded using AI, this is the first pass at integrating some AI assistance into my projects. However all code written has been verified and checked before submission. AI use has only been included since the v2 rewrite.
|
||||
|
||||
Reference in New Issue
Block a user