@webship.co/workspace-hosting 0.0.0-stage → 1.0.0-beta1

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,19 @@
1
- # Temporary Holding Version
1
+ # @webship.co/workspace-hosting
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
+ The hosting family's shared base for [Webship Workspace](https://github.com/webship/workspace)
4
+ plugins. A hosting plugin (`@webship.co/workspace-upsun`, `-cpanel`, `-digitalocean`, `-aws`, `-azure`,
5
+ `-acquia`, `-godaddy`) is one call plus its provider:
6
+
7
+ ```ts
8
+ import { registerHosting } from '@webship.co/workspace-hosting';
9
+ export function register(api: PluginApi) { registerHosting(api, provider); }
10
+ ```
11
+
12
+ `registerHosting` gives the provider its row menu (linked / unlinked, with the link step), the
13
+ live-site chip, the environments modal and the list inside the menu, the get-database-and-files
14
+ flow (environment, database / files, switching the code, the safety export, confirmation; run as a
15
+ screen session), the branch-and-data command block, every tool verb as a screen session on the
16
+ project, the `/<id>-site/<ws>/<project>` jump, and the New-folder section. The `HostingProvider`
17
+ says what differs between hosts: how a project is detected as linked, the CLI and its token, how
18
+ environments and their URLs are listed, which tool verbs the per-workspace command carries, the
19
+ pull flags, the console URL.
@@ -0,0 +1,24 @@
1
+ /* The hosting family: the per-provider menu, the live-site chip and the New-folder option.
2
+ Appended to the dashboard stylesheet after the components, so it may only ADD rules.
3
+ Every hosting plugin gets these through registerHosting(); a provider's own file adds to them. */
4
+
5
+ /* The project id and the environment list are lowercase and case-sensitive: shown uppercase they
6
+ cannot be read off the screen and used, so they opt out of the header styling. */
7
+ .hosting-mismatch { text-transform: none; font-family: var(--font-code); font-size: 0.72rem; }
8
+ .hosting-menu-id { text-transform: none; font-family: var(--font-code); }
9
+ .hosting-menu-note { text-transform: none; font-weight: 400; opacity: .75; white-space: normal; }
10
+ /* The link command carries a project name, so it is long: it wraps rather than widening the menu. */
11
+ .hosting-menu-note code { white-space: normal; overflow-wrap: anywhere; }
12
+
13
+ /* An unlinked project still shows the menu (the command is there), quieter than a linked one, so a
14
+ row of thirty unlinked projects does not read as thirty cloud deployments. */
15
+ .hosting-unlinked { opacity: .75; }
16
+ .hosting-unlinked:hover { opacity: 1; }
17
+
18
+ /* The live-site chip on the card. */
19
+ .pcard-meta .proj-hosting-link { max-width: 11rem; }
20
+ .proj-hosting-link { white-space: nowrap; }
21
+
22
+ /* New folder: the project id field shows only when "Link to an existing project" is chosen. */
23
+ .folder-new-form .folder-hosting-id { display: none; }
24
+ .folder-new-form .folder-hosting:has(input[value="link"]:checked) .folder-hosting-id { display: block; }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Where a provider's CLI is, inside the dashboard container.
3
+ *
4
+ * The container has no package manager run at request time: a CLI gets there by being mounted
5
+ * from the host (the plugin's compose file maps `~/.local/bin` to `/host-local-bin`, and the CLI's
6
+ * own config directory beside it) or by being in the image. The first executable wins.
7
+ */
8
+ export declare function findCli(name: string, extra?: string[]): string | null;
9
+ /** A settings value that is set: not empty, not the CHANGE_ME placeholder. */
10
+ export declare function configured(v: unknown): string | null;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ /**
3
+ * Where a provider's CLI is, inside the dashboard container.
4
+ *
5
+ * The container has no package manager run at request time: a CLI gets there by being mounted
6
+ * from the host (the plugin's compose file maps `~/.local/bin` to `/host-local-bin`, and the CLI's
7
+ * own config directory beside it) or by being in the image. The first executable wins.
8
+ */
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.findCli = findCli;
14
+ exports.configured = configured;
15
+ const fs_1 = __importDefault(require("fs"));
16
+ const path_1 = __importDefault(require("path"));
17
+ function findCli(name, extra = []) {
18
+ const home = process.env.HOME || '';
19
+ const candidates = [
20
+ ...extra,
21
+ `/host-local-bin/${name}`,
22
+ path_1.default.join(home, '.local', 'bin', name),
23
+ ...String(process.env.PATH || '').split(':').filter(Boolean).map((d) => path_1.default.join(d, name)),
24
+ ];
25
+ for (const c of candidates) {
26
+ try {
27
+ fs_1.default.accessSync(c, fs_1.default.constants.X_OK);
28
+ if (fs_1.default.statSync(c).isFile())
29
+ return c;
30
+ }
31
+ catch (_) { /* next */ }
32
+ }
33
+ return null;
34
+ }
35
+ /** A settings value that is set: not empty, not the CHANGE_ME placeholder. */
36
+ function configured(v) {
37
+ const s = String(v == null ? '' : v).trim();
38
+ return s && s !== 'CHANGE_ME' ? s : null;
39
+ }
40
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;;;AAKH,0BAYC;AAGD,gCAGC;AArBD,4CAAoB;AACpB,gDAAwB;AAExB,SAAgB,OAAO,CAAC,IAAY,EAAE,QAAkB,EAAE;IACxD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;IACpC,MAAM,UAAU,GAAG;QACjB,GAAG,KAAK;QACR,mBAAmB,IAAI,EAAE;QACzB,cAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC;QACtC,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAI,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;KAC5F,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,CAAC;YAAC,YAAE,CAAC,UAAU,CAAC,CAAC,EAAE,YAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAAC,IAAI,YAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE;gBAAE,OAAO,CAAC,CAAC;QAAC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC;IAC9G,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,8EAA8E;AAC9E,SAAgB,UAAU,CAAC,CAAU;IACnC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5C,OAAO,CAAC,IAAI,CAAC,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * registerHosting(api, provider): the whole surface of a hosting plugin, from a provider's facts.
3
+ *
4
+ * The row menu (linked / unlinked), the live-site chip, the environments modal, the get-database-
5
+ * and-files flow, the branch-and-data command block, the tool verbs as screen sessions, the
6
+ * `/<id>-site/<ws>/<project>` jump, and the New-folder section. Each hosting plugin is this call
7
+ * plus its provider; what differs between hosts is in the provider, what is the same is here.
8
+ */
9
+ import type { PluginApi } from '@webship.co/workspace-plugin-api';
10
+ import type { HostingProvider } from './provider';
11
+ export * from './provider';
12
+ export * from './cli';
13
+ export declare function registerHosting(api: PluginApi, p: HostingProvider): void;
@@ -0,0 +1,400 @@
1
+ "use strict";
2
+ /**
3
+ * registerHosting(api, provider): the whole surface of a hosting plugin, from a provider's facts.
4
+ *
5
+ * The row menu (linked / unlinked), the live-site chip, the environments modal, the get-database-
6
+ * and-files flow, the branch-and-data command block, the tool verbs as screen sessions, the
7
+ * `/<id>-site/<ws>/<project>` jump, and the New-folder section. Each hosting plugin is this call
8
+ * plus its provider; what differs between hosts is in the provider, what is the same is here.
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
22
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
23
+ };
24
+ var __importDefault = (this && this.__importDefault) || function (mod) {
25
+ return (mod && mod.__esModule) ? mod : { "default": mod };
26
+ };
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.registerHosting = registerHosting;
29
+ const fs_1 = __importDefault(require("fs"));
30
+ const path_1 = __importDefault(require("path"));
31
+ __exportStar(require("./provider"), exports);
32
+ __exportStar(require("./cli"), exports);
33
+ const registeredCss = new WeakSet();
34
+ function registerHosting(api, p) {
35
+ const { esc, workspaceDir, listProjects, isValidWorkspace, screenAction, iconHtml, NAME_RE, ROOT, run, startInScreen, screenStartedHtml, cliError, commandBlock } = api.lib;
36
+ const post = (name) => `/actions/p/${api.name}/${name}`;
37
+ const fragment = (rest) => `/fragments/p/${api.name}/${rest}`;
38
+ const typeAction = p.typeAction || p.id;
39
+ const can = (allowed, dir) => allowed.has(typeAction) && fs_1.default.existsSync(path_1.default.join(dir, p.script));
40
+ const shq = (v) => `'${String(v).replace(/'/g, `'\\''`)}'`;
41
+ // The family's stylesheet, once however many providers register.
42
+ if (typeof api.addCss === 'function' && !registeredCss.has(api.addCss)) {
43
+ registeredCss.add(api.addCss);
44
+ api.addCss(path_1.default.join(__dirname, '..', '..', 'css', 'hosting.css'));
45
+ }
46
+ // A page of thirty rows must not stat the same files thirty times: the link is memoised briefly.
47
+ let cache = new Map();
48
+ let cacheAt = 0;
49
+ const linkOf = (dir, project) => {
50
+ const now = Date.now();
51
+ if (now - cacheAt > 5000) {
52
+ cache = new Map();
53
+ cacheAt = now;
54
+ }
55
+ const k = `${dir}/${project}`;
56
+ if (!cache.has(k)) {
57
+ try {
58
+ cache.set(k, p.detect(path_1.default.join(dir, project)));
59
+ }
60
+ catch (_) {
61
+ cache.set(k, null);
62
+ }
63
+ }
64
+ return cache.get(k) || null;
65
+ };
66
+ const errorText = (out) => {
67
+ const msg = cliError(String(out.stderr || out.stdout || ''));
68
+ return p.errorHint ? p.errorHint(msg) : msg;
69
+ };
70
+ // The environments and their live URLs, cached: a row cannot pay for CLI calls per render.
71
+ const envCache = new Map();
72
+ async function environments(dir, project) {
73
+ const u = linkOf(dir, project);
74
+ if (!u || !u.id)
75
+ return null;
76
+ const hit = envCache.get(u.id);
77
+ if (hit)
78
+ return hit;
79
+ if (!p.environments)
80
+ return { id: u.id, rows: [], branch: u.branch, env: u.env, why: 'no list' };
81
+ const env = p.jobEnv();
82
+ if (!env)
83
+ return { id: u.id, rows: [], branch: u.branch, env: u.env, why: 'no token' };
84
+ let info;
85
+ try {
86
+ info = await p.environments(u, api.lib, env);
87
+ }
88
+ catch (_) {
89
+ info = { id: u.id, rows: [], why: 'list failed' };
90
+ }
91
+ const answer = { ...info, branch: u.branch, env: u.env };
92
+ envCache.set(u.id, answer);
93
+ // Five minutes: long enough to render a page many times, short enough that a new environment shows.
94
+ setTimeout(() => envCache.delete(u.id), 5 * 60 * 1000).unref?.();
95
+ return answer;
96
+ }
97
+ // ---- The menu on a project row ---------------------------------------------------------------
98
+ api.addRowMenu(p.id, { label: p.label, row: 0 }, ({ key, project, dir, allowed, vals }) => {
99
+ if (!can(allowed, dir))
100
+ return null;
101
+ const u = linkOf(dir, project);
102
+ const caption = !u ? 'not linked' : 'linked';
103
+ const status = p.tools.status;
104
+ if (!u) {
105
+ return `<span class="act-with-status"><div class="uk-inline act-menu act-menu-${esc(p.id)} act-menu-hosting">
106
+ <button class="uk-button uk-button-default uk-button-small hosting-unlinked" type="button" title="Not linked to ${esc(p.title)}"><span uk-icon="icon: cloud-upload; ratio: .7"></span> ${esc(p.label)} <span uk-icon="icon: chevron-down; ratio: .6"></span></button>
107
+ <div uk-dropdown="mode: click; pos: bottom-right"><ul class="uk-nav uk-dropdown-nav">
108
+ <li class="uk-nav-header">Not linked to ${esc(p.title)}</li>
109
+ ${status ? `<li><a href hx-post="${post('tool-status')}" ${vals()}><span uk-icon="icon: info; ratio: .7"></span> ${esc(status.label)}</a></li>` : ''}
110
+ <li class="uk-nav-divider"></li>
111
+ <li class="uk-nav-header hosting-menu-note" data-group="how to link">Link it from ${esc(key)}:<br><code>${esc(p.linkHint(project))}</code></li>
112
+ </ul></div>
113
+ </div><span class="act-status uk-text-meta">${esc(caption)}</span></span>`;
114
+ }
115
+ const idLine = `${esc(u.id || '(no id)')}${u.branch ? ` · <span title="the branch the local code is on">${esc(u.branch)}</span>` : ''}`
116
+ + `${u.mismatch ? ` <span class="uk-label uk-label-danger hosting-mismatch" title="${esc(u.mismatchNote || 'the link and the git remote disagree')}">mismatch</span>` : ''}`;
117
+ const toolItems = Object.entries(p.tools).filter(([k]) => k !== 'status').map(([k, t]) => `<li><a href hx-post="${post(`tool-${k}`)}" ${vals()}><span uk-icon="icon: ${esc(t.icon)}; ratio: .7"></span> ${esc(t.label)}</a></li>`).join('');
118
+ const console = p.consoleUrl ? p.consoleUrl(u) : null;
119
+ return `<span class="act-with-status"><div class="uk-inline act-menu act-menu-${esc(p.id)} act-menu-hosting">
120
+ <button class="uk-button uk-button-default uk-button-small" type="button" title="${esc(p.label)}"><span uk-icon="icon: cloud-upload; ratio: .7"></span> ${esc(p.label)} <span uk-icon="icon: chevron-down; ratio: .6"></span></button>
121
+ <div uk-dropdown="mode: click; pos: bottom-right"><ul class="uk-nav uk-dropdown-nav">
122
+ <li class="uk-nav-header hosting-menu-id" data-group="link">${idLine}</li>
123
+ ${p.environments ? `<li><a href hx-post="${post('envs')}" hx-vals='{"workspace":"${esc(key)}","projectName":"${esc(project)}"}' hx-target="#editor-modal-body" hx-swap="innerHTML"><span uk-icon="icon: world; ratio: .7"></span> Environments and live sites</a></li>
124
+ <li class="uk-nav-header" data-group="live sites">Live sites</li>
125
+ <li data-entry="live sites list"><ul class="uk-nav-sub" hx-get="${fragment(`envs/${encodeURIComponent(key)}/${encodeURIComponent(project)}`)}" hx-trigger="revealed" hx-swap="innerHTML"><li class="uk-nav-header">Loading…</li></ul></li>` : ''}
126
+ ${status ? `<li><a href hx-post="${post('tool-status')}" ${vals()}><span uk-icon="icon: info; ratio: .7"></span> ${esc(status.label)}</a></li>` : ''}
127
+ ${toolItems}
128
+ ${p.pull && (p.pull.db || p.pull.files) ? `<li class="uk-nav-divider"></li>
129
+ <li><a href hx-post="${post('pull-form')}" hx-vals='{"workspace":"${esc(key)}","projectName":"${esc(project)}"}' hx-target="#editor-modal-body" hx-swap="innerHTML"><span uk-icon="icon: cloud-download; ratio: .7"></span> Get ${p.pull.db && p.pull.files ? 'database &amp; files' : p.pull.db ? 'database' : 'files'}…</a></li>
130
+ <li><a href hx-post="${post('environments')}" ${vals()}><span uk-icon="icon: git-branch; ratio: .7"></span> Branch and data…${u.env ? ` <span class="uk-text-meta">(${esc(u.env)})</span>` : ''}</a></li>` : ''}
131
+ ${console ? `<li class="uk-nav-divider"></li><li><a href="${esc(console)}" target="_blank"><span uk-icon="icon: link-external; ratio: .7"></span> Open in the ${esc(p.title)} console</a></li>` : ''}
132
+ ${p.menuNote ? `<li class="uk-nav-header hosting-menu-note">${esc(p.menuNote)}</li>` : ''}
133
+ </ul></div>
134
+ </div><span class="act-status uk-text-meta">${esc(caption)}</span></span>`;
135
+ });
136
+ // ---- The live-site chip on the card ----------------------------------------------------------
137
+ api.addProjectChip(({ key, project, dir }) => {
138
+ const u = linkOf(dir, project);
139
+ if (!u || !p.environments)
140
+ return null;
141
+ return `<a class="proj-alias proj-plugin-link proj-hosting-link proj-${esc(p.id)}-link" href="/${encodeURIComponent(p.id)}-site/${encodeURIComponent(key)}/${encodeURIComponent(project)}" target="_blank" rel="noopener"
142
+ title="The live ${esc(p.title)} site for the environment this code is on">${iconHtml('tabler:cloud-upload', 0.7)}<span class="pcard-chip-text">${esc(u.branch || u.env || u.id || p.title)}</span></a>`;
143
+ });
144
+ // ---- Tool verbs: the per-workspace command, run as a screen session ---------------------------
145
+ const guard = ({ form, send, workspace }) => {
146
+ const dir = workspaceDir(workspace);
147
+ if (!fs_1.default.existsSync(path_1.default.join(dir, p.script))) {
148
+ send(`<div class="msg error">This workspace has no ${esc(p.title)} tool.</div>`, 400);
149
+ return null;
150
+ }
151
+ const project = String(form.projectName || '');
152
+ if (!NAME_RE.test(project) || !listProjects(dir).includes(project)) {
153
+ send('<div class="msg error">Unknown project.</div>', 400);
154
+ return null;
155
+ }
156
+ return { dir, project };
157
+ };
158
+ for (const [name, t] of Object.entries(p.tools)) {
159
+ api.addAction(`tool-${name}`, (ctx) => {
160
+ const g = guard(ctx);
161
+ if (!g)
162
+ return;
163
+ return screenAction(ctx.send, { workspace: ctx.workspace, project: g.project, projectDir: g.dir,
164
+ ownerDir: path_1.default.join(g.dir, g.project), verb: t.verb,
165
+ cmdLine: `bash ${p.script} ${g.project}${t.args.length ? ` ${t.args.join(' ')}` : ''}`,
166
+ label: `${p.id} ${t.label.toLowerCase()}` });
167
+ });
168
+ }
169
+ // ---- Environments: the modal, the list inside the menu, the site jump -------------------------
170
+ api.addAction('envs', async (ctx) => {
171
+ const g = guard(ctx);
172
+ if (!g)
173
+ return;
174
+ const u = linkOf(g.dir, g.project);
175
+ if (!u || !u.id)
176
+ return ctx.send(`<div class="msg error">This project is not linked to a ${esc(p.title)} project.</div>`, 409);
177
+ if (!p.environments)
178
+ return ctx.send(`<div class="msg error">${esc(p.title)} lists no environments from here.</div>`, 409);
179
+ if (!p.jobEnv())
180
+ return ctx.send(`<div class="msg error">No ${esc(p.title)} API token configured (${esc(p.tokenHint)}).</div>`, 409);
181
+ envCache.delete(u.id);
182
+ const info = await environments(g.dir, g.project);
183
+ const rows = (info && info.rows) || [];
184
+ const list = rows.map((r) => `
185
+ <div class="uk-margin-small-bottom">
186
+ ${/^active$/i.test(r.status) ? '<span class="uk-label uk-label-success">active</span>' : `<span class="uk-label">${esc(r.status.toLowerCase())}</span>`}
187
+ <strong>${esc(r.title)}</strong>${r.title !== r.envId ? ` <span class="uk-text-meta">(${esc(r.envId)})</span>` : ''}
188
+ ${r.envId === u.branch ? '<span class="uk-text-meta">· the local code is on this branch</span>' : ''}
189
+ ${r.url ? `· <a href="${esc(r.url)}" target="_blank">${esc(r.url)}</a>` : ''}
190
+ </div>`).join('');
191
+ return ctx.send(`
192
+ <div class="editor-card">
193
+ <h3 class="uk-margin-small-bottom">Environments — ${esc(g.project)}</h3>
194
+ <p class="uk-text-meta">${esc(p.title)} project <code>${esc(u.id)}</code>. Each active environment with its live site.</p>
195
+ ${list || `<div class="msg error">No environments${info && info.why ? ` (${esc(info.why)})` : ''}.</div>`}
196
+ </div>`);
197
+ });
198
+ api.addFragment(async ({ send, rest }) => {
199
+ const m = rest.match(/^envs\/([a-z0-9_-]+)\/([a-zA-Z0-9_-]+)$/);
200
+ if (!m || !isValidWorkspace(m[1]))
201
+ return send('', 404);
202
+ const info = await environments(workspaceDir(m[1]), m[2]);
203
+ const rows = (info && info.rows) || [];
204
+ const items = rows.map((r) => {
205
+ const here = r.envId === (info.branch || info.env);
206
+ return r.url
207
+ ? `<li><a href="${esc(r.url)}" target="_blank" title="${esc(r.url)}"><span uk-icon="icon: link-external; ratio: .7"></span> ${esc(r.title)}${here ? ' <span class="uk-text-meta">(this code)</span>' : ''}</a></li>`
208
+ : `<li><span class="uk-text-meta"><span uk-icon="icon: ban; ratio: .7"></span> ${esc(r.title)} — ${esc((r.status || '').toLowerCase())}</span></li>`;
209
+ }).join('');
210
+ send(items || `<li class="uk-nav-header">${info && info.why === 'no token' ? `No ${esc(p.title)} API token (${esc(p.tokenHint)})` : 'No environments'}</li>`);
211
+ });
212
+ api.addPage(new RegExp(`^\\/${p.id}-site\\/([a-z0-9_-]+)\\/([a-zA-Z0-9_-]+)$`), async ({ res, match }) => {
213
+ if (!isValidWorkspace(match[1])) {
214
+ res.writeHead(404);
215
+ return res.end();
216
+ }
217
+ const info = await environments(workspaceDir(match[1]), match[2]);
218
+ const want = info && (info.branch || info.env || 'main');
219
+ const row = info && (info.rows.find((r) => r.envId === want && r.url) || info.rows.find((r) => r.url));
220
+ if (row) {
221
+ res.writeHead(302, { Location: row.url });
222
+ return res.end();
223
+ }
224
+ res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' });
225
+ res.end(`<p>No live ${esc(p.title)} URL for ${esc(match[2])}${info && info.why ? ` (${esc(info.why)})` : ''}.</p>`);
226
+ });
227
+ // ---- Branch and data: the commands, shown rather than run --------------------------------------
228
+ api.addAction('environments', (ctx) => {
229
+ const g = guard(ctx);
230
+ if (!g)
231
+ return;
232
+ const u = linkOf(g.dir, g.project);
233
+ if (!u || !u.id)
234
+ return ctx.send(`<div class="msg error">This project is not linked to a ${esc(p.title)} project.</div>`, 409);
235
+ const cd = `cd ${ROOT}/${ctx.workspace}`;
236
+ const lines = [`${cd} && bash ${p.script} ${g.project} --environments`, `${cd} && bash ${p.script} ${g.project} --select <environment>`];
237
+ if (p.pull && p.pull.db)
238
+ lines.push(`${cd} && bash ${p.script} ${g.project} --pull-db`);
239
+ if (p.pull && p.pull.files)
240
+ lines.push(`${cd} && bash ${p.script} ${g.project} --pull-files`);
241
+ return ctx.send(commandBlock(`Branch and data for ${g.project} on ${p.title} ${u.id}`, lines, `${u.env ? `Currently working against <strong>${esc(u.env)}</strong>. ` : 'No environment selected yet — a pull refuses to guess which branch it takes data from. '}`
242
+ + 'Run these <strong>on the host</strong>: these are cloud credentials for client projects. A database pull REPLACES the local database '
243
+ + '(the current one is exported to <code>backups/</code> first) and a files pull adds and overwrites the public files mount. '
244
+ + 'Both bring client data onto this machine — sanitize before sharing.'));
245
+ });
246
+ // ---- Get database and files -------------------------------------------------------------------
247
+ if (p.pull && (p.pull.db || p.pull.files)) {
248
+ const pull = p.pull;
249
+ api.addAction('pull-form', async (ctx) => {
250
+ const g = guard(ctx);
251
+ if (!g)
252
+ return;
253
+ const u = linkOf(g.dir, g.project);
254
+ if (!u || !u.id)
255
+ return ctx.send(`<div class="msg error">This project is not linked to a ${esc(p.title)} project.</div>`, 409);
256
+ const env = p.jobEnv();
257
+ if (!env)
258
+ return ctx.send(`<div class="msg error">No ${esc(p.title)} API token configured — ${esc(p.tokenHint)}.</div>`, 409);
259
+ envCache.delete(u.id);
260
+ const info = await environments(g.dir, g.project);
261
+ const all = (info && info.rows) || [];
262
+ const envs = all.filter((r) => /^active$/i.test(r.status)).map((r) => r.envId);
263
+ const inactive = all.length - envs.length;
264
+ if (!envs.length)
265
+ return ctx.send(`<div class="msg error">No ACTIVE environments on this project${all.length ? ` (${all.length} inactive)` : ''}${info && info.why ? ` — ${esc(info.why)}` : ''}.</div>`, 409);
266
+ const isGit = fs_1.default.existsSync(path_1.default.join(g.dir, g.project, '.git'));
267
+ return ctx.send(`
268
+ <div class="editor-card">
269
+ <h3 class="uk-margin-small-bottom">Get ${pull.db && pull.files ? 'database &amp; files' : pull.db ? 'database' : 'files'} — ${esc(g.project)}</h3>
270
+ <p class="uk-text-meta">From ${esc(p.title)} project <code>${esc(u.id)}</code> into the local DDEV site.
271
+ ${pull.db ? 'The local database is REPLACED (the current one is exported to <code>backups/</code> first); ' : ''}${pull.files ? 'files are added and overwritten, never deleted. ' : ''}Client data lands on this machine.</p>
272
+ <form hx-post="${post('pull')}" hx-target="#editor-modal-body" hx-swap="innerHTML">
273
+ <input type="hidden" name="workspace" value="${esc(ctx.workspace)}">
274
+ <input type="hidden" name="projectName" value="${esc(g.project)}">
275
+ <label class="uk-form-label">Active environment to sync from
276
+ <select class="uk-select uk-margin-small-bottom" name="environment">
277
+ ${envs.map((e) => `<option value="${esc(e)}"${e === (u.env || u.branch || 'main') ? ' selected' : ''}>${esc(e)}</option>`).join('')}
278
+ </select>
279
+ ${inactive ? `<span class="uk-text-meta">${inactive} inactive environment${inactive > 1 ? 's' : ''} not offered.</span>` : ''}
280
+ </label>
281
+ ${pull.db ? '<label><input class="uk-checkbox" type="checkbox" name="db" checked> Database</label>' : ''}
282
+ ${pull.files ? '<label class="uk-margin-left"><input class="uk-checkbox" type="checkbox" name="files" checked> Files</label>' : ''}
283
+ ${isGit && pull.gitRemote
284
+ ? `<label class="uk-margin-left"><input class="uk-checkbox" type="checkbox" name="switch" checked> Switch the local code to this branch</label>
285
+ <div class="uk-text-meta">Uncommitted local changes are stashed first (recover with <code>git stash pop</code>), then the code switches. After the pull, caches are rebuilt and the local site is checked.</div>`
286
+ : '<div class="uk-text-meta">The code stays as it is; caches are rebuilt and the local site checked after the pull.</div>'}
287
+ ${pull.db && pull.noSafetyBackupArg ? `<label class="uk-margin-small uk-display-block">
288
+ <input class="uk-checkbox" type="checkbox" name="backup" value="yes" checked>
289
+ Back up the local database into <code>backups/${esc(ctx.workspace)}/${esc(g.project)}/</code> first
290
+ </label>` : ''}
291
+ <label class="uk-margin-small uk-display-block">
292
+ <input class="uk-checkbox" type="checkbox" name="confirm" value="yes" required>
293
+ I confirm: data from <strong>${esc(p.title)}</strong> replaces this local DDEV site's data
294
+ </label>
295
+ <div class="uk-margin-small-top">
296
+ <button type="submit" class="uk-button uk-button-primary"><span uk-icon="icon: cloud-download; ratio: .8"></span> Get it</button>
297
+ </div>
298
+ </form>
299
+ </div>`);
300
+ });
301
+ api.addAction('pull', async (ctx) => {
302
+ const g = guard(ctx);
303
+ if (!g)
304
+ return;
305
+ const { form, send, workspace } = ctx;
306
+ const environment = String(form.environment || '').trim();
307
+ const wantDb = pull.db && form.db !== undefined;
308
+ const wantFiles = pull.files && form.files !== undefined;
309
+ if (!/^[a-zA-Z0-9._\/-]+$/.test(environment))
310
+ return send('<div class="msg error">Not an environment name.</div>', 400);
311
+ const u = linkOf(g.dir, g.project);
312
+ if (!u || !u.id)
313
+ return send(`<div class="msg error">This project is not linked to a ${esc(p.title)} project.</div>`, 409);
314
+ const env = p.jobEnv();
315
+ if (!env)
316
+ return send(`<div class="msg error">No ${esc(p.title)} API token configured (${esc(p.tokenHint)}).</div>`, 409);
317
+ if (p.environmentActive && !(await p.environmentActive(u, environment, api.lib, env))) {
318
+ return send(`<div class="msg error">${esc(environment)} is not active — nothing running to sync from.</div>`, 409);
319
+ }
320
+ if (!wantDb && !wantFiles)
321
+ return send('<div class="msg error">Pick the database, the files, or both.</div>', 400);
322
+ const confirmed = [].concat(form.confirm || '').includes('yes');
323
+ if (!confirmed)
324
+ return send('<div class="msg error">Syncing replaces local data — it requires confirmation.</div>', 400);
325
+ const wantSwitch = !!pull.gitRemote && form.switch !== undefined && fs_1.default.existsSync(path_1.default.join(g.dir, g.project, '.git'));
326
+ const parts = [
327
+ `echo "Starting DDEV for ${g.project}…" && (cd ${g.project} && ddev start -y > /dev/null && ddev describe > /dev/null) && echo "DDEV is up and healthy."`,
328
+ ];
329
+ if (wantSwitch) {
330
+ parts.push([
331
+ `cd ${g.project}`,
332
+ `if [ -n "$(git status --porcelain)" ]; then STASH_NAME="${p.id}-pull $(date +%Y-%m-%d-%H%M%S)" && git stash push --include-untracked -m "$STASH_NAME" && echo "Local changes stashed as: $STASH_NAME (git stash pop brings them back)"; fi`,
333
+ `echo "Switching the code to ${environment}…"`,
334
+ `git fetch ${pull.gitRemote} ${environment}`,
335
+ `(git checkout ${environment} 2>/dev/null || git checkout -b ${environment} ${pull.gitRemote}/${environment})`,
336
+ `git pull --ff-only ${pull.gitRemote} ${environment}`,
337
+ `echo "Code is on ${environment}."`,
338
+ 'cd ..',
339
+ ].join(' && '));
340
+ }
341
+ const noSafety = pull.noSafetyBackupArg && form.backup === undefined ? ` ${pull.noSafetyBackupArg}` : '';
342
+ if (wantDb)
343
+ parts.push(`bash ${p.script} ${g.project} ${pull.dbArgs(environment).join(' ')}${noSafety}`);
344
+ if (wantFiles)
345
+ parts.push(`bash ${p.script} ${g.project} ${pull.filesArgs(environment).join(' ')}`);
346
+ parts.push([
347
+ `cd ${g.project}`,
348
+ 'echo "Rebuilding caches (drush cr)…"',
349
+ 'ddev drush cr > /dev/null 2>&1 || echo "drush cr failed — not a Drupal site, or the codebase does not match the database"',
350
+ `for i in 1 2 3 4 5 6 7 8 9 10; do code=$(curl -sk -o /dev/null -w "%{http_code}" "https://${g.project}.ddev.site/"); case "$code" in 200|301|302|303) break ;; esac; sleep 3; done`,
351
+ `echo "Local site check: https://${g.project}.ddev.site/ answered $code"`,
352
+ 'case "$code" in 200|301|302|303) echo "✅ The local site is working." ;; *) echo "⚠️ The local site did not answer cleanly (${code:-no response after 30s}) — check ddev logs." ;; esac',
353
+ ].join(' && '));
354
+ const started = await startInScreen({ workspace, project: g.project, projectDir: g.dir, ownerDir: path_1.default.join(g.dir, g.project),
355
+ verb: `${p.id}-pull-${environment}`, cmdLine: parts.join(' && '),
356
+ label: `get ${wantDb ? 'database' : ''}${wantDb && wantFiles ? ' + ' : ''}${wantFiles ? 'files' : ''} from ${environment}` });
357
+ return send(screenStartedHtml(started, `${p.id} pull from ${environment}`, g.project), started.ok ? 200 : (started.already ? 409 : 500));
358
+ });
359
+ void run;
360
+ void errorText;
361
+ }
362
+ // ---- New folder: link it to a project, or create one with AI ----------------------------------
363
+ if (p.folder) {
364
+ const f = p.folder;
365
+ api.addFolderSetup({
366
+ html: (ws, { allowed }) => {
367
+ if (!can(allowed, workspaceDir(ws)))
368
+ return '';
369
+ return `<div class="uk-margin-small-top folder-hosting folder-${esc(p.id)}">
370
+ <span class="uk-text-meta">${esc(p.title)}:</span>
371
+ <label class="uk-margin-small-right"><input class="uk-radio" type="radio" name="${esc(p.id)}" value="none" checked> Not now</label>
372
+ <label class="uk-margin-small-right"><input class="uk-radio" type="radio" name="${esc(p.id)}" value="link"> Link to an existing project</label>
373
+ ${f.createPrompt ? `<label><input class="uk-radio" type="radio" name="${esc(p.id)}" value="create"> Create a new project and deploy it, with AI (it asks before creating anything billed)</label>` : ''}
374
+ <div class="folder-hosting-id uk-margin-small-top">
375
+ <label class="uk-form-label" for="folder-${esc(p.id)}-id">${esc(p.title)} project id</label>
376
+ <input class="uk-input uk-form-small uk-form-width-medium" id="folder-${esc(p.id)}-id" name="${esc(p.id)}_id"
377
+ pattern="${esc(f.idPattern.source)}" placeholder="${esc(f.idPlaceholder)}">
378
+ </div>
379
+ </div>`;
380
+ },
381
+ steps: (ws, name, form, { allowed }) => {
382
+ if (form[p.id] !== 'link' || !can(allowed, workspaceDir(ws)))
383
+ return [];
384
+ return [`( cd ${shq(name)} && { git rev-parse --git-dir >/dev/null 2>&1 || git init -q; } )`,
385
+ `bash ${p.script} ${shq(name)} --link ${shq(form[`${p.id}_id`])}`];
386
+ },
387
+ prompt: (_ws, _name, form) => (form[p.id] === 'create' && f.createPrompt ? [f.createPrompt] : []),
388
+ problem: (ws, form, { allowed }) => {
389
+ if (form[p.id] !== 'link' && form[p.id] !== 'create')
390
+ return '';
391
+ if (!can(allowed, workspaceDir(ws)))
392
+ return `This workspace does not link projects to ${p.title}.`;
393
+ if (form[p.id] === 'link' && !f.idPattern.test(String(form[`${p.id}_id`] || '')))
394
+ return f.idHelp;
395
+ return '';
396
+ },
397
+ });
398
+ }
399
+ }
400
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * What a hosting provider is, to the dashboard.
3
+ *
4
+ * A project lives on one host. A provider knows how to tell that a project is linked to it (a file
5
+ * the CLI wrote, a git remote), what its environments are and where they answer, how to fetch a
6
+ * database and the files, how to deploy, and how to read its logs. The row menu, the modal, the
7
+ * actions and the New-folder section are the family's (registerHosting); the provider supplies the
8
+ * facts and the commands. Upsun is the reference implementation.
9
+ */
10
+ import type { PluginLib } from '@webship.co/workspace-plugin-api';
11
+ /** A project's link to the host: what the provider's CLI or config says it is. */
12
+ export interface HostingLink {
13
+ /** The project's id on the host (an Upsun project id, an Acquia application uuid, a DO app id…). */
14
+ id: string | null;
15
+ /** The environment the local tooling works against, when one was selected. */
16
+ env?: string | null;
17
+ /** The branch the local code is on, when the project is a git clone. */
18
+ branch?: string | null;
19
+ host?: string | null;
20
+ remote?: string | null;
21
+ /** The link and the git remote disagree (a deploy would go somewhere other than the id says). */
22
+ mismatch?: boolean;
23
+ mismatchNote?: string;
24
+ }
25
+ export interface EnvRow {
26
+ title: string;
27
+ envId: string;
28
+ /** 'active' means running; anything else is listed without a link. */
29
+ status: string;
30
+ url?: string;
31
+ }
32
+ export interface EnvInfo {
33
+ id: string | null;
34
+ rows: EnvRow[];
35
+ branch?: string | null;
36
+ env?: string | null;
37
+ /** Why there are no rows: 'no token', 'list failed', 'not linked'. */
38
+ why?: string;
39
+ }
40
+ /** A verb the per-workspace command carries, run in a screen session on the project. */
41
+ export interface ToolVerb {
42
+ /** The screen verb suffix (`upsun-logs`). */
43
+ verb: string;
44
+ /** What the menu calls it. */
45
+ label: string;
46
+ /** An icon name for the menu (`file-text`). */
47
+ icon: string;
48
+ /** The flags appended to `bash <script> <project>`. */
49
+ args: string[];
50
+ }
51
+ export interface PullSpec {
52
+ /** The provider can bring the database down. */
53
+ db: boolean;
54
+ /** The provider can bring the public files down. */
55
+ files: boolean;
56
+ /** The flags of the per-workspace command for each pull, given the environment. */
57
+ dbArgs: (env: string) => string[];
58
+ filesArgs: (env: string) => string[];
59
+ /** The git remote the local code may be switched to the environment's branch from, if any. */
60
+ gitRemote?: string;
61
+ /** The flag that skips the safety export of the local database before --pull-db, if the command has one. */
62
+ noSafetyBackupArg?: string;
63
+ }
64
+ export interface HostingProvider {
65
+ /** The slot, the settings key and the route namespace: 'upsun'. */
66
+ id: string;
67
+ /** The button's text: 'UPSUN'. */
68
+ label: string;
69
+ /** The product's name in sentences: 'Upsun'. */
70
+ title: string;
71
+ /** The CLI the actions run inside the dashboard container, when there is one. */
72
+ cli?: string;
73
+ /** The per-workspace command the plugin installs: 'cmd-tools-upsun.sh'. */
74
+ script: string;
75
+ /** The type action a workspace must allow: usually the id. */
76
+ typeAction?: string;
77
+ /** Whether a project is linked, and to what. Memoised by the caller per render. */
78
+ detect: (projectDir: string) => HostingLink | null;
79
+ /** How to link an unlinked project, from the workspace folder. */
80
+ linkHint: (project: string) => string;
81
+ /** The provider's console page for a linked project, or null. */
82
+ consoleUrl?: (link: HostingLink) => string | null;
83
+ /** The environment the actions inject (an API token from the plugin's settings), or null when there is none. */
84
+ jobEnv: () => NodeJS.ProcessEnv | null;
85
+ /** Where the token goes, for the message when there is none. */
86
+ tokenHint: string;
87
+ /** The environments and their live URLs. Omitted: the provider lists none. */
88
+ environments?: (link: HostingLink, lib: PluginLib, env: NodeJS.ProcessEnv) => Promise<EnvInfo>;
89
+ /** Whether an environment is active, before a pull; omitted: assumed. */
90
+ environmentActive?: (link: HostingLink, envId: string, lib: PluginLib, env: NodeJS.ProcessEnv) => Promise<boolean>;
91
+ /** The verbs the menu offers through the per-workspace command (status, activities, logs, deploy…). */
92
+ tools: Record<string, ToolVerb>;
93
+ /** Getting a database and files down, when the provider can. */
94
+ pull?: PullSpec;
95
+ /** The provider's own reading of a CLI failure, for the one message that matters. */
96
+ errorHint?: (message: string) => string;
97
+ /** The New-folder section: link an existing project (id pattern), or create one with AI. */
98
+ folder?: {
99
+ idPattern: RegExp;
100
+ idPlaceholder: string;
101
+ idHelp: string;
102
+ createPrompt?: string;
103
+ };
104
+ /** A line the menu ends with, for what stays on the command line. */
105
+ menuNote?: string;
106
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ /**
3
+ * What a hosting provider is, to the dashboard.
4
+ *
5
+ * A project lives on one host. A provider knows how to tell that a project is linked to it (a file
6
+ * the CLI wrote, a git remote), what its environments are and where they answer, how to fetch a
7
+ * database and the files, how to deploy, and how to read its logs. The row menu, the modal, the
8
+ * actions and the New-folder section are the family's (registerHosting); the provider supplies the
9
+ * facts and the commands. Upsun is the reference implementation.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/provider.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG"}
package/package.json CHANGED
@@ -1,6 +1,31 @@
1
1
  {
2
2
  "name": "@webship.co/workspace-hosting",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0-beta1",
4
+ "description": "The hosting family's shared base for Webship Workspace plugins: the HostingProvider contract, and the row menu, environments modal, get-database-and-files flow, live-site chip, actions and New-folder section every hosting plugin registers through registerHosting(api, provider).",
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-hosting.git"
11
+ },
12
+ "files": [
13
+ "dist/src",
14
+ "css",
15
+ "README.md"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc -p tsconfig.json",
19
+ "prepare": "npm run build",
20
+ "test": "npm run build && node --test dist/tests/*.test.js",
21
+ "lint": "tsc -p tsconfig.json --noEmit"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^24.0.0",
25
+ "@webship.co/workspace-plugin-api": "^1.0.0-beta1",
26
+ "typescript": "^5.9.0"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ }
31
+ }