mirror of
https://github.com/gavvvr/obsidian-timecodes-plugin.git
synced 2026-07-22 12:00:24 +00:00
Compare commits
47 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53d2692fe0 | ||
|
|
f212cbebd0 | ||
|
|
bc35cb11ee | ||
|
|
5e2ec97973 | ||
|
|
8a4f0ec4a9 | ||
|
|
1e40cd1979 | ||
|
|
a16e9f483a | ||
|
|
c5fcbee18a | ||
|
|
7c1f5894f8 | ||
|
|
8a2d7a05fb | ||
|
|
1b2350ee99 | ||
|
|
e451c18e33 | ||
|
|
8ce6c12ebf | ||
|
|
e276168617 | ||
|
|
64cbaa3f08 | ||
|
|
e99d127bc6 | ||
|
|
a97c023338 | ||
|
|
7f0d379928 | ||
|
|
a8aaaa0d37 | ||
|
|
1bf2109df0 | ||
|
|
0a68c244c1 | ||
|
|
46b76af4fd | ||
|
|
d026ed5f38 | ||
|
|
ebf9b7b49b | ||
|
|
320d93bb4f | ||
|
|
825ca9f060 | ||
|
|
a2fb2d7c4f | ||
|
|
3980070aae | ||
|
|
7ec2f52c57 | ||
|
|
6e2d657048 | ||
|
|
17a7228a47 | ||
|
|
c8646b6070 | ||
|
|
ef7f208370 | ||
|
|
0c5a41e14a | ||
|
|
45f5dfc1af | ||
|
|
da38f73330 | ||
|
|
8c674b5dcf | ||
|
|
56f5a6362c | ||
|
|
075a8849dd | ||
|
|
6eb9cf0a69 | ||
|
|
e27b30584a | ||
|
|
6369bb2d62 | ||
|
|
2b3ab72f49 | ||
|
|
99ed8adf07 | ||
|
|
7a2c1ba2e3 | ||
|
|
34e0d0009c | ||
|
|
938b95aab0 |
19 changed files with 360 additions and 74 deletions
68
.github/workflows/build-ci-runner-image.yml
vendored
Normal file
68
.github/workflows/build-ci-runner-image.yml
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
name: Docker image for CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- '.github/workflows/build-ci-runner-image.yml'
|
||||
- 'docker/ci-runner.Dockerfile'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
nodejs_version:
|
||||
description: 'Node.js version to use'
|
||||
type: string
|
||||
required: true
|
||||
obsidian_version:
|
||||
description: 'Obsidian version to test with'
|
||||
type: string
|
||||
required: true
|
||||
debian_version:
|
||||
description: 'Debian codename version to use'
|
||||
type: string
|
||||
required: true
|
||||
|
||||
env:
|
||||
NODEJS_VERSION: ${{ inputs.nodejs_version || '24' }}
|
||||
OBSIDIAN_VERSION: ${{ inputs.obsidian_version || '1.12.7' }}
|
||||
DEBIAN_VERSION: ${{ inputs.debian_version || 'trixie' }}
|
||||
|
||||
IMAGE_NAME: ghcr.io/${{ github.repository }}/ci-runner
|
||||
DOCKERFILE_PATH: docker/ci-runner.Dockerfile
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Prepare metadata for tags and labels
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=raw,value=node-${{ env.NODEJS_VERSION }}-obsidian-${{ env.OBSIDIAN_VERSION }}-${{ env.DEBIAN_VERSION }}-slim
|
||||
labels: |
|
||||
org.opencontainers.image.title: Obsidian plugins CI runner
|
||||
org.opencontainers.image.description=Docker image for building and testing obsidian plugins (with E2E tests support)
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: ${{ env.DOCKERFILE_PATH }}
|
||||
build-args: |
|
||||
NODEJS_VERSION=${{ env.NODEJS_VERSION }}
|
||||
OBSIDIAN_VERSION=${{ env.OBSIDIAN_VERSION }}
|
||||
DEBIAN_VERSION=${{ env.DEBIAN_VERSION }}
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
34
.github/workflows/ci.yml
vendored
34
.github/workflows/ci.yml
vendored
|
|
@ -7,32 +7,41 @@ on:
|
|||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
Build:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/${{ github.repository }}/ci-runner:node-24-obsidian-1.12.7-trixie-slim
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: '22'
|
||||
package-manager-cache: false
|
||||
- name: Environment details
|
||||
run: pnpm version
|
||||
run: pnpm dlx envinfo --system --binaries
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Build
|
||||
run: pnpm run build
|
||||
- name: Test
|
||||
run: pnpm run test:unit
|
||||
- name: Upload coverage data
|
||||
- name: E2E tests
|
||||
run: xvfb-run pnpm run test
|
||||
working-directory: e2e
|
||||
- name: Upload unit-test coverage data
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: lcov.info
|
||||
name: lcov-unit.info
|
||||
path: out/coverage/lcov.info
|
||||
- name: Upload E2E-test coverage data
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: lcov-e2e.info
|
||||
path: e2e/out/coverage/lcov.info
|
||||
|
||||
SonarScan:
|
||||
needs: Build
|
||||
|
|
@ -41,11 +50,16 @@ jobs:
|
|||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
- name: Download coverage data
|
||||
- name: Download unit-tests coverage data
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: lcov.info
|
||||
path: coverage/
|
||||
name: lcov-unit.info
|
||||
path: coverage/unit/
|
||||
- name: Download E2E-tests coverage data
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
name: lcov-e2e.info
|
||||
path: coverage/e2e/
|
||||
- name: SonarQube Scan
|
||||
uses: SonarSource/sonarqube-scan-action@v6
|
||||
env:
|
||||
|
|
|
|||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
|
|
@ -20,10 +20,10 @@ jobs:
|
|||
uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: '22'
|
||||
node-version: '24'
|
||||
package-manager-cache: false
|
||||
- name: Environment details
|
||||
run: pnpm version
|
||||
run: pnpm dlx envinfo --system --binaries
|
||||
- name: Build
|
||||
id: build
|
||||
run: |
|
||||
|
|
|
|||
49
docker/ci-runner.Dockerfile
Normal file
49
docker/ci-runner.Dockerfile
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
ARG NODEJS_VERSION=22
|
||||
ARG DEBIAN_VERSION=trixie
|
||||
ARG BASE_IMAGE=node:${NODEJS_VERSION}-${DEBIAN_VERSION}-slim
|
||||
ARG TARGET_IMAGE_PLATFORM=linux/amd64
|
||||
|
||||
ARG OBSIDIAN_VERSION=1.9.14
|
||||
ARG OBSIDIAN_APPIMAGE_FILE=Obsidian-${OBSIDIAN_VERSION}.AppImage
|
||||
|
||||
FROM busybox:latest AS obsidian-downloader
|
||||
ARG OBSIDIAN_VERSION
|
||||
ARG OBSIDIAN_APPIMAGE_FILE
|
||||
RUN wget https://github.com/obsidianmd/obsidian-releases/releases/download/v${OBSIDIAN_VERSION}/${OBSIDIAN_APPIMAGE_FILE}
|
||||
|
||||
FROM ${BASE_IMAGE} AS obsidian-extractor-amd64
|
||||
ARG OBSIDIAN_APPIMAGE_FILE
|
||||
COPY --from=obsidian-downloader /${OBSIDIAN_APPIMAGE_FILE} /${OBSIDIAN_APPIMAGE_FILE}
|
||||
RUN chmod +x ${OBSIDIAN_APPIMAGE_FILE}
|
||||
RUN ./${OBSIDIAN_APPIMAGE_FILE} --appimage-extract
|
||||
|
||||
# `--appimage-extract` won't work on Apple Silicon, hence using a special extraction stage
|
||||
FROM --platform=${TARGET_IMAGE_PLATFORM} ${BASE_IMAGE} AS obsidian-extractor-arm64
|
||||
ARG OBSIDIAN_APPIMAGE_FILE
|
||||
RUN apt-get update && \
|
||||
apt-get install -y squashfs-tools && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=obsidian-downloader /${OBSIDIAN_APPIMAGE_FILE} /${OBSIDIAN_APPIMAGE_FILE}
|
||||
RUN offset=$(grep -aob 'hsqs' ${OBSIDIAN_APPIMAGE_FILE} | tail -n1 | cut -d: -f1) && \
|
||||
dd if=${OBSIDIAN_APPIMAGE_FILE} of=image.squashfs bs=4M iflag=skip_bytes,count_bytes skip=$offset status=progress && \
|
||||
unsquashfs image.squashfs
|
||||
|
||||
FROM obsidian-extractor-${TARGETARCH} AS obsidian-extractor
|
||||
|
||||
FROM --platform=${TARGET_IMAGE_PLATFORM} ${BASE_IMAGE}
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends --no-install-suggests \
|
||||
libgtk-3-0 libnss3 libgbm1 libasound2 \
|
||||
xvfb xauth && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=obsidian-extractor /squashfs-root /obsidian-appimage-extracted
|
||||
|
||||
ENV OBSIDIAN_BINARY_PATH=/obsidian-appimage-extracted/obsidian
|
||||
ENV OBSIDIAN_NO_SANDBOX=true
|
||||
|
||||
RUN corepack enable pnpm
|
||||
|
||||
# for running locally as: docker run --rm -it -v ${PWD}:/plugin local-ci-runner
|
||||
WORKDIR /plugin
|
||||
CMD ["sh", "-c", "pnpm i && cd e2e && xvfb-run pnpm run test"]
|
||||
|
|
@ -1,2 +1,4 @@
|
|||
import * as path from 'node:path'
|
||||
|
||||
export const TIMECODES_PLUGIN_ID = 'timecodes'
|
||||
export const TEST_VAULT_DIR = '.e2e_test_vault'
|
||||
export const TEST_VAULT_DIR = path.resolve(process.cwd(), '.e2e_test_vault')
|
||||
|
|
|
|||
|
|
@ -3,19 +3,27 @@
|
|||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"pretest": "pnpm --dir .. run build",
|
||||
"pretest": "pnpm --dir .. run build:coverage",
|
||||
"test": "wdio run wdio.conf.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@wdio/cli": "9.11.0",
|
||||
"@wdio/globals": "9.11.0",
|
||||
"@wdio/local-runner": "9.11.0",
|
||||
"@wdio/mocha-framework": "9.11.0",
|
||||
"@wdio/spec-reporter": "9.11.0",
|
||||
"electron": "32.2.5",
|
||||
"expect-webdriverio": "5.1.0",
|
||||
"obsidian": "1.8.7",
|
||||
"wdio-electron-service": "8.0.1",
|
||||
"webdriverio": "9.11.0"
|
||||
"@types/istanbul-lib-coverage": "2.0.6",
|
||||
"@types/istanbul-lib-report": "3.0.3",
|
||||
"@types/istanbul-lib-source-maps": "4.0.4",
|
||||
"@types/istanbul-reports": "3.0.4",
|
||||
"@wdio/cli": "9.27.2",
|
||||
"@wdio/electron-service": "^10.0.0",
|
||||
"@wdio/globals": "9.27.2",
|
||||
"@wdio/local-runner": "9.27.2",
|
||||
"@wdio/mocha-framework": "9.27.2",
|
||||
"@wdio/spec-reporter": "9.27.2",
|
||||
"electron": "39.8.3",
|
||||
"expect-webdriverio": "5.6.7",
|
||||
"istanbul-lib-coverage": "3.2.2",
|
||||
"istanbul-lib-report": "3.0.1",
|
||||
"istanbul-lib-source-maps": "5.0.6",
|
||||
"istanbul-reports": "3.2.0",
|
||||
"obsidian": "1.12.3",
|
||||
"webdriverio": "9.27.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ describe('Markdown view post-processor', () => {
|
|||
const link = $('//div[contains(@class, "markdown-preview-section")]//a[text()="22:53"]')
|
||||
await expect(link).toBePresent()
|
||||
const href = await link.getAttribute('href')
|
||||
await expect(href).toBe('https://youtu.be/k_ItB5btREU?t=1373')
|
||||
expect(href).toBe('https://youtu.be/k_ItB5btREU?t=1373')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ describe('Markdown view post-processor', () => {
|
|||
const link = $('//div[contains(@class, "markdown-preview-section")]//a[text()="22:53"]')
|
||||
await expect(link).toBePresent()
|
||||
const href = await link.getAttribute('href')
|
||||
await expect(href).toBe('https://youtu.be/k_ItB5btREU?t=1373')
|
||||
expect(href).toBe('https://youtu.be/k_ItB5btREU?t=1373')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ describe('Markdown view post-processor', () => {
|
|||
const link = $('//div[contains(@class, "markdown-preview-section")]//a[text()="22:53"]')
|
||||
await expect(link).toBePresent()
|
||||
const href = await link.getAttribute('href')
|
||||
await expect(href).toBe('https://youtu.be/k_ItB5btREU?t=1373')
|
||||
expect(href).toBe('https://youtu.be/k_ItB5btREU?t=1373')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ describe('Markdown view post-processor', () => {
|
|||
const link = $('//div[contains(@class, "markdown-preview-section")]//a[text()="22:53"]')
|
||||
await expect(link).toBePresent()
|
||||
const href = await link.getAttribute('href')
|
||||
await expect(href).toBe('https://youtu.be/k_ItB5btREU?t=1373')
|
||||
expect(href).toBe('https://youtu.be/k_ItB5btREU?t=1373')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
|
|||
const link = $('//div[contains(@class, "markdown-preview-section")]//a[text()="22:53"]')
|
||||
await expect(link).toBePresent()
|
||||
const href = await link.getAttribute('href')
|
||||
await expect(href).toBe('https://youtu.be/k_ItB5btREU?t=1373')
|
||||
expect(href).toBe('https://youtu.be/k_ItB5btREU?t=1373')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -102,17 +102,17 @@ https://www.youtube.com/watch?v=k_ItB5btREU
|
|||
let link = $('//div[contains(@class, "markdown-preview-section")]//a[text()="14:27"]')
|
||||
await expect(link).toBePresent()
|
||||
let href = await link.getAttribute('href')
|
||||
await expect(href).toBe('https://youtu.be/Z5n9VK3sOnI?t=867')
|
||||
expect(href).toBe('https://youtu.be/Z5n9VK3sOnI?t=867')
|
||||
|
||||
link = $('//div[contains(@class, "markdown-preview-section")]//a[text()="4:07"]')
|
||||
await expect(link).toBePresent()
|
||||
href = await link.getAttribute('href')
|
||||
await expect(href).toBe('https://youtu.be/Lipf5piizZc?t=247')
|
||||
expect(href).toBe('https://youtu.be/Lipf5piizZc?t=247')
|
||||
|
||||
link = $('//div[contains(@class, "markdown-preview-section")]//a[text()="22:53"]')
|
||||
await expect(link).toBePresent()
|
||||
href = await link.getAttribute('href')
|
||||
await expect(href).toBe('https://youtu.be/k_ItB5btREU?t=1373')
|
||||
expect(href).toBe('https://youtu.be/k_ItB5btREU?t=1373')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -140,17 +140,17 @@ referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
|
|||
let link = $('//div[contains(@class, "markdown-preview-section")]//a[text()="14:27"]')
|
||||
await expect(link).toBePresent()
|
||||
let href = await link.getAttribute('href')
|
||||
await expect(href).toBe('https://youtu.be/Z5n9VK3sOnI?t=867')
|
||||
expect(href).toBe('https://youtu.be/Z5n9VK3sOnI?t=867')
|
||||
|
||||
link = $('//div[contains(@class, "markdown-preview-section")]//a[text()="4:07"]')
|
||||
await expect(link).toBePresent()
|
||||
href = await link.getAttribute('href')
|
||||
await expect(href).toBe('https://youtu.be/Lipf5piizZc?t=247')
|
||||
expect(href).toBe('https://youtu.be/Lipf5piizZc?t=247')
|
||||
|
||||
link = $('//div[contains(@class, "markdown-preview-section")]//a[text()="22:53"]')
|
||||
await expect(link).toBePresent()
|
||||
href = await link.getAttribute('href')
|
||||
await expect(href).toBe('https://youtu.be/k_ItB5btREU?t=1373')
|
||||
expect(href).toBe('https://youtu.be/k_ItB5btREU?t=1373')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -176,7 +176,7 @@ https://www.youtube.com/watch?v=k_ItB5btREU
|
|||
const textLinesFromAllNoteParagraphs = $$('.markdown-preview-section p:not(.mod-ui)')
|
||||
.map(p => p.getText())
|
||||
const textFromAllNoteParagraphs = (await textLinesFromAllNoteParagraphs).join('\n\n')
|
||||
await expect(textFromAllNoteParagraphs).toBe(noteContent)
|
||||
expect(textFromAllNoteParagraphs).toBe(noteContent)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
6
e2e/types/require-electron.d.ts
vendored
Normal file
6
e2e/types/require-electron.d.ts
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
namespace NodeJS {
|
||||
interface Require {
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-function-type
|
||||
(module: 'electron'): typeof import('electron')
|
||||
}
|
||||
}
|
||||
44
e2e/utils/coverage.ts
Normal file
44
e2e/utils/coverage.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import type { CoverageMapData } from 'istanbul-lib-coverage'
|
||||
|
||||
import * as fs from 'node:fs'
|
||||
import * as path from 'node:path'
|
||||
|
||||
import { createCoverageMap } from 'istanbul-lib-coverage'
|
||||
import { createContext } from 'istanbul-lib-report'
|
||||
import { createSourceMapStore } from 'istanbul-lib-source-maps'
|
||||
import * as reports from 'istanbul-reports'
|
||||
|
||||
export async function exportCoverageToLcov() {
|
||||
const coverageMap = createCoverageMap(await collectCoverageData())
|
||||
const remappedCoverageMap = await createSourceMapStore().transformCoverage(coverageMap)
|
||||
|
||||
const context = createContext({
|
||||
coverageMap: remappedCoverageMap,
|
||||
dir: 'out/coverage',
|
||||
sourceFinder: (filePath: string) => {
|
||||
return fs.readFileSync(restorePathRelativeToMainPluginProject(filePath), 'utf-8')
|
||||
},
|
||||
})
|
||||
reports.create('lcov').execute(context)
|
||||
}
|
||||
|
||||
const cwd = process.cwd()
|
||||
|
||||
function restorePathRelativeToMainPluginProject(filePath: string) {
|
||||
const relativePart = path.relative(cwd, filePath)
|
||||
return path.join(path.resolve(cwd, '..'), relativePart)
|
||||
}
|
||||
|
||||
async function collectCoverageData() {
|
||||
const coverageData = await browser.execute(() => {
|
||||
return window.__coverage__
|
||||
})
|
||||
if (!coverageData) throw new Error('No coverage data found!')
|
||||
return coverageData
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__coverage__?: CoverageMapData
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
/// <reference types="wdio-electron-service" />
|
||||
/// <reference types="@wdio/electron-service" />
|
||||
import ObsidianApp from './specs/pageobjects/obsidian-app.page'
|
||||
import { exportCoverageToLcov } from './utils/coverage'
|
||||
|
||||
const debug = process.env.DEBUG
|
||||
const ONE_DAY = 24 * 60 * 60 * 1000
|
||||
|
||||
const obsidianBinaryPath = process.env.OBSIDIAN_BINARY_PATH
|
||||
const obsidianNoSandbox = process.env.OBSIDIAN_NO_SANDBOX === 'true'
|
||||
|
||||
export const config: WebdriverIO.Config = {
|
||||
runner: 'local',
|
||||
specs: ['./specs/*.e2e.ts'],
|
||||
|
|
@ -12,11 +16,10 @@ export const config: WebdriverIO.Config = {
|
|||
capabilities: [
|
||||
{
|
||||
browserName: 'electron',
|
||||
browserVersion: '32.2.5',
|
||||
'wdio:electronServiceOptions': {
|
||||
// custom application args
|
||||
appBinaryPath: '/Applications/Obsidian.app/Contents/MacOS/Obsidian',
|
||||
appArgs: [],
|
||||
appBinaryPath: obsidianBinaryPath ?? '/Applications/Obsidian.app/Contents/MacOS/Obsidian',
|
||||
appArgs: [obsidianNoSandbox ? '--no-sandbox' : '--sandbox'],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
@ -38,4 +41,7 @@ export const config: WebdriverIO.Config = {
|
|||
await ObsidianApp.createAndOpenFreshVault()
|
||||
await ObsidianApp.activateTimecodesPlugin()
|
||||
},
|
||||
afterSuite: async () => {
|
||||
await exportCoverageToLcov()
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ export default tseslint.config(
|
|||
tseslint.configs.strictTypeChecked,
|
||||
tseslint.configs.stylisticTypeChecked,
|
||||
stylistic.configs.customize({
|
||||
flat: true,
|
||||
jsx: false,
|
||||
indent: 2,
|
||||
quotes: 'single',
|
||||
|
|
@ -51,17 +50,17 @@ export default tseslint.config(
|
|||
type: 'natural',
|
||||
order: 'asc',
|
||||
ignoreCase: false,
|
||||
newlinesBetween: 'always',
|
||||
newlinesBetween: 1,
|
||||
environment: 'node',
|
||||
groups: [
|
||||
'type',
|
||||
'builtin',
|
||||
'external',
|
||||
'internal-type',
|
||||
'internal',
|
||||
['parent-type', 'sibling-type', 'index-type'],
|
||||
['parent', 'sibling', 'index'],
|
||||
'object',
|
||||
'type-import',
|
||||
'value-builtin',
|
||||
'value-external',
|
||||
'type-internal',
|
||||
'value-internal',
|
||||
['type-parent', 'type-sibling', 'type-index'],
|
||||
['value-parent', 'value-sibling', 'value-index'],
|
||||
'ts-equals-import',
|
||||
'unknown',
|
||||
],
|
||||
}],
|
||||
|
|
|
|||
40
package.json
40
package.json
|
|
@ -5,6 +5,7 @@
|
|||
"scripts": {
|
||||
"dev": "node scripts/dev.js",
|
||||
"build": "tsc -noEmit -skipLibCheck && node scripts/esbuild.build.js production",
|
||||
"build:coverage": "node scripts/esbuild.build.js coverage",
|
||||
"test:eslint": "eslint --cache --cache-location out/.eslintcache",
|
||||
"test:unit": "vitest --coverage"
|
||||
},
|
||||
|
|
@ -15,22 +16,27 @@
|
|||
],
|
||||
"author": "Kirill Gavrilov",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@eslint/js": "9.21.0",
|
||||
"@stylistic/eslint-plugin": "4.2.0",
|
||||
"@types/node": "22.10.8",
|
||||
"@vitest/coverage-v8": "3.0.7",
|
||||
"enquirer": "2.4.1",
|
||||
"esbuild": "0.25.0",
|
||||
"eslint": "9.21.0",
|
||||
"eslint-plugin-perfectionist": "4.9.0",
|
||||
"eslint-plugin-wdio": "9.9.1",
|
||||
"globals": "16.0.0",
|
||||
"obsidian": "1.8.7",
|
||||
"obsidian-utils": "0.10.2",
|
||||
"typescript": "5.8.2",
|
||||
"typescript-eslint": "8.26.0",
|
||||
"vitest": "3.0.7"
|
||||
"engines": {
|
||||
"node": ">=24 <25"
|
||||
},
|
||||
"packageManager": "pnpm@10.5.2"
|
||||
"devDependencies": {
|
||||
"@eslint/js": "10.0.1",
|
||||
"@stylistic/eslint-plugin": "5.10.0",
|
||||
"@types/istanbul-lib-instrument": "1.7.8",
|
||||
"@types/node": "24.13.1",
|
||||
"@vitest/coverage-v8": "4.1.8",
|
||||
"enquirer": "2.4.1",
|
||||
"esbuild": "0.28.0",
|
||||
"eslint": "10.4.1",
|
||||
"eslint-plugin-perfectionist": "5.9.0",
|
||||
"eslint-plugin-wdio": "9.27.2",
|
||||
"globals": "17.6.0",
|
||||
"istanbul-lib-instrument": "6.0.3",
|
||||
"obsidian": "1.12.3",
|
||||
"obsidian-utils": "0.10.2",
|
||||
"typescript": "6.0.3",
|
||||
"typescript-eslint": "8.60.1",
|
||||
"vitest": "4.1.8"
|
||||
},
|
||||
"packageManager": "pnpm@11.5.2"
|
||||
}
|
||||
|
|
|
|||
9
pnpm-workspace.yaml
Normal file
9
pnpm-workspace.yaml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
packages:
|
||||
- 'e2e'
|
||||
allowBuilds:
|
||||
edgedriver: false
|
||||
electron: false
|
||||
esbuild: false
|
||||
geckodriver: false
|
||||
minimumReleaseAgeExclude:
|
||||
- '@types/node@24.13.1'
|
||||
|
|
@ -5,21 +5,30 @@ import process from 'node:process'
|
|||
import esbuild from 'esbuild'
|
||||
|
||||
import { sharedEsbuildConfig } from './esbuild.config.js'
|
||||
import { instrumentWithSourceMaps } from './utils/instrument.js'
|
||||
|
||||
const config = sharedEsbuildConfig
|
||||
|
||||
const prod = process.argv[2] === 'production'
|
||||
const coverage = process.argv[2] === 'coverage'
|
||||
|
||||
const context = await esbuild.context({
|
||||
...config,
|
||||
...prod
|
||||
? { minify: true, sourcemap: false }
|
||||
: {},
|
||||
...coverage
|
||||
? { minify: false, sourcemap: 'inline' }
|
||||
: {},
|
||||
})
|
||||
|
||||
if (prod) {
|
||||
await context.rebuild()
|
||||
process.exit(0)
|
||||
} else if (coverage) {
|
||||
await context.rebuild()
|
||||
instrumentWithSourceMaps('out/main.js', 'out/main.js')
|
||||
process.exit(0)
|
||||
} else {
|
||||
await context.watch()
|
||||
}
|
||||
|
|
|
|||
47
scripts/utils/instrument.js
Normal file
47
scripts/utils/instrument.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// @ts-check
|
||||
import * as fs from 'node:fs'
|
||||
|
||||
import { createInstrumenter } from 'istanbul-lib-instrument'
|
||||
|
||||
/**
|
||||
* Instrument a JavaScript file with Istanbul coverage instrumentation
|
||||
* while preserving and remapping original sourcemaps
|
||||
* @param {string} inputFile
|
||||
* @param {string} outputFile
|
||||
*/
|
||||
export function instrumentWithSourceMaps(inputFile, outputFile) {
|
||||
const code = fs.readFileSync(inputFile, 'utf8')
|
||||
|
||||
// Extract the original sourcemap from the bundled file
|
||||
const sourceMapMatch
|
||||
= code.match(/\/\/# sourceMappingURL=data:application\/json;base64,(.+)/)
|
||||
if (!sourceMapMatch) throw new Error(`No sourcemap found in ${inputFile}`)
|
||||
|
||||
const sourceMapJson = Buffer.from(sourceMapMatch[1].trim(), 'base64').toString('utf8')
|
||||
const originalSourceMap = JSON.parse(sourceMapJson)
|
||||
|
||||
const instrumenter = createInstrumenter({
|
||||
esModules: false, // esbuild outputs Obsidian plugins as CommonJS
|
||||
compact: false,
|
||||
preserveComments: true,
|
||||
produceSourceMap: true,
|
||||
autoWrap: true,
|
||||
})
|
||||
|
||||
const instrumentedCode = instrumenter.instrumentSync(
|
||||
code,
|
||||
inputFile,
|
||||
originalSourceMap, // <- This is critical for sourcemaps remapping!
|
||||
)
|
||||
const sourceMap = instrumenter.lastSourceMap()
|
||||
|
||||
let output = instrumentedCode
|
||||
if (!sourceMap) throw new Error('No source map found!')
|
||||
|
||||
const base64SourceMap = Buffer.from(JSON.stringify(sourceMap)).toString('base64')
|
||||
output += `\n//# sourceMappingURL=data:application/json;base64,${base64SourceMap}`
|
||||
|
||||
fs.writeFileSync(outputFile, output, 'utf8')
|
||||
|
||||
console.log(`✓ Instrumented: ${inputFile} -> ${outputFile}`)
|
||||
}
|
||||
|
|
@ -6,4 +6,4 @@ sonar.exclusions=**/*.test.ts
|
|||
sonar.tests=src
|
||||
sonar.test.inclusions=**/*.test.ts
|
||||
|
||||
sonar.javascript.lcov.reportPaths=./coverage/lcov.info
|
||||
sonar.javascript.lcov.reportPaths=./coverage/unit/lcov.info,./coverage/e2e/lcov.info
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export function turnRawTimecodesIntoClickableLinks(
|
|||
}
|
||||
latestNoteBeingProcessed = currentNoteSourcePath
|
||||
|
||||
let node: Node | null = null
|
||||
let node: Node | null
|
||||
const nodesWithTimecodes: Text[] = []
|
||||
const nodeWalker = document.createTreeWalker(root, NodeFilter.SHOW_ALL)
|
||||
while ((node = nodeWalker.nextNode()) != null) {
|
||||
|
|
@ -38,7 +38,7 @@ export function turnRawTimecodesIntoClickableLinks(
|
|||
textForFindingVideoLink = elementNode.src
|
||||
}
|
||||
if (elementNode instanceof HTMLMediaElement) {
|
||||
latestRequiredEnricher = localMedicaEnricherFor(elementNode)
|
||||
latestRequiredEnricher = localMediaEnricherFor(elementNode)
|
||||
}
|
||||
} else if (node.nodeType === Node.TEXT_NODE) {
|
||||
const textNode = node as Text
|
||||
|
|
@ -123,7 +123,7 @@ const youtubeEnricherFor = (videoId: string): TextTimecodeEnricher => ({
|
|||
},
|
||||
})
|
||||
|
||||
const localMedicaEnricherFor = (media: HTMLMediaElement): TextTimecodeEnricher => ({
|
||||
const localMediaEnricherFor = (media: HTMLMediaElement): TextTimecodeEnricher => ({
|
||||
composeLinkElementWithTimecode: (raw, timecode) => {
|
||||
const link = document.createElement('a')
|
||||
link.href = '#'
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"target": "ES6",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"rootDir": ".",
|
||||
"target": "es6",
|
||||
"noImplicitAny": true,
|
||||
"isolatedModules": true,
|
||||
"strict": true,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,24 @@
|
|||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
{
|
||||
name: 'virtual-obsidian',
|
||||
enforce: 'pre',
|
||||
resolveId(id: string) {
|
||||
if (id === 'obsidian') {
|
||||
return '\0obsidian'
|
||||
}
|
||||
},
|
||||
load(id: string) {
|
||||
if (id === '\0obsidian') {
|
||||
return `
|
||||
export class Plugin {}
|
||||
export default {}`
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
test: {
|
||||
isolate: false,
|
||||
pool: 'threads',
|
||||
|
|
|
|||
Loading…
Reference in a new issue