diff --git a/.env.submit.example b/.env.submit.example new file mode 100644 index 00000000..4064d875 --- /dev/null +++ b/.env.submit.example @@ -0,0 +1,16 @@ +# Copy this file to .env.submit and fill in the values as you wish to publish +CHROME_EXTENSION_ID= +CHROME_CLIENT_ID= +CHROME_CLIENT_SECRET= +CHROME_REFRESH_TOKEN= +CHROME_PUBLISH_TARGET= +CHROME_SKIP_SUBMIT_REVIEW= +FIREFOX_EXTENSION_ID= +FIREFOX_JWT_ISSUER= +FIREFOX_JWT_SECRET= +FIREFOX_CHANNEL= +EDGE_PRODUCT_ID= +EDGE_CLIENT_ID= +EDGE_CLIENT_SECRET= +EDGE_ACCESS_TOKEN_URL= +EDGE_SKIP_SUBMIT_REVIEW= # true or false \ No newline at end of file diff --git a/.github/workflows/mvp.yml b/.github/workflows/mvp.yml new file mode 100644 index 00000000..09d4865d --- /dev/null +++ b/.github/workflows/mvp.yml @@ -0,0 +1,73 @@ +name: MVP - make, version & publish + +on: + push: + branches: + - main + workflow_dispatch: # This line adds manual triggering from the GitHub UI + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +jobs: + make_version_publish: + name: Make, Version & Publish + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + + - name: Setup Node 20.x + uses: actions/setup-node@v4 + with: + node-version: 20.x + + - name: Install bun & Deps + run: | + npm install bun -g + bun install + + - name: 'Build - all browsers' + id: buildProject + run: MODE=chrome vite build && MODE=firefox vite build + + - name: '[ V E R S I O N ] : Create or Update Release Pull Request - Version Changes' + id: changesets + uses: changesets/action@v1 + with: + version: bun run version + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: 'Get current version info from package.json' + if: steps.changesets.outputs.hasChangesets == 'false' + id: package + run: | + echo "::set-output name=PACKAGE_NAME::$(jq -r .name package.json)" + echo "::set-output name=PACKAGE_VERSION::$(jq -r .version package.json)" + working-directory: ${{ github.workspace }} + + - name: 'Check if a git release already exists for current version' + if: steps.changesets.outputs.hasChangesets == 'false' + id: checkRelease + run: | + TAG_NAME=${{ steps.package.outputs.PACKAGE_NAME }}@${{ steps.package.outputs.PACKAGE_VERSION }} + if gh release view $TAG_NAME &>/dev/null; then + echo "Release $TAG_NAME already exists." + echo "RELEASE_EXISTS=true" >> $GITHUB_ENV + else + echo "RELEASE_EXISTS=false" >> $GITHUB_ENV + fi + + - name: 'Create Release Archive(s) - zip 🫰 it 🫰 up 🫰 !' + id: zip + if: steps.changesets.outputs.hasChangesets == 'false' + run: bun run zip + + - name: 'Create a git release w/ notes & release archive(s)' + id: gitRelease + if: steps.changesets.outputs.hasChangesets == 'false' && env.RELEASE_EXISTS != 'true' + run: bun run release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PACKAGE_NAME: ${{ steps.package.outputs.PACKAGE_NAME }} + PACKAGE_VERSION: ${{ steps.package.outputs.PACKAGE_VERSION }} diff --git a/.gitignore b/.gitignore index 068ce288..8bf0af1d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,24 +7,15 @@ yarn.lock .parcel-cache .env +.env.submit # Build extension.zip build/ dist/ +betterseqtaplus-safari/ .million/ .vscode/ -**/.DS_Store -# Sentry Config File -.env.sentry-build-plugin - -# Sentry Config File -.env.sentry-build-plugin - -# Sentry Config File -.env.sentry-build-plugin - -# Sentry Config File -.sentryclirc +**/.DS_Store \ No newline at end of file diff --git a/lib/base64loader.ts b/lib/base64loader.ts new file mode 100644 index 00000000..b62a7eef --- /dev/null +++ b/lib/base64loader.ts @@ -0,0 +1,16 @@ +import fs from "fs"; +import mime from "mime-types"; + +export const base64Loader = { + name: "base64-loader", + transform(_: any, id: string) { + const [filePath, query] = id.split("?"); + if (query !== "base64") return null; + + const data = fs.readFileSync(filePath, { encoding: 'base64' }); + const mimeType = mime.lookup(filePath); + const dataURL = `data:${mimeType};base64,${data}`; + + return `export default '${dataURL}';`; + }, +}; \ No newline at end of file diff --git a/lib/createManifest.ts b/lib/createManifest.ts new file mode 100644 index 00000000..11392a49 --- /dev/null +++ b/lib/createManifest.ts @@ -0,0 +1,33 @@ +import type { Browser, BuildTarget, Manifest } from './types' +import type { AnyCase } from './utils' +/** + * + * + * @export + * @param {Manifest} manifest + * @param {AnyCase} browser + * @return {*} {@link BuildTarget} + */ +export function createManifest( + manifest: Manifest, + browser: AnyCase, +): BuildTarget { + return { + manifest, + browser, + } +} + +/** + * create a base Manifest to inherit from + * type Manifest = chrome.runtime.ManifestV3 + * + * use as shared base to extend inBrowser manifests + * + * @export + * @param {Manifest} manifest + * @return {*} {@link Manifest} + */ +export function createManifestBase(manifest: Manifest): Manifest { + return manifest +} \ No newline at end of file diff --git a/lib/publish.js b/lib/publish.js new file mode 100644 index 00000000..8da79994 --- /dev/null +++ b/lib/publish.js @@ -0,0 +1,90 @@ +const glob = require('glob'); +const semver = require('semver'); +const { execSync } = require('child_process'); +const path = require('path'); + +function getLatestVersion(files) { + console.log('Files passed to getLatestVersion:', files); + const versions = files.map(file => { + const match = file.match(/@(\d+\.\d+\.\d+)-/); + console.log('Matching file:', file, 'Version found:', match ? match[1] : 'None'); + return match ? match[1] : null; + }).filter(Boolean); + + console.log('Extracted versions:', versions); + const latestVersion = semver.maxSatisfying(versions, '*'); + console.log('Latest version:', latestVersion); + return latestVersion; +} + +function getLatestFiles(browser) { + const pattern = `dist/betterseqtaplus@*-*${browser}.zip`; + console.log('Glob pattern:', pattern); + const files = glob.sync(pattern); + console.log('Files found for browser', browser, ':', files); + const latestVersion = getLatestVersion(files); + + const latestFile = files.find(file => file.includes(latestVersion)); + console.log('Latest file for browser', browser, ':', latestFile); + return latestFile; +} + +function zipSources() { + const zipFileName = `dist/betterseqtaplus@latest-sources.zip`; + + const excludePatterns = [ + 'node_modules', + 'dist', + '.env*', + '.git', + '.github', + '.vscode', + 'LICENSE', + 'package.json' + ].map(pattern => `-x!${pattern}`).join(' '); + + const zipCommand = `7z a ${zipFileName} . ${excludePatterns}`; + + console.log('Zipping project sources with command:', zipCommand); + execSync(zipCommand, { stdio: 'inherit' }); + + return zipFileName; +} + +function runPublishCommand(browsers) { + const chromeZip = browsers.includes('chrome') ? getLatestFiles('chrome') : null; + const firefoxZip = browsers.includes('firefox') ? getLatestFiles('firefox') : null; + const firefoxSourcesZip = browsers.includes('firefox') ? zipSources() : null; + + console.log('Chrome zip:', chromeZip); + console.log('Firefox zip:', firefoxZip); + console.log('Firefox sources zip:', firefoxSourcesZip); + + if (browsers.length === 0) { + console.log('No browsers specified. Exiting.'); + process.exit(0); + } + + if ((browsers.includes('chrome') && !chromeZip) || (browsers.includes('firefox') && (!firefoxZip || !firefoxSourcesZip))) { + console.error('Could not find required zip files for specified browsers.'); + process.exit(1); + } + + let command = 'publish-extension'; + if (chromeZip) { + command += ` --chrome-zip ${chromeZip}`; + } + if (firefoxZip && firefoxSourcesZip) { + command += ` --firefox-zip ${firefoxZip} --firefox-sources-zip ${firefoxSourcesZip}`; + } + + console.log('Running command:', command); + execSync(command, { stdio: 'inherit' }); +} + +// Parse command-line arguments +const args = process.argv.slice(2); +const browserIndex = args.indexOf('--b'); +const browsers = browserIndex !== -1 ? args.slice(browserIndex + 1) : []; + +runPublishCommand(browsers); \ No newline at end of file diff --git a/lib/types.ts b/lib/types.ts new file mode 100644 index 00000000..ed8e3752 --- /dev/null +++ b/lib/types.ts @@ -0,0 +1,104 @@ +import type { ManifestV3Export } from '@crxjs/vite-plugin' +import { type AnyCase, createEnum } from './utils' + +export const FrameworkEnum = { + React: 'React', + Vanilla: 'Vanilla', + Preact: 'Preact', + Lit: 'Lit', + Svelte: 'Svelte', + Vue: 'Vue', +} as const + +export const BrowserEnum = { + Chrome: 'Chrome', + Brave: 'Brave', + Opera: 'Opera', + Edge: 'Edge', + Firefox: 'Firefox', + Safari: 'Safari', +} as const + +const LanguageEnum = { + TypeScript: 'TypeScript', + JavaScript: 'JavaScript', +} as const + +export const StyleEnum = { + Tailwind: 'Tailwind', +} as const + +export const PackageManagerEnum = { + Bun: 'Bun', + PnPm: 'PnPm', + Npm: 'Npm', + Yarn: 'Yarn', +} as const + +// see: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/firefox-webext-browser/index.d.ts +export type BrowserSpecificSettings = { + browser_specific_settings?: { + gecko?: { + id: string + strict_min_version?: string + strict_max_version?: string + } + } +} + +export type Manifest = ManifestV3Export +export type ManifestIcons = chrome.runtime.ManifestIcons +export type ManifestBackground = chrome.runtime.ManifestV3['background'] +export type ManifestContentScripts = + chrome.runtime.ManifestV3['content_scripts'] +export type ManifestWebAccessibleResources = + chrome.runtime.ManifestV3['web_accessible_resources'] +export type ManifestCommands = chrome.runtime.ManifestV3['commands'] +export type ManifestAction = chrome.runtime.ManifestV3['action'] +export type ManifestPermissions = chrome.runtime.ManifestV3['permissions'] +export type ManifestOptionsUI = chrome.runtime.ManifestV3['options_ui'] +export type ManifestURLOverrides = + chrome.runtime.ManifestV3['chrome_url_overrides'] + +export type BrowserName = Capitalize | Lowercase +export type BrowserEnumType = { + [browser in BrowserName]: BrowserName +} + +export type BuildMode = AnyCase +export type BuildTarget = { + manifest: Manifest + browser: AnyCase +} +export type BuildConfig = { + command?: 'build' | 'serve' + mode?: AnyCase | string | undefined +} + +export interface Repository { + type: string + url?: string + bugs?: Bugs +} + +export interface Bugs { + url?: string + email?: string +} + +export type Browser = (typeof BrowserEnum)[keyof typeof BrowserEnum] +export const Browser: AnyCase = createEnum(BrowserEnum) + +export type PackageManager = + (typeof PackageManagerEnum)[keyof typeof PackageManagerEnum] +export const PackageManager: AnyCase = + createEnum(PackageManagerEnum) + +export type Framework = (typeof FrameworkEnum)[keyof typeof FrameworkEnum] +export const Framework: AnyCase = createEnum(FrameworkEnum) + +export type Style = (typeof StyleEnum)[keyof typeof StyleEnum] +export const Style: AnyCase