Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 77 additions & 2 deletions .github/workflows/build-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,37 @@ jobs:
if: runner.os == 'Linux'
run: sudo apt-get install -y fakeroot

# ── 4. Build fat JAR + native installer ──────────────────────────────
# ── 4a. macOS: import Developer ID certificate into a temp keychain ──
# Prérequis : créer un certificat "Developer ID Application" sur
# https://developer.apple.com/account/resources/certificates/add
# (section Software → Developer ID Application), puis l'exporter
# depuis Keychain Access en .p12 (clic droit → Exporter).
#
# Secrets requis (optionnels – DMG non signé si absents) :
# MACOS_CERTIFICATE base64 du .p12 → base64 -i cert.p12 | pbcopy
# MACOS_CERTIFICATE_PWD mot de passe choisi lors de l'export .p12
- name: Import Developer ID certificate (macOS)
if: runner.os == 'macOS'
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
run: |
if [[ -z "${MACOS_CERTIFICATE:-}" ]]; then
echo "::notice::MACOS_CERTIFICATE not configured – DMG will be unsigned"
exit 0
fi
echo "$MACOS_CERTIFICATE" | base64 --decode > /tmp/cert.p12
security create-keychain -p "" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "" build.keychain
security import /tmp/cert.p12 -k build.keychain \
-P "$MACOS_CERTIFICATE_PWD" \
-T /usr/bin/codesign \
-T "$(which jpackage 2>/dev/null || true)"
security set-key-partition-list -S apple-tool:,apple: -s -k "" build.keychain
rm /tmp/cert.p12

# ── 4b. Build fat JAR + native installer ─────────────────────────────
# The active OS profile (linux / mac / win) is detected automatically
# by Maven via <os><family> activation in pom.xml.
# maven_extra_args overrides javafx.platform for the two macOS arches.
Expand All @@ -85,10 +115,55 @@ jobs:

- name: Build and package
shell: bash
env:
MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY }}
run: |
ARGS="${{ matrix.maven_extra_args }}"
[[ -n "${JPACKAGE_APP_VERSION:-}" ]] && ARGS="$ARGS -Djpackage.app.version=$JPACKAGE_APP_VERSION"
mvn package -Ppackage $ARGS --batch-mode
# Enable Developer ID signing on macOS when the secret is configured
SIGN_ARGS=()
if [[ "$RUNNER_OS" == "macOS" ]] && [[ -n "${MACOS_SIGNING_IDENTITY:-}" ]]; then
SIGN_ARGS=(-Pmac-sign "-Djpackage.mac.signing.key=${MACOS_SIGNING_IDENTITY}")
fi
mvn package -Ppackage $ARGS "${SIGN_ARGS[@]}" --batch-mode

# ── 4c. macOS: notarise and staple the DMG ────────────────────────────
# Utilise un mot de passe d'app (app-specific password) — différent
# du vrai mot de passe Apple ID, généré sur :
# https://appleid.apple.com → Connexion & sécurité → Mots de passe
# d'app → "+" → nommer "marknote-notary" → copier le mot de passe.
#
# Secrets requis (optionnels – notarisation ignorée si absents) :
# APPLE_ID adresse e-mail du compte Apple Developer
# APPLE_TEAM_ID Team ID (visible sur developer.apple.com/account)
# APPLE_APP_SPECIFIC_PWD mot de passe d'app généré sur appleid.apple.com
- name: Notarize and staple DMG (macOS)
if: runner.os == 'macOS'
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_APP_SPECIFIC_PWD: ${{ secrets.APPLE_APP_SPECIFIC_PWD }}
run: |
if [[ -z "${APPLE_APP_SPECIFIC_PWD:-}" ]]; then
echo "::notice::APPLE_APP_SPECIFIC_PWD not configured – skipping notarization"
exit 0
fi
DMG=$(ls target/dist/*.dmg 2>/dev/null | head -1)
if [[ -z "$DMG" ]]; then
echo "::error::No DMG found in target/dist/ – notarization skipped"
exit 1
fi
xcrun notarytool submit "$DMG" \
--apple-id "$APPLE_ID" \
--team-id "$APPLE_TEAM_ID" \
--password "$APPLE_APP_SPECIFIC_PWD" \
--wait
xcrun stapler staple "$DMG"

# ── 4d. Always clean up the temp keychain ────────────────────────────
- name: Clean up keychain (macOS)
if: always() && runner.os == 'macOS'
run: security delete-keychain build.keychain 2>/dev/null || true

# ── 5. Upload installer as workflow artifact ──────────────────────────
# Artifact is available in the Actions run page for 90 days.
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
target/
notarize-poll.sh
notarization-error.log
46 changes: 43 additions & 3 deletions build
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
#!/bin/bash
set -e
#---- project parameters
project_name=MarkNote
project_version=0.1.5
main_class=Main
JARS=
JFX_VERSION=25
vendor_name="SNAPGAMES"
author_name="Frédéric Delorme<contact.snapgames@gmail.com>"
author_name="Frédéric Delorme<contact.snapgames@gmail.com>/JB Meyer<jib@jibstudios.com>"
# macOS signing + notarisation (optional) – set via env vars before calling: ./build package
# Signing : export MAC_SIGNING_IDENTITY="Developer ID Application: Nom (TEAMXXXX)"
# Certificat à créer sur developer.apple.com/account/resources/certificates/add
# → Software → Developer ID Application
# Notarisation locale : utilise un profil stocké dans le Keychain via :
# xcrun notarytool store-credentials "marknote-notary" \
# --apple-id "votre@email.com" --team-id "TEAMXXXX" \
# --password "xxxx-xxxx-xxxx-xxxx" ← mot de passe d'app (≠ mot de passe Apple ID)
# généré sur https://appleid.apple.com → Connexion & sécurité → Mots de passe d'app
mac_signing_identity="${MAC_SIGNING_IDENTITY:-}"
mac_notary_profile="${MAC_NOTARY_PROFILE:-marknote-notary}"
#
#--- DO NOT CHANGE THE FOLLOWING LINES ---
#
Expand Down Expand Up @@ -510,11 +522,25 @@ WIN_INSTALL_EOF2
[[ -f "${GENERATED_ICNS}" ]] && JPACKAGE_ICON_OPT=(--icon "${GENERATED_ICNS}")
fi

# jpackage requires the first version digit to be >= 1 (e.g. 0.1.5 → 1.1.5)
local JPACKAGE_VERSION="${project_version}"
if [[ "${JPACKAGE_VERSION%%.*}" == "0" ]]; then
JPACKAGE_VERSION="1.${JPACKAGE_VERSION#*.}"
echo " Version ajustée pour jpackage : ${project_version} → ${JPACKAGE_VERSION}"
fi

# Signing options (optional – set MAC_SIGNING_IDENTITY env var to enable)
local SIGN_OPTS=()
if [[ -n "${mac_signing_identity}" ]]; then
SIGN_OPTS=(--mac-sign --mac-signing-key-user-name "${mac_signing_identity}")
echo " Code signing enabled: ${mac_signing_identity}"
fi

mkdir -p "${TARGET}/dist"
jpackage \
--type dmg \
--name "${project_name}" \
--app-version "${project_version}" \
--app-version "${JPACKAGE_VERSION}" \
--vendor "${vendor_name}" \
--description "Markdown Note Editor" \
--input "${JPACKAGE_INPUT}" \
Expand All @@ -526,7 +552,8 @@ WIN_INSTALL_EOF2
--dest "${TARGET}/dist" \
--mac-package-identifier "com.snapgames.marknote" \
--mac-package-name "${project_name}" \
"${JPACKAGE_ICON_OPT[@]}"
"${JPACKAGE_ICON_OPT[@]}" \
"${SIGN_OPTS[@]}"

[[ -n "${ICON_TMPDIR}" ]] && rm -rf "${ICON_TMPDIR}"

Expand All @@ -535,6 +562,19 @@ WIN_INSTALL_EOF2
if [[ -f "${DMG_FILE}" ]]; then
echo -e "${GREEN}macOS DMG created: ${DMG_FILE}${NC}"
echo "Package size: $(du -sh "${DMG_FILE}" | cut -f1)"
# Notarisation (uniquement si signing activé)
if [[ -n "${mac_signing_identity}" ]]; then
echo " Submitting DMG for notarization (keychain-profile: ${mac_notary_profile})..."
if xcrun notarytool submit "${DMG_FILE}" \
--keychain-profile "${mac_notary_profile}" \
--wait; then
echo " Notarization successful. Stapling ticket to DMG..."
xcrun stapler staple "${DMG_FILE}"
echo -e "${GREEN} DMG notarisé et agrafé.${NC}"
else
echo -e "${YELLOW} Notarization failed – DMG signé mais non notarisé.${NC}"
fi
fi
else
echo -e "${YELLOW}Warning: DMG not found – check ${TARGET}/dist/${NC}"
ls -lh "${TARGET}/dist/" 2>/dev/null || true
Expand Down
Binary file added libs/common/eddsa-0.3.0.jar
Binary file not shown.
Binary file removed libs/common/javafx-base-24.jar
Binary file not shown.
Binary file removed libs/common/javafx-controls-24.jar
Binary file not shown.
Binary file removed libs/common/javafx-fxml-24.jar
Binary file not shown.
Binary file removed libs/common/javafx-graphics-24.jar
Binary file not shown.
Binary file removed libs/common/javafx-media-24.jar
Binary file not shown.
Binary file removed libs/common/javafx-web-24.jar
Binary file not shown.
Binary file added libs/common/jcl-over-slf4j-1.7.36.jar
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added libs/common/slf4j-api-1.7.36.jar
Binary file not shown.
Binary file added libs/common/sshd-osgi-2.14.0.jar
Binary file not shown.
Binary file added libs/common/sshd-sftp-2.14.0.jar
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added libs/mac/javafx-media-24-mac-aarch64.jar
Binary file not shown.
Binary file removed libs/mac/javafx-media-24-mac.jar
Binary file not shown.
Binary file not shown.
26 changes: 25 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
</developer>
<developer>
<name>Jean-Baptiste Meyer</name>
<email>unfixed11_scarab@icloud.com</email>
<email>jib@jibstudios.com</email>
</developer>
</developers>

Expand Down Expand Up @@ -74,6 +74,9 @@
<jpackage.win.dir.chooser>false</jpackage.win.dir.chooser>
<jpackage.win.menu>false</jpackage.win.menu>
<jpackage.win.shortcut>false</jpackage.win.shortcut>
<!-- macOS signing defaults (overridden by mac-sign profile) -->
<jpackage.mac.sign>false</jpackage.mac.sign>
<jpackage.mac.signing.key></jpackage.mac.signing.key>
</properties>

<!-- ═══════════════════════════════════════════════════════════════════════
Expand Down Expand Up @@ -487,6 +490,7 @@
<properties>
<javafx.platform>mac</javafx.platform>
<jpackage.type>DMG</jpackage.type>
<jpackage.icon>${project.basedir}/src/main/resources/images/icons/marknote.icns</jpackage.icon>
</properties>
</profile>

Expand All @@ -502,6 +506,7 @@
<properties>
<javafx.platform>mac-aarch64</javafx.platform>
<jpackage.type>DMG</jpackage.type>
<jpackage.icon>${project.basedir}/src/main/resources/images/icons/marknote.icns</jpackage.icon>
</properties>
</profile>

Expand All @@ -523,6 +528,19 @@
</properties>
</profile>

<!-- ── mac-sign : signature Developer ID + notarisation ─────────────
Activation : mvn package -Ppackage,mac-sign
Requiert : variable d'env MAC_SIGNING_IDENTITY
ex: Developer ID Application: First Name Last Name (TEAMXXXX)
──────────────────────────────────────────────────────────────── -->
<profile>
<id>mac-sign</id>
<properties>
<jpackage.mac.sign>true</jpackage.mac.sign>
<jpackage.mac.signing.key>${env.MAC_SIGNING_IDENTITY}</jpackage.mac.signing.key>
</properties>
</profile>

<!-- ── package : native installer via jpackage ───────────────────────
Activation : mvn package -Ppackage
Équivalent de : build package / build package-all
Expand Down Expand Up @@ -646,6 +664,12 @@
<!-- ── macOS-specific ──────────────────────────────── -->
<macPackageIdentifier>${jpackage.bundle.id}</macPackageIdentifier>
<macPackageName>${project.name}</macPackageName>
<!--
Signature Developer ID : activer avec -Pmac-sign et
export MAC_SIGNING_IDENTITY="Developer ID Application: Nom (TEAMID)"
-->
<macSign>${jpackage.mac.sign}</macSign>
<macSigningKeyUserName>${jpackage.mac.signing.key}</macSigningKeyUserName>
<!--
NOTE: jpackage ne supporte pas de script post-install pour le
type DMG. Le script post-install-mac.sh doit être distribué
Expand Down
Binary file added src/main/resources/images/icons/marknote.icns
Binary file not shown.
70 changes: 35 additions & 35 deletions src/test/java/utils/DebouncerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,62 +20,62 @@ class DebouncerTest {

@BeforeEach
void setUp() {
debouncer = new Debouncer(100); // 100ms pour les tests
debouncer = new Debouncer(500); // 500ms — wide margin against CI scheduler jitter
}

@AfterEach
void tearDown() {
debouncer.shutdown();
}

@Test
void testDebounceExecutesAfterDelay() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
boolean[] executed = {false};

debouncer.debounce(() -> {
executed[0] = true;
latch.countDown();
});

// L'action ne doit pas s'être exécutée immédiatement
assertFalse(executed[0]);

// Attendre le délai
assertTrue(latch.await(200, TimeUnit.MILLISECONDS));
assertTrue(latch.await(1000, TimeUnit.MILLISECONDS));
assertTrue(executed[0]);
}

@Test
void testDebounceCancelsPreviousCall() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
List<Integer> executionOrder = new ArrayList<>();

// Premier appel
debouncer.debounce(() -> {
executionOrder.add(1);
});
// Deuxième appel avant le délai
Thread.sleep(50);

// Deuxième appel bien avant le délai (100ms << 500ms)
Thread.sleep(100);
debouncer.debounce(() -> {
executionOrder.add(2);
latch.countDown();
});

// Attendre
assertTrue(latch.await(200, TimeUnit.MILLISECONDS));
assertTrue(latch.await(1000, TimeUnit.MILLISECONDS));

// Seule la deuxième action doit s'être exécutée
assertEquals(List.of(2), executionOrder);
}

@Test
void testMultipleConsecutiveCallsOnlyLastExecutes() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
List<Integer> executions = new ArrayList<>();
// Plusieurs appels rapides

// Plusieurs appels rapides (30ms << 500ms debounce, ratio 16x)
for (int i = 0; i < 10; i++) {
final int value = i;
debouncer.debounce(() -> {
Expand All @@ -84,45 +84,45 @@ void testMultipleConsecutiveCallsOnlyLastExecutes() throws InterruptedException
latch.countDown();
}
});
Thread.sleep(20);
Thread.sleep(30);
}
// Attendre
assertTrue(latch.await(200, TimeUnit.MILLISECONDS));

// Attendre (10*30ms calls + 500ms debounce + buffer)
assertTrue(latch.await(1500, TimeUnit.MILLISECONDS));

// Seule la dernière action doit s'être exécutée
assertEquals(List.of(9), executions);
}

@Test
void testCancelPreventsExecution() throws InterruptedException {
boolean[] executed = {false};

debouncer.debounce(() -> {
executed[0] = true;
});

// Annuler avant le délai
debouncer.cancel();

// Attendre plus longtemps que le délai
Thread.sleep(150);
Thread.sleep(700);

assertFalse(executed[0]);
}

@Test
void testShutdownPreventsFurtherExecution() throws InterruptedException {
boolean[] executed = {false};

debouncer.debounce(() -> {
executed[0] = true;
});

debouncer.shutdown();
Thread.sleep(150);

Thread.sleep(700);

assertFalse(executed[0]);
}
}
Loading