@webship.co/workspace-github 0.0.0-stage → 1.0.0-alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,32 @@
1
- # Temporary Holding Version
1
+ # @webship.co/workspace-github
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ GitHub for the [Webship Workspace](https://github.com/webship/workspace) dashboard: the **GITHUB**
4
+ menu on every project whose git remote is on GitHub (open the repository, the open issues and
5
+ pull requests, open one from the current branch, copy the clone URL), the repository chip on the
6
+ card, "Clone a repository" when a new folder is made, and the credential the dashboard uses for the
7
+ host. A provider of [`@webship.co/workspace-forge`](https://github.com/webship/workspace-forge).
8
+
9
+ The lists come from `gh` (`gh issue list`, `gh pr list`) with the token in the environment, or from the REST API with `curl` when gh is not there; a pull request from the current branch is `gh pr create --head <branch> --fill` in a terminal in the project. The token also serves the dashboard's other GitHub work (the AI-items sync, the tooling repository): `github-auth.js` asks this plugin first, through `api.addForge('github.com', …)`.
10
+
11
+ ## Use it
12
+
13
+ ```bash
14
+ cd ~/workspace
15
+ bash core/scripts/plugins.sh --require github:webship/workspace-github
16
+ bash core/scripts/plugins.sh --activate github
17
+ bash core/scripts/plugins.sh --install github # carries the git.github block of settings.yml into plugin.github.settings.yml
18
+ bash core/scripts/plugins.sh --configure github
19
+ ```
20
+
21
+ The same four steps are buttons on the dashboard's **Settings → Plugins** page.
22
+
23
+ ## What the package holds
24
+
25
+ | Path | What it is |
26
+ | --- | --- |
27
+ | `src/index.ts` | `register(api)`: `registerForge(api, githubProvider(api))` |
28
+ | `src/lib/provider.ts` | The GitHub provider: the host, the credential, how a remote reads, the pages, the issue and request lists, the clone URL |
29
+ | `config/plugin.github.settings.yml` | The settings template |
30
+ | `scripts/host-install.sh` | The host side: the CLI and the credential check |
31
+
32
+ The `workspacePlugin` block in `package.json` is the manifest the installer reads.
@@ -0,0 +1,13 @@
1
+ # GitHub — core/config/plugin.github.settings.yml
2
+ #
3
+ # How the dashboard reaches GitHub. gh's own login is used when it works (on the host); inside the dashboard container the token lives in the host keyring, which the container cannot read, so the token below is what the dashboard uses there. Never printed, never written to a job log.
4
+ # github.com, or a GitHub Enterprise host.
5
+ host: github.com
6
+ # Authenticate with the gh CLI's own login when it works, and only fall back to the token below.
7
+ use_gh_cli: true
8
+ # The account name GitHub knows you by.
9
+ username: CHANGE_ME
10
+ # The address commits pushed to GitHub carry. Blank falls back to contributor.email.
11
+ email: CHANGE_ME
12
+ # A fine-grained personal access token (Settings → Developer settings). Read-only is enough for the lists and the AI-items sync; pull requests from the dashboard need write on the repositories.
13
+ token: CHANGE_ME
@@ -0,0 +1,16 @@
1
+ # Labels for core/config/plugin.github.settings.yml, merged into the Settings page.
2
+ host:
3
+ label: Host
4
+ info: 'github.com, or a GitHub Enterprise host.'
5
+ use_gh_cli:
6
+ label: Prefer the gh login
7
+ info: 'Authenticate with the gh CLI''s own login when it works, and only fall back to the token below.'
8
+ username:
9
+ label: Username
10
+ info: 'The account name GitHub knows you by.'
11
+ email:
12
+ label: Commit email
13
+ info: 'The address commits pushed to GitHub carry. Blank falls back to contributor.email.'
14
+ token:
15
+ label: Token
16
+ info: 'A fine-grained personal access token (Settings → Developer settings). Read-only is enough for the lists and the AI-items sync; pull requests from the dashboard need write on the repositories.'
@@ -0,0 +1,7 @@
1
+ # The gh CLI and its config, shared FROM THE HOST: the bin DIRECTORY is mounted, not the file,
2
+ # so an update on the host is live in the container at once.
3
+ services:
4
+ web:
5
+ volumes:
6
+ - "${HOME}/.local/bin:/host-local-bin:ro"
7
+ - "${HOME}/.config/gh:/home/${USER}/.config/gh:rw"
@@ -0,0 +1,13 @@
1
+ /**
2
+ * GitHub for Webship Workspace.
3
+ *
4
+ * The GITHUB menu on every project whose git remote is on GitHub (the repository, open issues,
5
+ * open pull requests, open one from the current branch, the clone URL), the repository chip,
6
+ * "Clone a repository" when a new folder is made, and the credential the dashboard uses for the
7
+ * host. All of it is the forge family's surface (@webship.co/workspace-forge); this package is the
8
+ * GitHub provider plus its settings template and host install.
9
+ */
10
+ import type { PluginApi } from '@webship.co/workspace-plugin-api';
11
+ import { githubProvider } from './lib/provider';
12
+ export { githubProvider };
13
+ export declare function register(api: PluginApi): void;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ /**
3
+ * GitHub for Webship Workspace.
4
+ *
5
+ * The GITHUB menu on every project whose git remote is on GitHub (the repository, open issues,
6
+ * open pull requests, open one from the current branch, the clone URL), the repository chip,
7
+ * "Clone a repository" when a new folder is made, and the credential the dashboard uses for the
8
+ * host. All of it is the forge family's surface (@webship.co/workspace-forge); this package is the
9
+ * GitHub provider plus its settings template and host install.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.githubProvider = void 0;
13
+ exports.register = register;
14
+ const workspace_forge_1 = require("@webship.co/workspace-forge");
15
+ const provider_1 = require("./lib/provider");
16
+ Object.defineProperty(exports, "githubProvider", { enumerable: true, get: function () { return provider_1.githubProvider; } });
17
+ function register(api) {
18
+ (0, workspace_forge_1.registerForge)(api, (0, provider_1.githubProvider)(api));
19
+ }
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AASH,4BAEC;AARD,iEAA4D;AAE5D,6CAAgD;AAEvC,+FAFA,yBAAc,OAEA;AAEvB,SAAgB,QAAQ,CAAC,GAAc;IACrC,IAAA,+BAAa,EAAC,GAAG,EAAE,IAAA,yBAAc,EAAC,GAAG,CAAC,CAAC,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * GitHub, as a forge provider.
3
+ *
4
+ * gh does the lists and opens pull requests when it is there, with the token from
5
+ * plugin.github.settings.yml in its environment; without gh the REST API answers through curl.
6
+ */
7
+ import type { PluginApi } from '@webship.co/workspace-plugin-api';
8
+ import type { ForgeProvider } from '@webship.co/workspace-forge';
9
+ export declare function githubProvider(api: PluginApi): ForgeProvider;
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ /**
3
+ * GitHub, as a forge provider.
4
+ *
5
+ * gh does the lists and opens pull requests when it is there, with the token from
6
+ * plugin.github.settings.yml in its environment; without gh the REST API answers through curl.
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.githubProvider = githubProvider;
13
+ const fs_1 = __importDefault(require("fs"));
14
+ const path_1 = __importDefault(require("path"));
15
+ const workspace_forge_1 = require("@webship.co/workspace-forge");
16
+ const configured = (v) => { const s = String(v == null ? '' : v).trim(); return s && s !== 'CHANGE_ME' ? s : null; };
17
+ function hasCli(name) {
18
+ for (const d of ['/host-local-bin', ...String(process.env.PATH || '').split(':')]) {
19
+ try {
20
+ fs_1.default.accessSync(path_1.default.join(d, name), fs_1.default.constants.X_OK);
21
+ return true;
22
+ }
23
+ catch (_) { /* next */ }
24
+ }
25
+ return false;
26
+ }
27
+ function githubProvider(api) {
28
+ const settings = () => api.settings.read() || {};
29
+ const host = () => String(configured(settings().host) || 'github.com').toLowerCase();
30
+ const apiBase = () => (host() === 'github.com' ? 'https://api.github.com' : `https://${host()}/api/v3`);
31
+ const env = () => {
32
+ const t = configured(settings().token);
33
+ if (!t)
34
+ return null;
35
+ const e = { ...process.env, GH_TOKEN: t };
36
+ if (host() !== 'github.com')
37
+ e.GH_HOST = host();
38
+ return e;
39
+ };
40
+ async function rest(lib, e, url) {
41
+ const headers = ['-H', 'Accept: application/vnd.github+json'];
42
+ if (e.GH_TOKEN)
43
+ headers.push('-H', `Authorization: Bearer ${e.GH_TOKEN}`);
44
+ const out = await lib.run('curl', ['-sS', '-m', '30', ...headers, url], process.env.HOME, { timeoutMs: 40 * 1000, env: process.env });
45
+ if (!out.ok)
46
+ throw new Error(lib.cliError(String(out.stderr || out.stdout)));
47
+ const data = JSON.parse(String(out.stdout));
48
+ if (data && data.message && !Array.isArray(data))
49
+ throw new Error(String(data.message));
50
+ return data;
51
+ }
52
+ async function list(kind, repo, lib, e) {
53
+ if (hasCli('gh')) {
54
+ const out = await lib.run('gh', [kind, 'list', '-R', `${host()}/${repo.path}`, '--state', 'open', '--limit', '20', '--json', 'number,title,url,author,updatedAt'], process.env.HOME, { timeoutMs: 40 * 1000, env: e });
55
+ if (out.ok) {
56
+ return JSON.parse(String(out.stdout) || '[]').map((i) => ({ number: i.number, title: i.title, url: i.url, author: i.author && i.author.login, updated: i.updatedAt }));
57
+ }
58
+ // gh is there but could not answer (no credential in this environment): the REST API may still, for a public repository.
59
+ }
60
+ const url = `${apiBase()}/repos/${repo.path}/${kind === 'pr' ? 'pulls' : 'issues'}?state=open&per_page=20`;
61
+ const data = await rest(lib, e, url);
62
+ return data.filter((i) => kind === 'pr' || !i.pull_request).map((i) => ({ number: i.number, title: i.title, url: i.html_url, author: i.user && i.user.login, updated: i.updated_at }));
63
+ }
64
+ return {
65
+ id: 'github',
66
+ label: 'GITHUB',
67
+ title: 'GitHub',
68
+ host,
69
+ cli: 'gh',
70
+ pullsLabel: 'Pull requests',
71
+ env,
72
+ auth: () => {
73
+ const s = settings();
74
+ return { username: configured(s.username) || undefined, email: configured(s.email) || undefined, how: configured(s.token) ? 'token' : (String(s.use_gh_cli) !== 'false' ? 'gh login' : 'none') };
75
+ },
76
+ repoOf: (url, remote) => { const p = (0, workspace_forge_1.repoPathAt)(host(), url); return p ? { path: p, web: `https://${host()}/${p}`, remote } : null; },
77
+ cloneUrl: (spec) => (/^(https?:\/\/|git@)/.test(spec) ? spec : `https://${host()}/${spec.replace(/\.git$/, '')}.git`),
78
+ urls: (repo) => ({ issues: `${repo.web}/issues`, pulls: `${repo.web}/pulls`, newIssue: `${repo.web}/issues/new`, newPull: (branch) => `${repo.web}/compare/${encodeURIComponent(branch)}?expand=1` }),
79
+ listIssues: (repo, lib, e) => list('issue', repo, lib, e),
80
+ listPulls: (repo, lib, e) => list('pr', repo, lib, e),
81
+ newPullCommand: (repo, branch) => `git push -u origin ${branch} && gh pr create -R ${host()}/${repo.path} --head ${branch} --fill`,
82
+ // git borrows gh's login for a private clone, the way the AI-items sync does; the token is in the environment when the dashboard runs it.
83
+ cloneEnv: () => `GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=credential.https://${host()}.helper GIT_CONFIG_VALUE_0='!gh auth git-credential'`,
84
+ clone: { pattern: /^[\w.-]+\/[\w.-]+$|^(https?:\/\/|git@)[\w.-]+[:/][\w./-]+$/, placeholder: 'webship/workspace', help: 'A GitHub repository is owner/name, or its clone URL.' },
85
+ tokenHint: 'token in core/config/plugin.github.settings.yml',
86
+ };
87
+ }
88
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../../src/lib/provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ Feature: The GitHub plugin on a project row
2
+ Once the plugin is activated and installed, every project whose git remote is on GitHub carries
3
+ the GITHUB menu, and the Plugins page lists the plugin with its four steps.
4
+
5
+ Scenario: A project cloned from GitHub offers the GITHUB menu
6
+ Given I am on "/dev"
7
+ Then the element ".act-menu-github" should be displayed
8
+ And I should see "GITHUB"
9
+
10
+ Scenario: The plugin is listed on the Plugins page
11
+ Given I am on "/settings/plugins"
12
+ Then I should see "GitHub"
13
+ And I should see "@webship.co/workspace-github"
@@ -0,0 +1,2 @@
1
+ # Directories the dashboard container bind-mounts for this plugin; host-mounts.sh creates them as you first.
2
+ ~/.config/gh
package/package.json CHANGED
@@ -1,6 +1,60 @@
1
1
  {
2
2
  "name": "@webship.co/workspace-github",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0-alpha1",
4
+ "description": "GitHub for Webship Workspace: the GITHUB menu on every project whose remote is on github.com (repository, issues, pull requests, open one from the branch), the repository chip, clone when a new folder is made, and the credential gh-based work uses from the dashboard. A provider of @webship.co/workspace-forge.",
5
+ "main": "dist/src/index.js",
6
+ "types": "dist/src/index.d.ts",
7
+ "license": "ISC",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/webship/workspace-github.git"
11
+ },
12
+ "files": [
13
+ "dist/src",
14
+ "config",
15
+ "features",
16
+ "scripts",
17
+ "README.md",
18
+ "ddev",
19
+ "host-mounts.txt"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.json",
23
+ "prepare": "npm run build",
24
+ "test": "npm run build && node --test dist/tests/*.test.js",
25
+ "lint": "tsc -p tsconfig.json --noEmit"
26
+ },
27
+ "workspacePlugin": {
28
+ "name": "github",
29
+ "label": "GitHub",
30
+ "settingsFile": "plugin.github.settings.yml",
31
+ "settingsTemplate": "config/plugin.github.settings.yml",
32
+ "labels": "config/settings.labels.yml",
33
+ "legacySettingsKey": "git.github",
34
+ "scripts": {
35
+ "hostInstall": "scripts/host-install.sh"
36
+ },
37
+ "features": [
38
+ "features/github.feature"
39
+ ],
40
+ "ddev": {
41
+ "compose": [
42
+ "ddev/docker-compose.github-cli.yaml"
43
+ ],
44
+ "hostMounts": [
45
+ "~/.config/gh"
46
+ ]
47
+ }
48
+ },
49
+ "dependencies": {
50
+ "@webship.co/workspace-forge": "^1.0.0-alpha1"
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "^24.0.0",
54
+ "@webship.co/workspace-plugin-api": "^1.0.0-alpha1",
55
+ "typescript": "^5.9.0"
56
+ },
57
+ "publishConfig": {
58
+ "access": "public"
59
+ }
60
+ }
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env bash
2
+ ## The host side of the GitHub plugin: the CLI on this machine and the dashboard's credential.
3
+ ## Run by core/scripts/plugins.sh --install github, and by core/scripts/install.sh for every installed
4
+ ## plugin that ships one. Idempotent: whatever is already there is reported, not redone.
5
+ set -u
6
+ WORKSPACE_ROOT="${WORKSPACE_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../../.." && pwd)}";
7
+
8
+ BIN='';
9
+ for candidate in "$(command -v gh 2>/dev/null)" "${HOME}/.local/bin/gh" ; do
10
+ [ -n "${candidate}" ] && [ -x "${candidate}" ] && { BIN="${candidate}"; break; }
11
+ done
12
+ if [ -n "${BIN}" ] ; then
13
+ printf ' ✓ %-16s %s\n' "gh" "${BIN}" ;
14
+ else
15
+ printf ' ✗ %-16s %s\n' "gh" "missing — opening pull requests from the dashboard needs it (cli.github.com)" ;
16
+ fi
17
+
18
+ STATE="$(grep -E '^token:' "${WORKSPACE_ROOT}/core/config/plugin.github.settings.yml" 2>/dev/null | head -1 | sed -E 's/^[a-z_]+:[[:space:]]*//')";
19
+ case "${STATE}" in
20
+ ''|CHANGE_ME|"''"|'""') printf ' ✗ %-16s %s\n' "token" "not set in plugin.github.settings.yml — the dashboard reads only public repositories on GitHub" ;;
21
+ *) printf ' ✓ %-16s %s\n' "token" "set (plugin.github.settings.yml)" ;;
22
+ esac
23
+