dev-cockpit 0.1.0
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/CHANGELOG.md +36 -0
- package/LICENSE +21 -0
- package/README.md +131 -0
- package/bin/dev-cockpit.mjs +20 -0
- package/dist/buildCli.d.ts +17 -0
- package/dist/buildCli.d.ts.map +1 -0
- package/dist/buildCli.js +107 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +2 -0
- package/dist/cockpit/Cockpit.d.ts +33 -0
- package/dist/cockpit/Cockpit.d.ts.map +1 -0
- package/dist/cockpit/Cockpit.js +73 -0
- package/dist/cockpit/Footer.d.ts +22 -0
- package/dist/cockpit/Footer.d.ts.map +1 -0
- package/dist/cockpit/Footer.js +33 -0
- package/dist/cockpit/TabBar.d.ts +3 -0
- package/dist/cockpit/TabBar.d.ts.map +1 -0
- package/dist/cockpit/TabBar.js +12 -0
- package/dist/cockpit/help/content.d.ts +12 -0
- package/dist/cockpit/help/content.d.ts.map +1 -0
- package/dist/cockpit/help/content.js +22 -0
- package/dist/cockpit/help/loader.d.ts +65 -0
- package/dist/cockpit/help/loader.d.ts.map +1 -0
- package/dist/cockpit/help/loader.js +118 -0
- package/dist/cockpit/help/renderer.d.ts +16 -0
- package/dist/cockpit/help/renderer.d.ts.map +1 -0
- package/dist/cockpit/help/renderer.js +35 -0
- package/dist/cockpit/help/types.d.ts +12 -0
- package/dist/cockpit/help/types.d.ts.map +1 -0
- package/dist/cockpit/help/types.js +1 -0
- package/dist/cockpit/hooks/useCockpitStore.d.ts +3 -0
- package/dist/cockpit/hooks/useCockpitStore.d.ts.map +1 -0
- package/dist/cockpit/hooks/useCockpitStore.js +5 -0
- package/dist/cockpit/hooks/useGlobalKeys.d.ts +56 -0
- package/dist/cockpit/hooks/useGlobalKeys.d.ts.map +1 -0
- package/dist/cockpit/hooks/useGlobalKeys.js +173 -0
- package/dist/cockpit/panes/FilterModal.d.ts +3 -0
- package/dist/cockpit/panes/FilterModal.d.ts.map +1 -0
- package/dist/cockpit/panes/FilterModal.js +22 -0
- package/dist/cockpit/panes/Health.d.ts +13 -0
- package/dist/cockpit/panes/Health.d.ts.map +1 -0
- package/dist/cockpit/panes/Health.js +30 -0
- package/dist/cockpit/panes/Help.d.ts +14 -0
- package/dist/cockpit/panes/Help.d.ts.map +1 -0
- package/dist/cockpit/panes/Help.js +81 -0
- package/dist/cockpit/panes/Output.d.ts +14 -0
- package/dist/cockpit/panes/Output.d.ts.map +1 -0
- package/dist/cockpit/panes/Output.js +108 -0
- package/dist/cockpit/panes/Repos.d.ts +3 -0
- package/dist/cockpit/panes/Repos.d.ts.map +1 -0
- package/dist/cockpit/panes/Repos.js +48 -0
- package/dist/cockpit/panes/SearchModal.d.ts +3 -0
- package/dist/cockpit/panes/SearchModal.d.ts.map +1 -0
- package/dist/cockpit/panes/SearchModal.js +31 -0
- package/dist/cockpit/state/store.d.ts +93 -0
- package/dist/cockpit/state/store.d.ts.map +1 -0
- package/dist/cockpit/state/store.js +111 -0
- package/dist/cockpit/tab-state.d.ts +4 -0
- package/dist/cockpit/tab-state.d.ts.map +1 -0
- package/dist/cockpit/tab-state.js +7 -0
- package/dist/commands/dev.d.ts +20 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +158 -0
- package/dist/commands/doctor.d.ts +20 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +66 -0
- package/dist/commands/init-config-wizard.d.ts +84 -0
- package/dist/commands/init-config-wizard.d.ts.map +1 -0
- package/dist/commands/init-config-wizard.js +818 -0
- package/dist/commands/init-config.d.ts +35 -0
- package/dist/commands/init-config.d.ts.map +1 -0
- package/dist/commands/init-config.js +131 -0
- package/dist/commands/mount.d.ts +48 -0
- package/dist/commands/mount.d.ts.map +1 -0
- package/dist/commands/mount.js +150 -0
- package/dist/core/config.d.ts +391 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +152 -0
- package/dist/core/logger.d.ts +6 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +38 -0
- package/dist/core/notifier.d.ts +23 -0
- package/dist/core/notifier.d.ts.map +1 -0
- package/dist/core/notifier.js +100 -0
- package/dist/core/paths.d.ts +15 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +18 -0
- package/dist/core/subprocess.d.ts +20 -0
- package/dist/core/subprocess.d.ts.map +1 -0
- package/dist/core/subprocess.js +82 -0
- package/dist/core/types.d.ts +125 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +1 -0
- package/dist/docker/highlights.d.ts +48 -0
- package/dist/docker/highlights.d.ts.map +1 -0
- package/dist/docker/highlights.js +79 -0
- package/dist/docker/logs.d.ts +84 -0
- package/dist/docker/logs.d.ts.map +1 -0
- package/dist/docker/logs.js +172 -0
- package/dist/docker/restart.d.ts +26 -0
- package/dist/docker/restart.d.ts.map +1 -0
- package/dist/docker/restart.js +45 -0
- package/dist/docker/stack-trace.d.ts +25 -0
- package/dist/docker/stack-trace.d.ts.map +1 -0
- package/dist/docker/stack-trace.js +44 -0
- package/dist/health/builtin.d.ts +8 -0
- package/dist/health/builtin.d.ts.map +1 -0
- package/dist/health/builtin.js +144 -0
- package/dist/health/context.d.ts +3 -0
- package/dist/health/context.d.ts.map +1 -0
- package/dist/health/context.js +31 -0
- package/dist/health/notify-resolver.d.ts +18 -0
- package/dist/health/notify-resolver.d.ts.map +1 -0
- package/dist/health/notify-resolver.js +28 -0
- package/dist/health/registry.d.ts +20 -0
- package/dist/health/registry.d.ts.map +1 -0
- package/dist/health/registry.js +64 -0
- package/dist/health/remediations.d.ts +6 -0
- package/dist/health/remediations.d.ts.map +1 -0
- package/dist/health/remediations.js +41 -0
- package/dist/health/runner.d.ts +4 -0
- package/dist/health/runner.d.ts.map +1 -0
- package/dist/health/runner.js +22 -0
- package/dist/health/scheduler.d.ts +41 -0
- package/dist/health/scheduler.d.ts.map +1 -0
- package/dist/health/scheduler.js +107 -0
- package/dist/health/types.d.ts +73 -0
- package/dist/health/types.d.ts.map +1 -0
- package/dist/health/types.js +1 -0
- package/dist/health/useHealth.d.ts +40 -0
- package/dist/health/useHealth.d.ts.map +1 -0
- package/dist/health/useHealth.js +122 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/ink.d.ts +3 -0
- package/dist/ink.d.ts.map +1 -0
- package/dist/ink.js +1 -0
- package/dist/lint/reactive.d.ts +38 -0
- package/dist/lint/reactive.d.ts.map +1 -0
- package/dist/lint/reactive.js +131 -0
- package/dist/react.d.ts +3 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +1 -0
- package/dist/runCockpit.d.ts +34 -0
- package/dist/runCockpit.d.ts.map +1 -0
- package/dist/runCockpit.js +75 -0
- package/dist/watchers/manager.d.ts +63 -0
- package/dist/watchers/manager.d.ts.map +1 -0
- package/dist/watchers/manager.js +239 -0
- package/dist/watchers/path-mapper.d.ts +23 -0
- package/dist/watchers/path-mapper.d.ts.map +1 -0
- package/dist/watchers/path-mapper.js +29 -0
- package/dist/watchers/types.d.ts +22 -0
- package/dist/watchers/types.d.ts.map +1 -0
- package/dist/watchers/types.js +9 -0
- package/docs/commands.md +71 -0
- package/docs/config-reference.md +20 -0
- package/docs/getting-started.md +39 -0
- package/docs/health.md +120 -0
- package/docs/index.md +13 -0
- package/docs/init-config.md +46 -0
- package/docs/mount.md +55 -0
- package/docs/notifications.md +39 -0
- package/docs/panes.md +45 -0
- package/docs/watchers.md +27 -0
- package/examples/cockpit.yaml +116 -0
- package/package.json +91 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dev-cockpit init-config` — write a starter `cockpit.yaml`.
|
|
3
|
+
*
|
|
4
|
+
* The template is intentionally minimal and domain-neutral. Every section is
|
|
5
|
+
* commented out except `version` and `appName`; users uncomment the blocks
|
|
6
|
+
* they need. `--with-docker` includes the docker stanza uncommented as a
|
|
7
|
+
* jumping-off point for containerized workflows.
|
|
8
|
+
*/
|
|
9
|
+
import type { Profile } from '../core/types.js';
|
|
10
|
+
export interface InitConfigOptions {
|
|
11
|
+
/** Target directory for cockpit.yaml. Default: process.cwd(). */
|
|
12
|
+
cwd?: string;
|
|
13
|
+
/** Include an uncommented docker block. Default: false. */
|
|
14
|
+
withDocker?: boolean;
|
|
15
|
+
/** Overwrite without prompting. Default: false. */
|
|
16
|
+
force?: boolean;
|
|
17
|
+
/** Override the default app name baked into the template. */
|
|
18
|
+
appName?: string;
|
|
19
|
+
/** Run the interactive wizard instead of writing the static template. */
|
|
20
|
+
interactive?: boolean;
|
|
21
|
+
/** Active profile (passed by buildCli) — forwarded to the post-wizard doctor pass. */
|
|
22
|
+
profile?: Profile;
|
|
23
|
+
}
|
|
24
|
+
export declare function buildTemplate(opts: {
|
|
25
|
+
withDocker: boolean;
|
|
26
|
+
appName: string;
|
|
27
|
+
}): string;
|
|
28
|
+
export interface InitConfigResult {
|
|
29
|
+
/** Absolute path written. */
|
|
30
|
+
path: string;
|
|
31
|
+
/** Whether an existing file was overwritten. */
|
|
32
|
+
overwrote: boolean;
|
|
33
|
+
}
|
|
34
|
+
export declare function initConfigCommand(opts?: InitConfigOptions): Promise<InitConfigResult | null>;
|
|
35
|
+
//# sourceMappingURL=init-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init-config.d.ts","sourceRoot":"","sources":["../../src/commands/init-config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAIhD,MAAM,WAAW,iBAAiB;IAChC,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,mDAAmD;IACnD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,sFAAsF;IACtF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAoEpF;AAED,MAAM,WAAW,gBAAgB;IAC/B,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,wBAAsB,iBAAiB,CACrC,IAAI,GAAE,iBAAsB,GAC3B,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CA4DlC"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dev-cockpit init-config` — write a starter `cockpit.yaml`.
|
|
3
|
+
*
|
|
4
|
+
* The template is intentionally minimal and domain-neutral. Every section is
|
|
5
|
+
* commented out except `version` and `appName`; users uncomment the blocks
|
|
6
|
+
* they need. `--with-docker` includes the docker stanza uncommented as a
|
|
7
|
+
* jumping-off point for containerized workflows.
|
|
8
|
+
*/
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { renderWizardYaml, runInitWizard } from './init-config-wizard.js';
|
|
12
|
+
import { doctorCommand } from './doctor.js';
|
|
13
|
+
export function buildTemplate(opts) {
|
|
14
|
+
const dockerBlock = opts.withDocker
|
|
15
|
+
? `
|
|
16
|
+
# Docker services to tail in the Output pane. Remove this block on plain-Node
|
|
17
|
+
# projects.
|
|
18
|
+
docker:
|
|
19
|
+
composeFile: docker-compose.yml
|
|
20
|
+
services:
|
|
21
|
+
- name: app
|
|
22
|
+
tail: true
|
|
23
|
+
`
|
|
24
|
+
: `
|
|
25
|
+
# Docker services to tail in the Output pane. Uncomment for containerized projects.
|
|
26
|
+
# docker:
|
|
27
|
+
# composeFile: docker-compose.yml
|
|
28
|
+
# services:
|
|
29
|
+
# - name: app
|
|
30
|
+
# tail: true
|
|
31
|
+
`;
|
|
32
|
+
return `# dev-cockpit configuration. All keys except \`version\` and \`appName\` are
|
|
33
|
+
# optional; defaults from src/core/config.ts apply when missing.
|
|
34
|
+
|
|
35
|
+
version: 1
|
|
36
|
+
appName: ${opts.appName}
|
|
37
|
+
|
|
38
|
+
# Long-running watcher processes streamed into the Output pane.
|
|
39
|
+
# watchers:
|
|
40
|
+
# - id: build
|
|
41
|
+
# command: npm run watch
|
|
42
|
+
# color: cyan
|
|
43
|
+
|
|
44
|
+
# Repos surfaced in the Repos pane (linkable to watchers / lint).
|
|
45
|
+
# repos:
|
|
46
|
+
# - id: app
|
|
47
|
+
# path: .
|
|
48
|
+
# label: app
|
|
49
|
+
${dockerBlock}
|
|
50
|
+
# Output highlight patterns (regex) — matched lines render with elevated severity.
|
|
51
|
+
# highlights:
|
|
52
|
+
# - pattern: 'ERROR'
|
|
53
|
+
# severity: error
|
|
54
|
+
# - pattern: 'WARN'
|
|
55
|
+
# severity: warn
|
|
56
|
+
|
|
57
|
+
# Health checks. Built-in types: container-running, port-open, http-ok,
|
|
58
|
+
# file-exists, exec-zero. See docs/health.md for the full schema.
|
|
59
|
+
# health:
|
|
60
|
+
# - id: app-up
|
|
61
|
+
# label: app responsive
|
|
62
|
+
# type: http-ok
|
|
63
|
+
# url: http://localhost:3000/health
|
|
64
|
+
|
|
65
|
+
# Help-pane sources (markdown trees) and the landing page slug.
|
|
66
|
+
# help:
|
|
67
|
+
# sources: []
|
|
68
|
+
# defaultPage: getting-started
|
|
69
|
+
|
|
70
|
+
# Native OS notifications. Set enabled: false to mute entirely.
|
|
71
|
+
# notifications:
|
|
72
|
+
# enabled: true
|
|
73
|
+
# exclude: []
|
|
74
|
+
|
|
75
|
+
# Bind-mount overlays applied by \`dev-cockpit mount\`.
|
|
76
|
+
# mounts:
|
|
77
|
+
# - hostPath: ./packages/api
|
|
78
|
+
# containerPath: /srv/api
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
export async function initConfigCommand(opts = {}) {
|
|
82
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
83
|
+
const target = path.join(cwd, 'cockpit.yaml');
|
|
84
|
+
const exists = fs.existsSync(target);
|
|
85
|
+
if (exists && !opts.force) {
|
|
86
|
+
process.stdout.write(`dev-cockpit init-config: ${target} already exists.\n` +
|
|
87
|
+
` Pass --force to overwrite.\n`);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const content = opts.interactive
|
|
91
|
+
? renderWizardYaml(await runInitWizard({
|
|
92
|
+
defaultAppName: opts.appName ?? (path.basename(cwd) || 'my-cockpit'),
|
|
93
|
+
cwd,
|
|
94
|
+
}))
|
|
95
|
+
: buildTemplate({
|
|
96
|
+
withDocker: opts.withDocker ?? false,
|
|
97
|
+
appName: opts.appName ?? 'my-cockpit',
|
|
98
|
+
});
|
|
99
|
+
fs.writeFileSync(target, content, 'utf8');
|
|
100
|
+
process.stdout.write(`\ndev-cockpit init-config: wrote ${target}\n`);
|
|
101
|
+
if (opts.withDocker) {
|
|
102
|
+
process.stdout.write(' Docker block uncommented; adjust composeFile/services as needed.\n');
|
|
103
|
+
}
|
|
104
|
+
if (opts.interactive) {
|
|
105
|
+
// Offer to run doctor immediately so the user gets instant feedback that
|
|
106
|
+
// the config parses + every health check has a baseline state. Note for
|
|
107
|
+
// edits: re-run `dev-cockpit doctor` any time after hand-editing.
|
|
108
|
+
const { confirm } = (await import('@inquirer/prompts'));
|
|
109
|
+
const runDoctorNow = await confirm({
|
|
110
|
+
message: 'Run `dev-cockpit doctor` now to validate the file + show initial health?',
|
|
111
|
+
default: true,
|
|
112
|
+
});
|
|
113
|
+
if (runDoctorNow) {
|
|
114
|
+
process.stdout.write('\n');
|
|
115
|
+
try {
|
|
116
|
+
await doctorCommand({ config: target, profile: opts.profile });
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
process.stderr.write(`\ndoctor failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
process.stdout.write('\nNext:\n');
|
|
123
|
+
process.stdout.write(' • Edit cockpit.yaml any time, then re-run `dev-cockpit doctor`.\n');
|
|
124
|
+
process.stdout.write(' • Run `dev-cockpit dev` to launch the cockpit (Tab/arrows cycle panes, q quits).\n');
|
|
125
|
+
process.stdout.write(' • The Help tab inside the cockpit has the full docs.\n');
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
process.stdout.write(' Run `dev-cockpit doctor` to verify the config loads cleanly.\n');
|
|
129
|
+
}
|
|
130
|
+
return { path: target, overwrote: exists };
|
|
131
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dev-cockpit mount` — generic bind-mount overlay manager.
|
|
3
|
+
*
|
|
4
|
+
* Collects mount candidates from two sources and writes a Docker compose
|
|
5
|
+
* override file plus a manifest:
|
|
6
|
+
* 1. `config.mounts[]` — explicit, declared in cockpit.yaml
|
|
7
|
+
* 2. `profile.mountCandidatesProvider?.()` — discovered programmatically
|
|
8
|
+
*
|
|
9
|
+
* The merged set is keyed by `containerPath`; if both sources name the same
|
|
10
|
+
* container path, the config entry wins (explicit > discovered). The
|
|
11
|
+
* compose override targets a single configurable service (default: first
|
|
12
|
+
* service in `config.docker.services`).
|
|
13
|
+
*
|
|
14
|
+
* Subcommands:
|
|
15
|
+
* mount — write/refresh the overlay and manifest
|
|
16
|
+
* mount status — list active mounts from the manifest
|
|
17
|
+
* mount clear — remove overlay and manifest
|
|
18
|
+
*/
|
|
19
|
+
import type { Mount, Profile } from '../core/types.js';
|
|
20
|
+
export interface MountCommandOptions {
|
|
21
|
+
config?: string;
|
|
22
|
+
profile?: Profile;
|
|
23
|
+
/** Container service name to attach the bind mounts to. */
|
|
24
|
+
service?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface MountManifest {
|
|
27
|
+
appName: string;
|
|
28
|
+
workspaceRoot: string;
|
|
29
|
+
service: string;
|
|
30
|
+
overlayPath: string;
|
|
31
|
+
mounts: Mount[];
|
|
32
|
+
/** ISO timestamp of last apply. */
|
|
33
|
+
appliedAt: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Merge mount sources, keyed on containerPath. Config entries win over
|
|
37
|
+
* provider-discovered ones for the same container path.
|
|
38
|
+
*/
|
|
39
|
+
export declare function mergeMounts(configMounts: readonly Mount[], providerMounts: readonly Mount[]): Mount[];
|
|
40
|
+
/**
|
|
41
|
+
* Render a Docker compose override YAML for the given service + mounts.
|
|
42
|
+
* Hand-rolled (no `yaml` writer) so the output stays stable + diff-friendly.
|
|
43
|
+
*/
|
|
44
|
+
export declare function renderOverlay(service: string, mounts: readonly Mount[]): string;
|
|
45
|
+
export declare function mountCommand(opts?: MountCommandOptions): Promise<void>;
|
|
46
|
+
export declare function mountStatusCommand(opts?: MountCommandOptions): Promise<void>;
|
|
47
|
+
export declare function mountClearCommand(opts?: MountCommandOptions): Promise<void>;
|
|
48
|
+
//# sourceMappingURL=mount.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../../src/commands/mount.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAIvD,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,YAAY,EAAE,SAAS,KAAK,EAAE,EAC9B,cAAc,EAAE,SAAS,KAAK,EAAE,GAC/B,KAAK,EAAE,CAKT;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,KAAK,EAAE,GAAG,MAAM,CAe/E;AAkDD,wBAAsB,YAAY,CAAC,IAAI,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgChF;AAED,wBAAsB,kBAAkB,CAAC,IAAI,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBtF;AAED,wBAAsB,iBAAiB,CAAC,IAAI,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBrF"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dev-cockpit mount` — generic bind-mount overlay manager.
|
|
3
|
+
*
|
|
4
|
+
* Collects mount candidates from two sources and writes a Docker compose
|
|
5
|
+
* override file plus a manifest:
|
|
6
|
+
* 1. `config.mounts[]` — explicit, declared in cockpit.yaml
|
|
7
|
+
* 2. `profile.mountCandidatesProvider?.()` — discovered programmatically
|
|
8
|
+
*
|
|
9
|
+
* The merged set is keyed by `containerPath`; if both sources name the same
|
|
10
|
+
* container path, the config entry wins (explicit > discovered). The
|
|
11
|
+
* compose override targets a single configurable service (default: first
|
|
12
|
+
* service in `config.docker.services`).
|
|
13
|
+
*
|
|
14
|
+
* Subcommands:
|
|
15
|
+
* mount — write/refresh the overlay and manifest
|
|
16
|
+
* mount status — list active mounts from the manifest
|
|
17
|
+
* mount clear — remove overlay and manifest
|
|
18
|
+
*/
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import fs from 'node:fs';
|
|
21
|
+
import { loadConfig } from '../core/config.js';
|
|
22
|
+
import { getStatePaths } from '../core/paths.js';
|
|
23
|
+
/**
|
|
24
|
+
* Merge mount sources, keyed on containerPath. Config entries win over
|
|
25
|
+
* provider-discovered ones for the same container path.
|
|
26
|
+
*/
|
|
27
|
+
export function mergeMounts(configMounts, providerMounts) {
|
|
28
|
+
const seen = new Map();
|
|
29
|
+
for (const m of providerMounts)
|
|
30
|
+
seen.set(m.containerPath, m);
|
|
31
|
+
for (const m of configMounts)
|
|
32
|
+
seen.set(m.containerPath, m);
|
|
33
|
+
return Array.from(seen.values());
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Render a Docker compose override YAML for the given service + mounts.
|
|
37
|
+
* Hand-rolled (no `yaml` writer) so the output stays stable + diff-friendly.
|
|
38
|
+
*/
|
|
39
|
+
export function renderOverlay(service, mounts) {
|
|
40
|
+
if (mounts.length === 0) {
|
|
41
|
+
return `# dev-cockpit: no mounts to apply\nservices: {}\n`;
|
|
42
|
+
}
|
|
43
|
+
const lines = [
|
|
44
|
+
'# Generated by `dev-cockpit mount`. Do not edit by hand — re-run the command.',
|
|
45
|
+
'services:',
|
|
46
|
+
` ${service}:`,
|
|
47
|
+
' volumes:',
|
|
48
|
+
];
|
|
49
|
+
for (const m of mounts) {
|
|
50
|
+
lines.push(` - ${m.hostPath}:${m.containerPath}`);
|
|
51
|
+
}
|
|
52
|
+
lines.push('');
|
|
53
|
+
return lines.join('\n');
|
|
54
|
+
}
|
|
55
|
+
function resolveOverlayPath(workspaceRoot) {
|
|
56
|
+
return path.join(workspaceRoot, 'docker-compose.dev-cockpit.yml');
|
|
57
|
+
}
|
|
58
|
+
function manifestPath(workspaceRoot, appName) {
|
|
59
|
+
const paths = getStatePaths(workspaceRoot, { appName });
|
|
60
|
+
return path.join(paths.stateDir, 'mount.manifest.json');
|
|
61
|
+
}
|
|
62
|
+
function loadCommandConfig(opts) {
|
|
63
|
+
const profile = opts.profile;
|
|
64
|
+
const configPath = path.resolve(opts.config ?? 'cockpit.yaml');
|
|
65
|
+
if (!fs.existsSync(configPath)) {
|
|
66
|
+
process.stderr.write(`dev-cockpit mount: no cockpit.yaml found at ${configPath}.\n`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
const config = loadConfig(configPath, {
|
|
70
|
+
configSchemaExt: profile?.configSchemaExt,
|
|
71
|
+
profileKey: profile?.appName,
|
|
72
|
+
});
|
|
73
|
+
const workspaceRoot = profile?.discoverer?.()?.root ?? path.dirname(configPath);
|
|
74
|
+
const explicitService = opts.service ?? config.docker?.services?.[0]?.name;
|
|
75
|
+
if (!explicitService) {
|
|
76
|
+
process.stderr.write('dev-cockpit mount: no target service. Pass --service <name> or set docker.services in cockpit.yaml.\n');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
const providerMounts = profile?.mountCandidatesProvider?.() ?? [];
|
|
80
|
+
return {
|
|
81
|
+
configPath,
|
|
82
|
+
workspaceRoot,
|
|
83
|
+
appName: config.appName,
|
|
84
|
+
service: explicitService,
|
|
85
|
+
configMounts: config.mounts,
|
|
86
|
+
providerMounts,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export async function mountCommand(opts = {}) {
|
|
90
|
+
const { workspaceRoot, appName, service, configMounts, providerMounts } = loadCommandConfig(opts);
|
|
91
|
+
const merged = mergeMounts(configMounts, providerMounts);
|
|
92
|
+
if (merged.length === 0) {
|
|
93
|
+
process.stdout.write('dev-cockpit mount: no mount candidates from config.mounts[] or profile.mountCandidatesProvider.\n');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const overlayPath = resolveOverlayPath(workspaceRoot);
|
|
97
|
+
fs.writeFileSync(overlayPath, renderOverlay(service, merged), 'utf8');
|
|
98
|
+
const manifest = {
|
|
99
|
+
appName,
|
|
100
|
+
workspaceRoot,
|
|
101
|
+
service,
|
|
102
|
+
overlayPath,
|
|
103
|
+
mounts: merged,
|
|
104
|
+
appliedAt: new Date().toISOString(),
|
|
105
|
+
};
|
|
106
|
+
fs.writeFileSync(manifestPath(workspaceRoot, appName), JSON.stringify(manifest, null, 2), 'utf8');
|
|
107
|
+
process.stdout.write(`dev-cockpit mount: wrote ${overlayPath}\n`);
|
|
108
|
+
for (const m of merged) {
|
|
109
|
+
process.stdout.write(` ${m.hostPath} → ${service}:${m.containerPath}\n`);
|
|
110
|
+
}
|
|
111
|
+
process.stdout.write(` Apply with: docker compose -f <your-compose>.yml -f ${path.basename(overlayPath)} up -d\n`);
|
|
112
|
+
}
|
|
113
|
+
export async function mountStatusCommand(opts = {}) {
|
|
114
|
+
const { workspaceRoot, appName } = loadCommandConfig(opts);
|
|
115
|
+
const target = manifestPath(workspaceRoot, appName);
|
|
116
|
+
if (!fs.existsSync(target)) {
|
|
117
|
+
process.stdout.write('dev-cockpit mount: no active overlay (no manifest found).\n');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const manifest = JSON.parse(fs.readFileSync(target, 'utf8'));
|
|
121
|
+
process.stdout.write(`dev-cockpit mount status — service: ${manifest.service}\n`);
|
|
122
|
+
process.stdout.write(` overlay: ${manifest.overlayPath}\n`);
|
|
123
|
+
process.stdout.write(` applied: ${manifest.appliedAt}\n`);
|
|
124
|
+
if (manifest.mounts.length === 0) {
|
|
125
|
+
process.stdout.write(' (no mounts)\n');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
for (const m of manifest.mounts) {
|
|
129
|
+
process.stdout.write(` ${m.hostPath} → ${m.containerPath}\n`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
export async function mountClearCommand(opts = {}) {
|
|
133
|
+
const { workspaceRoot, appName } = loadCommandConfig(opts);
|
|
134
|
+
const overlayPath = resolveOverlayPath(workspaceRoot);
|
|
135
|
+
const target = manifestPath(workspaceRoot, appName);
|
|
136
|
+
let removed = 0;
|
|
137
|
+
if (fs.existsSync(overlayPath)) {
|
|
138
|
+
fs.rmSync(overlayPath);
|
|
139
|
+
removed += 1;
|
|
140
|
+
process.stdout.write(`dev-cockpit mount: removed ${overlayPath}\n`);
|
|
141
|
+
}
|
|
142
|
+
if (fs.existsSync(target)) {
|
|
143
|
+
fs.rmSync(target);
|
|
144
|
+
removed += 1;
|
|
145
|
+
process.stdout.write(`dev-cockpit mount: removed ${target}\n`);
|
|
146
|
+
}
|
|
147
|
+
if (removed === 0) {
|
|
148
|
+
process.stdout.write('dev-cockpit mount: nothing to clear.\n');
|
|
149
|
+
}
|
|
150
|
+
}
|