-
Notifications
You must be signed in to change notification settings - Fork 1
Phase 6: tooling (lint, format, build, CI) #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| name: Checks | ||
|
|
||
| on: | ||
| pull_request: | ||
| push: | ||
| branches: [main] | ||
|
|
||
| jobs: | ||
| lint-format: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '20' | ||
| cache: 'npm' | ||
| - run: npm ci || npm install | ||
| - run: npm run lint | ||
| - run: npm run format | ||
|
|
||
| build: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '20' | ||
| - run: npm install --no-save | ||
| - name: Build extension zip | ||
| run: npm run package | ||
| - name: Upload artifact | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: tweai-extension | ||
| path: tweai-v*.zip | ||
| retention-days: 14 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| name: Release | ||
|
|
||
| on: | ||
| push: | ||
| tags: | ||
| - 'v*' | ||
|
|
||
| permissions: | ||
| contents: write | ||
|
|
||
| jobs: | ||
| release: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '20' | ||
| - run: npm install --no-save | ||
| - name: Build extension zip | ||
| run: npm run package | ||
| - name: Capture zip path | ||
| id: zip | ||
| run: | | ||
| ZIP=$(ls tweai-v*.zip | head -1) | ||
| echo "path=$ZIP" >> "$GITHUB_OUTPUT" | ||
| echo "name=$(basename "$ZIP")" >> "$GITHUB_OUTPUT" | ||
| - name: Create GitHub release | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| TAG="${GITHUB_REF#refs/tags/}" | ||
| gh release create "$TAG" "${{ steps.zip.outputs.path }}" \ | ||
| --title "$TAG" \ | ||
| --generate-notes |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,9 @@ | ||
| node_modules/ | ||
| env.json | ||
|
|
||
| # Build artifacts | ||
| dist/ | ||
| *.zip | ||
|
|
||
| # Tooling | ||
| .eslintcache |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| node_modules | ||
| dist | ||
| *.zip | ||
| docs | ||
| tweai-mcp-server | ||
| _locales | ||
| LICENSE | ||
| .git |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "semi": true, | ||
| "singleQuote": true, | ||
| "trailingComma": "all", | ||
| "printWidth": 100, | ||
| "tabWidth": 2, | ||
| "arrowParens": "avoid", | ||
| "endOfLine": "lf" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| // Flat config for ESLint 9.x. WebExtensions globals + browser env. | ||
| // Намеренно мягкие правила: цель — поймать опечатки и явные баги, не переписать | ||
| // весь codebase. Style-issues идут через prettier. | ||
|
|
||
| export default [ | ||
| { | ||
| ignores: [ | ||
| 'node_modules/**', | ||
| 'dist/**', | ||
| '*.zip', | ||
| 'tweai-mcp-server/**', | ||
| 'docs/**', | ||
| '_locales/**', | ||
| ], | ||
| }, | ||
| { | ||
| files: ['**/*.js', '**/*.mjs'], | ||
| languageOptions: { | ||
| ecmaVersion: 2022, | ||
| sourceType: 'script', | ||
|
Comment on lines
+17
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
| globals: { | ||
| // WebExtensions | ||
| chrome: 'readonly', | ||
| browser: 'readonly', | ||
| // Browser | ||
| window: 'readonly', | ||
| document: 'readonly', | ||
| navigation: 'readonly', | ||
| location: 'readonly', | ||
| localStorage: 'readonly', | ||
| history: 'readonly', | ||
| console: 'readonly', | ||
| fetch: 'readonly', | ||
| URL: 'readonly', | ||
| URLSearchParams: 'readonly', | ||
| setTimeout: 'readonly', | ||
| clearTimeout: 'readonly', | ||
| setInterval: 'readonly', | ||
| clearInterval: 'readonly', | ||
| Promise: 'readonly', | ||
| Map: 'readonly', | ||
| Set: 'readonly', | ||
| Boolean: 'readonly', | ||
| Number: 'readonly', | ||
| String: 'readonly', | ||
| Object: 'readonly', | ||
| Array: 'readonly', | ||
| Math: 'readonly', | ||
| Date: 'readonly', | ||
| JSON: 'readonly', | ||
| RegExp: 'readonly', | ||
| Error: 'readonly', | ||
| AbortController: 'readonly', | ||
| AbortSignal: 'readonly', | ||
| MutationObserver: 'readonly', | ||
| IntersectionObserver: 'readonly', | ||
| InputEvent: 'readonly', | ||
| Event: 'readonly', | ||
| CustomEvent: 'readonly', | ||
| HTMLElement: 'readonly', | ||
|
Comment on lines
+58
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
After the config-file parsing issue is fixed, this browser globals list still makes Useful? React with 👍 / 👎. |
||
| Element: 'readonly', | ||
| Node: 'readonly', | ||
| getComputedStyle: 'readonly', | ||
| requestAnimationFrame: 'readonly', | ||
| addEventListener: 'readonly', | ||
| // own globals (shared between content scripts in same isolated world) | ||
| TTASelectors: 'writable', | ||
| TTALogger: 'writable', | ||
| }, | ||
| }, | ||
| rules: { | ||
| 'no-undef': 'error', | ||
| 'no-unused-vars': ['warn', { args: 'none', varsIgnorePattern: '^_' }], | ||
| 'no-constant-condition': ['error', { checkLoops: false }], | ||
| 'no-empty': ['error', { allowEmptyCatch: true }], | ||
| 'no-prototype-builtins': 'off', | ||
| 'no-cond-assign': ['error', 'except-parens'], | ||
| 'no-control-regex': 'off', | ||
| 'no-useless-escape': 'warn', | ||
| }, | ||
| }, | ||
| { | ||
| // Build/tools — Node environment | ||
| files: ['tools/**/*.mjs', '*.mjs'], | ||
| languageOptions: { | ||
| sourceType: 'module', | ||
| globals: { | ||
| process: 'readonly', | ||
| console: 'readonly', | ||
| URL: 'readonly', | ||
| }, | ||
| }, | ||
| }, | ||
| ]; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
| "name": "tweai", | ||
| "version": "1.8.1", | ||
| "private": true, | ||
| "description": "TweAI — AI Reply Assistant for X / Twitter. Chrome extension.", | ||
| "license": "Apache-2.0", | ||
| "type": "module", | ||
| "scripts": { | ||
| "lint": "eslint .", | ||
| "lint:fix": "eslint . --fix", | ||
| "format": "prettier --check .", | ||
| "format:fix": "prettier --write .", | ||
| "package": "node tools/build.mjs", | ||
| "check": "npm run lint && npm run format" | ||
| }, | ||
| "devDependencies": { | ||
| "eslint": "^9.0.0", | ||
| "prettier": "^3.0.0" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| #!/usr/bin/env node | ||
| // Сборка артефакта расширения для Chrome Web Store. | ||
| // | ||
| // Текущее состояние: vanilla content scripts без bundler — просто копируем | ||
| // рантайм-файлы в dist/, исключая node-репку MCP-сервера, docs, dev-файлы. | ||
| // Когда (если) перейдём на ESM-модули в options/ — этот же скрипт будет | ||
| // запускать esbuild перед копированием. | ||
| // | ||
| // Использование: | ||
| // node tools/build.mjs # → dist/ + tweai-v<version>.zip | ||
| // node tools/build.mjs --no-zip # только dist/ | ||
|
|
||
| import { readFile, rm, mkdir, cp, readdir, stat } from 'node:fs/promises'; | ||
| import { spawn } from 'node:child_process'; | ||
| import { join, dirname } from 'node:path'; | ||
| import { fileURLToPath } from 'node:url'; | ||
|
|
||
| const __dirname = dirname(fileURLToPath(import.meta.url)); | ||
| const ROOT = join(__dirname, '..'); | ||
| const DIST = join(ROOT, 'dist'); | ||
|
|
||
| const INCLUDE = [ | ||
| 'manifest.json', | ||
| 'background.js', | ||
| 'content_script.js', | ||
| 'selectors.js', | ||
| 'dev-logger.js', | ||
| 'ad-blocker.js', | ||
| 'profile-scraper.js', | ||
| 'options.html', | ||
| 'options.js', | ||
| 'options.css', | ||
| 'styles.css', | ||
| 'icons', | ||
| '_locales', | ||
| 'LICENSE', | ||
| ]; | ||
|
|
||
| async function readVersion() { | ||
| const m = JSON.parse(await readFile(join(ROOT, 'manifest.json'), 'utf8')); | ||
| return m.version; | ||
| } | ||
|
|
||
| async function copyTree(src, dst) { | ||
| await cp(src, dst, { recursive: true }); | ||
| } | ||
|
|
||
| async function zipDir(srcDir, outZip) { | ||
| // Используем системный `zip` — он есть на macOS/Linux и GitHub Actions | ||
| // runner. Без extra deps: добавлять JS-zip-библиотеку ради одной операции | ||
| // не стоит. | ||
| await new Promise((resolve, reject) => { | ||
| const proc = spawn('zip', ['-r', outZip, '.'], { cwd: srcDir, stdio: 'inherit' }); | ||
| proc.on('exit', code => (code === 0 ? resolve() : reject(new Error(`zip exit ${code}`)))); | ||
| proc.on('error', reject); | ||
| }); | ||
| } | ||
|
|
||
| async function main() { | ||
| const version = await readVersion(); | ||
| const wantZip = !process.argv.includes('--no-zip'); | ||
|
|
||
| await rm(DIST, { recursive: true, force: true }); | ||
| await mkdir(DIST, { recursive: true }); | ||
|
|
||
| for (const name of INCLUDE) { | ||
| const src = join(ROOT, name); | ||
| const dst = join(DIST, name); | ||
| try { | ||
| await stat(src); | ||
| } catch { | ||
| console.warn(`[build] skip missing: ${name}`); | ||
| continue; | ||
| } | ||
| await copyTree(src, dst); | ||
| } | ||
|
|
||
| console.log(`[build] dist/ ready — version ${version}`); | ||
| const entries = await readdir(DIST); | ||
| console.log(`[build] ${entries.length} top-level entries`); | ||
|
|
||
| if (wantZip) { | ||
| const zipPath = join(ROOT, `tweai-v${version}.zip`); | ||
| await rm(zipPath, { force: true }); | ||
| await zipDir(DIST, zipPath); | ||
| const s = await stat(zipPath); | ||
| console.log(`[build] wrote ${zipPath} (${(s.size / 1024).toFixed(1)} KB)`); | ||
| } | ||
| } | ||
|
|
||
| main().catch(e => { | ||
| console.error('[build] failed:', e); | ||
| process.exit(1); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I checked the new workflow by running
prettier --check ., and the current tree reports 15 unformatted files including.github/dependabot.yml,.github/workflows/codeql.yml,background.js,content_script.js, andoptions.js. Since this PR gates every PR/main push onnpm run format, the Checks workflow is red until those existing files are formatted or deliberately ignored.Useful? React with 👍 / 👎.