Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @TableProApp/maintainers
11 changes: 11 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Report a security problem in a listed integration
url: https://github.com/TableProApp/integrations/security/advisories/new
about: Malware, credential theft or a vulnerability. Reported privately.
- name: A bug in TablePro itself
url: https://github.com/TableProApp/TablePro/issues/new/choose
about: This repository lists integrations. TablePro bugs go to the app repository.
- name: A bug in an integration
url: https://github.com/TableProApp/integrations/tree/main/integrations
about: Open the entry and use the issue tracker in its links. The integration's author fixes it, not TablePro.
47 changes: 47 additions & 0 deletions .github/ISSUE_TEMPLATE/report-integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Report a listed integration
description: An entry is broken, misleading, or breaks the listing policy.
labels: ["report"]
body:
- type: markdown
attributes:
value: |
Malware, credential theft or a vulnerability: do not file it here. Use **Report a security problem** on the previous page, which is private.
A bug in the integration itself goes to its own issue tracker first.
- type: input
id: slug
attributes:
label: Entry
description: The last part of the entry's URL, for example localwp in tablepro.app/integrations/localwp.
validations:
required: true
- type: dropdown
id: reason
attributes:
label: Problem
options:
- It does not work with the current TablePro release
- A link is broken or goes somewhere else
- It reads TablePro's private files
- It uses the TablePro name or icon in a confusing way
- A disclosure on the page is wrong
- It pretends to be someone else
- Something else
validations:
required: true
- type: textarea
id: details
attributes:
label: Details
description: What you saw, with versions and links.
validations:
required: true
- type: dropdown
id: contacted
attributes:
label: Did you tell the author
options:
- Yes, with no reply after 7 days
- Yes, recently
- "No"
validations:
required: true
32 changes: 32 additions & 0 deletions .github/ISSUE_TEMPLATE/request-removal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Request removal or archive
description: For publishers who want their entry archived or removed.
labels: ["removal request"]
body:
- type: input
id: slug
attributes:
label: Entry
description: The last part of the entry's URL.
validations:
required: true
- type: dropdown
id: action
attributes:
label: What should happen
options:
- Archive it. The page stays with a notice
- Remove it. The page answers 410 Gone
validations:
required: true
- type: textarea
id: reason
attributes:
label: Reason
description: Optional.
- type: checkboxes
id: owner
attributes:
label: Ownership
options:
- label: I am the publisher shown on the entry, or I can act for them. A maintainer checks this against the GitHub account on the entry.
required: true
41 changes: 41 additions & 0 deletions .github/ISSUE_TEMPLATE/suggest-integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Suggest an integration
description: Tell us about an integration that should be listed. Its author can also open a pull request.
labels: ["suggestion"]
body:
- type: markdown
attributes:
value: |
Authors: a pull request is faster. See CONTRIBUTING.md.
Everyone else: we ask the author before listing anything.
- type: input
id: name
attributes:
label: Name
placeholder: DDEV Manager
validations:
required: true
- type: input
id: url
attributes:
label: Link
description: Store page, repository or homepage.
placeholder: https://
validations:
required: true
- type: textarea
id: what
attributes:
label: What it does with TablePro
description: One or two sentences.
validations:
required: true
- type: dropdown
id: relation
attributes:
label: Who are you
options:
- I made it
- I work for the company that makes it
- I use it
validations:
required: true
24 changes: 24 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
cooldown:
default-days: 7
commit-message:
prefix: ci
groups:
actions:
patterns: ["*"]
- package-ecosystem: pip
directory: /
schedule:
interval: weekly
cooldown:
default-days: 7
commit-message:
prefix: build
groups:
python:
patterns: ["*"]
23 changes: 23 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!--
Title: feat(<slug>): add <Name> for a new entry, fix(<slug>): <what changed> for an update.
One entry per pull request. Read POLICY.md first.
-->

## Entry

<!-- Slug, and one line on what the integration does. -->

## Checklist

- [ ] I read the [listing policy](https://github.com/TableProApp/integrations/blob/main/POLICY.md) and the entry follows it.
- [ ] `python3 scripts/validate.py` passes on my machine.
- [ ] I am the publisher, a member of the publishing organization, or a major contributor. If not, the owner gave consent in a comment on this pull request (link it below).
- [ ] The integration uses only documented TablePro interfaces. It never reads `connections.json`, `com.TablePro.plist`, `mcp-handshake.json`, TablePro's Keychain items or its SQLite files.
- [ ] The name does not include TablePro, and the icon is not the TablePro app icon. A small TablePro badge in a corner is fine.
- [ ] The disclosures are true: what it reads and writes, Safe Mode, network, account, payment.
- [ ] I own the icon and screenshots, or may use them, and I let TablePro show them in the directory and its published data.
- [ ] I wrote this pull request myself. If an AI tool helped, I say how below and I checked every claim.

## Consent or notes

<!-- Optional. A link to the owner's consent comment, or anything the reviewer should know. -->
35 changes: 35 additions & 0 deletions .github/workflows/stale.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Stale

on:
schedule:
- cron: "41 3 * * *"
workflow_dispatch:

permissions: {}

concurrency:
group: stale

jobs:
stale:
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
actions: write
issues: write
pull-requests: write
steps:
- uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
with:
days-before-issue-stale: -1
days-before-issue-close: -1
only-pr-labels: waiting on author
days-before-pr-stale: 14
days-before-pr-close: 7
stale-pr-label: stale
remove-pr-stale-when-updated: true
operations-per-run: 100
stale-pr-message: >
No reply for 14 days. This pull request closes in 7 days unless you push a change or comment.
close-pr-message: >
Closed after 21 days without a reply. Open a new pull request when you are ready.
89 changes: 89 additions & 0 deletions .github/workflows/triage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
name: Triage

# Labels only. It never checks out or runs pull request code; it reads changed files through the API as data.
on: # zizmor: ignore[dangerous-triggers]
pull_request_target:
types: [opened, reopened, synchronize, ready_for_review]

permissions: {}

concurrency:
group: triage-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
label:
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: read
pull-requests: write
issues: write
steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const pr = context.payload.pull_request;
const { owner, repo } = context.repo;
const maintainer = ['OWNER', 'MEMBER', 'COLLABORATOR'].includes(pr.author_association);
const files = await github.paginate(github.rest.pulls.listFiles, { owner, repo, pull_number: pr.number, per_page: 100 });
const labels = new Set();
const slugs = new Map();
for (const file of files) {
const entry = /^integrations\/([a-z0-9-]{1,40})\/(.+)$/.exec(file.filename);
if (entry) {
if (entry[2] === 'integration.json') {
slugs.set(entry[1], file.status);
labels.add(file.status === 'added' ? 'new integration' : file.status === 'removed' ? 'removal' : 'update');
} else {
labels.add('update');
}
} else if (file.filename.startsWith('curation/')) {
labels.add('curation');
} else {
labels.add('tooling');
}
}
async function readJson(repoOwner, repoName, ref, path) {
try {
const { data } = await github.rest.repos.getContent({ owner: repoOwner, repo: repoName, path, ref });
return JSON.parse(Buffer.from(data.content, 'base64').toString('utf8'));
} catch {
return null;
}
}
const tiers = ['official', 'partner', 'community'];
let newName = null;
for (const [slug, status] of slugs) {
const path = `integrations/${slug}/integration.json`;
const head = pr.head.repo && status !== 'removed'
? await readJson(pr.head.repo.owner.login, pr.head.repo.name, pr.head.sha, path) : null;
const base = status === 'added' ? null : await readJson(owner, repo, pr.base.sha, path);
if (head && tiers.includes(head.tier)) labels.add(`tier: ${head.tier}`);
if (head && base && head.tier !== base.tier) labels.add('tier change');
if (head && head.compliance) labels.add('grace period');
if (status === 'added' && head && typeof head.name === 'string') newName = head.name.slice(0, 40);
if (!maintainer && head) {
const author = pr.user.login.toLowerCase();
const publisher = String((head.publisher && head.publisher.github) || '').toLowerCase();
const sourceRepo = head.source && head.source.type === 'github' ? String(head.source.repo || '') : '';
const repoOwner = sourceRepo.split('/')[0].toLowerCase();
let owns = author === publisher || (repoOwner !== '' && author === repoOwner);
if (!owns && repoOwner) {
try {
await github.rest.orgs.checkPublicMembershipForUser({ org: repoOwner, username: pr.user.login });
owns = true;
} catch {}
}
if (!owns) labels.add('needs owner consent');
}
}
if (!maintainer && slugs.size > 1) labels.add('multiple entries');
if (labels.size) {
await github.rest.issues.addLabels({ owner, repo, issue_number: pr.number, labels: [...labels] });
}
const conventional = /^(feat|fix|docs|chore|ci|build|refactor|test|revert)(\([a-z0-9-]+\))?!?: \S/;
if (context.payload.action === 'opened' && slugs.size === 1 && newName && !conventional.test(pr.title)) {
const [slug] = slugs.keys();
await github.rest.pulls.update({ owner, repo, pull_number: pr.number, title: `feat(${slug}): add ${newName}` });
}
Loading