@webship.co/workspace-container 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,16 @@
1
- # Temporary Holding Version
1
+ # @webship.co/workspace-container
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 container family's shared base for [Webship Workspace](https://github.com/webship/workspace)
4
+ plugins. A runtime plugin (`@webship.co/workspace-docker`, `-lando`, `-docksal`, `-podman`,
5
+ `-kubernetes`) is one call plus its runtime:
6
+
7
+ ```ts
8
+ import { registerContainer } from '@webship.co/workspace-container';
9
+ export function register(api: PluginApi) { registerContainer(api, runtime); }
10
+ ```
11
+
12
+ `registerContainer` draws the row menu on every project the runtime recognises (its verbs as
13
+ terminal sessions in the project: start, stop, restart, status, logs, shell and the runtime's own,
14
+ plus the site link), answers the up/down caption after the row is drawn, and adds the "scaffold a
15
+ project file here" option to New folder. The `ContainerRuntime` says what differs: how a folder is
16
+ detected as a project, the CLI, how to tell whether it is up, the verbs and their commands.
@@ -0,0 +1,7 @@
1
+ /* The container family: the per-runtime menu and the New-folder init option.
2
+ Appended to the dashboard stylesheet after the components, so it may only ADD rules. */
3
+ .container-menu-note { text-transform: none; font-weight: 400; opacity: .75; white-space: normal; }
4
+ .container-menu-note code { white-space: normal; overflow-wrap: anywhere; }
5
+ .container-absent { opacity: .75; }
6
+ .container-absent:hover { opacity: 1; }
7
+ .act-menu-container .uk-nav-header[data-group="project"] { text-transform: none; font-family: var(--font-code); }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * registerContainer(api, runtime): the whole surface of a container-runtime plugin.
3
+ *
4
+ * The row menu on every project the runtime recognises (its verbs as screen sessions in the
5
+ * project, grouped: start/stop/restart, status, logs and shell, the rest; the site link), the
6
+ * up/down caption, and the New-folder init option. What differs between runtimes is in the runtime.
7
+ */
8
+ import type { PluginApi } from '@webship.co/workspace-plugin-api';
9
+ import type { ContainerProject, ContainerRuntime } from './runtime';
10
+ export * from './runtime';
11
+ /** The first of `files` that exists in the folder, as a project, or null. */
12
+ export declare function detectByFiles(projectDir: string, files: string[]): ContainerProject | null;
13
+ export declare function registerContainer(api: PluginApi, r: ContainerRuntime): void;
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ /**
3
+ * registerContainer(api, runtime): the whole surface of a container-runtime plugin.
4
+ *
5
+ * The row menu on every project the runtime recognises (its verbs as screen sessions in the
6
+ * project, grouped: start/stop/restart, status, logs and shell, the rest; the site link), the
7
+ * up/down caption, and the New-folder init option. What differs between runtimes is in the runtime.
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
21
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
22
+ };
23
+ var __importDefault = (this && this.__importDefault) || function (mod) {
24
+ return (mod && mod.__esModule) ? mod : { "default": mod };
25
+ };
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.detectByFiles = detectByFiles;
28
+ exports.registerContainer = registerContainer;
29
+ const fs_1 = __importDefault(require("fs"));
30
+ const path_1 = __importDefault(require("path"));
31
+ __exportStar(require("./runtime"), exports);
32
+ /** The first of `files` that exists in the folder, as a project, or null. */
33
+ function detectByFiles(projectDir, files) {
34
+ for (const f of files) {
35
+ if (fs_1.default.existsSync(path_1.default.join(projectDir, f)))
36
+ return { file: f };
37
+ }
38
+ return null;
39
+ }
40
+ function registerContainer(api, r) {
41
+ const { esc, workspaceDir, listProjects, screenAction, NAME_RE } = api.lib;
42
+ const post = (name) => `/actions/p/${api.name}/${name}`;
43
+ const shq = (v) => `'${String(v).replace(/'/g, `'\\''`)}'`;
44
+ if (typeof api.addCss === 'function')
45
+ api.addCss(path_1.default.join(__dirname, '..', '..', 'css', 'container.css'));
46
+ if (typeof api.addTypeAction === 'function')
47
+ api.addTypeAction(r.id, ['sites', 'code']);
48
+ // The detection, memoised briefly: a page of thirty rows stats the same files once.
49
+ let cache = new Map();
50
+ let cacheAt = 0;
51
+ const projectOf = (dir) => {
52
+ const now = Date.now();
53
+ if (now - cacheAt > 5000) {
54
+ cache = new Map();
55
+ cacheAt = now;
56
+ }
57
+ if (!cache.has(dir)) {
58
+ try {
59
+ cache.set(dir, r.detect(dir));
60
+ }
61
+ catch (_) {
62
+ cache.set(dir, null);
63
+ }
64
+ }
65
+ return cache.get(dir) || null;
66
+ };
67
+ // The state, cached for a short while per folder: the menu must not run the CLI per render.
68
+ const states = new Map();
69
+ async function stateOf(p, dir) {
70
+ const hit = states.get(dir);
71
+ if (hit && Date.now() - hit.at < 15 * 1000)
72
+ return hit.value;
73
+ let value = 'unknown';
74
+ try {
75
+ value = await r.state(p, dir, api.lib);
76
+ }
77
+ catch (_) {
78
+ value = 'unknown';
79
+ }
80
+ states.set(dir, { at: Date.now(), value });
81
+ return value;
82
+ }
83
+ const item = (name, v, vals) => `<li><a href hx-post="${post(`verb-${name}`)}" ${vals()}${v.danger ? ' class="uk-text-danger"' : ''}><span uk-icon="icon: ${esc(v.icon)}; ratio: .7"></span> ${esc(v.label)}</a></li>`;
84
+ api.addRowMenu(r.id, { label: r.label, row: 0 }, ({ project, dir, allowed, vals }) => {
85
+ if (allowed.size && !allowed.has(r.id))
86
+ return null;
87
+ const pdir = path_1.default.join(dir, project);
88
+ const p = projectOf(pdir);
89
+ if (!p)
90
+ return null;
91
+ const groups = { lifecycle: [], status: [], look: [], other: [] };
92
+ for (const [name, v] of Object.entries(r.verbs)) {
93
+ const html = item(name, v, vals);
94
+ if (v.kind === 'start' || v.kind === 'stop' || v.kind === 'restart')
95
+ groups.lifecycle.push(html);
96
+ else if (v.kind === 'status')
97
+ groups.status.push(html);
98
+ else if (v.kind === 'logs' || v.kind === 'shell')
99
+ groups.look.push(html);
100
+ else
101
+ groups.other.push(html);
102
+ }
103
+ return `<span class="act-with-status"><div class="uk-inline act-menu act-menu-${esc(r.id)} act-menu-container">
104
+ <button class="uk-button uk-button-default uk-button-small" type="button" title="${esc(r.title)}: ${esc(p.file)}"><span uk-icon="icon: server; ratio: .7"></span> ${esc(r.label)} <span uk-icon="icon: chevron-down; ratio: .6"></span></button>
105
+ <div uk-dropdown="mode: click; pos: bottom-right"><ul class="uk-nav uk-dropdown-nav">
106
+ <li class="uk-nav-header" data-group="project">${esc(p.name || project)} · ${esc(p.file)}</li>
107
+ ${p.url ? `<li><a href="${esc(p.url)}" target="_blank" rel="noopener"><span uk-icon="icon: link-external; ratio: .7"></span> Open the site</a></li>` : ''}
108
+ ${groups.lifecycle.join('')}
109
+ ${groups.status.length ? `<li class="uk-nav-divider"></li>${groups.status.join('')}` : ''}
110
+ ${groups.look.length ? `<li class="uk-nav-divider"></li>${groups.look.join('')}` : ''}
111
+ ${groups.other.length ? `<li class="uk-nav-divider"></li>${groups.other.join('')}` : ''}
112
+ ${r.menuNote ? `<li class="uk-nav-header container-menu-note">${esc(r.menuNote)}</li>` : ''}
113
+ </ul></div>
114
+ </div><span class="act-status uk-text-meta" hx-get="/fragments/p/${esc(api.name)}/state/${encodeURIComponent(dir.split('/').pop() || '')}/${encodeURIComponent(project)}" hx-trigger="load delay:200ms" hx-swap="innerHTML">…</span></span>`;
115
+ });
116
+ // The caption, answered after the row is drawn: 'up', 'down' or '?'.
117
+ api.addFragment(async ({ send, rest }) => {
118
+ const m = rest.match(/^state\/([a-z0-9_-]+)\/([a-zA-Z0-9_-]+)$/);
119
+ if (!m)
120
+ return send('', 404);
121
+ const dir = path_1.default.join(workspaceDir(m[1]), m[2]);
122
+ const p = projectOf(dir);
123
+ if (!p)
124
+ return send('');
125
+ const s = await stateOf(p, dir);
126
+ send(s === 'unknown' ? '?' : s);
127
+ });
128
+ const guard = ({ form, send, workspace }) => {
129
+ const dir = workspaceDir(workspace);
130
+ const project = String(form.projectName || '');
131
+ if (!NAME_RE.test(project) || !listProjects(dir).includes(project)) {
132
+ send('<div class="msg error">Unknown project.</div>', 400);
133
+ return null;
134
+ }
135
+ const p = projectOf(path_1.default.join(dir, project));
136
+ if (!p) {
137
+ send(`<div class="msg error">This folder is not a ${esc(r.title)} project.</div>`, 409);
138
+ return null;
139
+ }
140
+ return { dir, project, p };
141
+ };
142
+ for (const [name, v] of Object.entries(r.verbs)) {
143
+ api.addAction(`verb-${name}`, (ctx) => {
144
+ const g = guard(ctx);
145
+ if (!g)
146
+ return;
147
+ states.delete(path_1.default.join(g.dir, g.project));
148
+ return screenAction(ctx.send, { workspace: ctx.workspace, project: g.project, projectDir: path_1.default.join(g.dir, g.project),
149
+ ownerDir: path_1.default.join(g.dir, g.project), verb: `${r.id}-${name}`,
150
+ cmdLine: `command -v ${r.cli} >/dev/null 2>&1 || { echo "${r.cli} is not installed: ${r.cliHint.replace(/"/g, '\\"')}"; exit 1; }; ${v.cmd(g.p, g.project)}`,
151
+ label: `${r.id} ${v.label.toLowerCase()}` });
152
+ });
153
+ }
154
+ if (r.init) {
155
+ const init = r.init;
156
+ api.addFolderSetup({
157
+ html: () => `<div class="uk-margin-small-top folder-container folder-${esc(r.id)}">
158
+ <span class="uk-text-meta">${esc(r.title)}:</span>
159
+ <label class="uk-margin-small-right"><input class="uk-radio" type="radio" name="${esc(r.id)}" value="none" checked> Not now</label>
160
+ <label><input class="uk-radio" type="radio" name="${esc(r.id)}" value="init"> ${esc(init.label)}</label>
161
+ ${init.help ? `<div class="uk-text-meta">${esc(init.help)}</div>` : ''}
162
+ </div>`,
163
+ steps: (_ws, name, form) => (form[r.id] === 'init' ? [`( cd ${shq(name)} && ${init.cmd(name)} )`] : []),
164
+ prompt: () => [],
165
+ problem: () => '',
166
+ });
167
+ }
168
+ }
169
+ //# 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,59 @@
1
+ /**
2
+ * What a container runtime is, to the dashboard.
3
+ *
4
+ * A runtime (Docker Compose, Lando, Docksal, Podman, Kubernetes) runs a project from a file the
5
+ * project carries (docker-compose.yml, .lando.yml, .docksal/, a Containerfile, k8s manifests). The
6
+ * row menu, the caption, the screen sessions and the New-folder section are the family's
7
+ * (registerContainer); the runtime says how a project is detected, which commands do what, how to
8
+ * tell whether it is up, and where the site answers.
9
+ */
10
+ import type { PluginLib } from '@webship.co/workspace-plugin-api';
11
+ /** The project as the runtime sees it. */
12
+ export interface ContainerProject {
13
+ /** The file (or folder) that made it a project of this runtime: 'docker-compose.yml'. */
14
+ file: string;
15
+ /** The project's name for the runtime, when it has one (the compose project, the lando app). */
16
+ name?: string;
17
+ /** Where the site answers, when the file says (a lando proxy, a compose port). */
18
+ url?: string;
19
+ }
20
+ export interface RuntimeVerb {
21
+ /** What the menu calls it. */
22
+ label: string;
23
+ /** An icon name for the menu. */
24
+ icon: string;
25
+ /** The command line, run in the project folder in a screen session. */
26
+ cmd: (p: ContainerProject, project: string) => string;
27
+ /** 'start' | 'stop' | 'restart' | 'status' | 'logs' | 'shell' | 'other': where the menu puts it. */
28
+ kind: 'start' | 'stop' | 'restart' | 'status' | 'logs' | 'shell' | 'other';
29
+ /** Only offered when the project is up (logs, shell) or down (start). */
30
+ when?: 'up' | 'down';
31
+ /** Outward-facing or destructive: the menu says so. */
32
+ danger?: boolean;
33
+ }
34
+ export interface ContainerRuntime {
35
+ /** The slot, the type action and the route namespace: 'lando'. */
36
+ id: string;
37
+ /** The button's text: 'LANDO'. */
38
+ label: string;
39
+ /** The product's name in sentences: 'Lando'. */
40
+ title: string;
41
+ /** The CLI: 'lando', 'fin', 'docker', 'podman', 'kubectl'. */
42
+ cli: string;
43
+ /** How to install it, for the message when it is missing. */
44
+ cliHint: string;
45
+ /** Whether a folder is a project of this runtime, and what. Memoised by the caller per render. */
46
+ detect: (projectDir: string) => ContainerProject | null;
47
+ /** Whether the project is up: a quick command answering 'up', 'down' or 'unknown'. Cached by the caller. */
48
+ state: (p: ContainerProject, projectDir: string, lib: PluginLib) => Promise<'up' | 'down' | 'unknown'>;
49
+ /** The verbs, in menu order. */
50
+ verbs: Record<string, RuntimeVerb>;
51
+ /** The New-folder option: scaffold a project file (the command run in the new folder), or none. */
52
+ init?: {
53
+ label: string;
54
+ cmd: (name: string) => string;
55
+ help?: string;
56
+ };
57
+ /** A line the menu ends with. */
58
+ menuNote?: string;
59
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ /**
3
+ * What a container runtime is, to the dashboard.
4
+ *
5
+ * A runtime (Docker Compose, Lando, Docksal, Podman, Kubernetes) runs a project from a file the
6
+ * project carries (docker-compose.yml, .lando.yml, .docksal/, a Containerfile, k8s manifests). The
7
+ * row menu, the caption, the screen sessions and the New-folder section are the family's
8
+ * (registerContainer); the runtime says how a project is detected, which commands do what, how to
9
+ * tell whether it is up, and where the site answers.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/runtime.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG"}
package/package.json CHANGED
@@ -1,6 +1,31 @@
1
1
  {
2
2
  "name": "@webship.co/workspace-container",
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": "The container family's shared base for Webship Workspace plugins: the ContainerRuntime contract, and the row menu (start, stop, restart, status, logs, shell, open the site, the runtime's own verbs), the state caption and the New-folder init option every runtime plugin registers through registerContainer(api, runtime).",
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-container.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-alpha1",
26
+ "typescript": "^5.9.0"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ }
31
+ }