@localctl/cli 0.2.0 → 0.4.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.
Files changed (52) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +398 -431
  3. package/assets/manifests/namespace-addons.yaml +4 -4
  4. package/assets/manifests/tls-store.yaml +8 -8
  5. package/bin/localctl.js +2 -2
  6. package/package.json +61 -54
  7. package/src/commands/addons.js +56 -56
  8. package/src/commands/doctor.js +44 -43
  9. package/src/commands/down.js +35 -35
  10. package/src/commands/exec.js +15 -15
  11. package/src/commands/generateManifests.js +12 -12
  12. package/src/commands/hosts.js +23 -23
  13. package/src/commands/logs.js +45 -45
  14. package/src/commands/profiles.js +180 -180
  15. package/src/commands/prune.js +49 -49
  16. package/src/commands/registryInfo.js +18 -18
  17. package/src/commands/reload.js +44 -44
  18. package/src/commands/secrets.js +75 -75
  19. package/src/commands/setup.js +36 -36
  20. package/src/commands/status.js +136 -136
  21. package/src/commands/up.js +123 -123
  22. package/src/index.js +38 -36
  23. package/src/lib/addonDirs.js +43 -43
  24. package/src/lib/addonLoader.js +190 -190
  25. package/src/lib/addons/cluster/logging/addon.yaml +46 -46
  26. package/src/lib/addons/cluster/logging/config/all-logs.json +21 -21
  27. package/src/lib/addons/cluster/monitoring/addon.yaml +33 -33
  28. package/src/lib/addons/mongo/addon.yaml +18 -18
  29. package/src/lib/addons/postgres/addon.yaml +39 -39
  30. package/src/lib/addons/redis/addon.yaml +29 -29
  31. package/src/lib/appSecrets.js +60 -60
  32. package/src/lib/clusterAddons.js +59 -59
  33. package/src/lib/clusterProvision.js +171 -170
  34. package/src/lib/configSchema.js +103 -103
  35. package/src/lib/constants.js +16 -16
  36. package/src/lib/engine.js +36 -20
  37. package/src/lib/exec.js +77 -77
  38. package/src/lib/hostsFile.js +79 -79
  39. package/src/lib/loadConfig.js +51 -51
  40. package/src/lib/logger.js +9 -9
  41. package/src/lib/manifestGen.js +210 -210
  42. package/src/lib/paths.js +41 -41
  43. package/src/lib/podman.js +72 -72
  44. package/src/lib/profileStore.js +134 -134
  45. package/src/lib/projectContext.js +32 -32
  46. package/src/lib/prompt.js +97 -97
  47. package/src/lib/toolInstall.js +30 -30
  48. package/src/lib/ui.js +50 -50
  49. package/src/lib/updateCheck.js +84 -0
  50. package/src/lib/updateCheckWorker.js +46 -0
  51. package/src/lib/workloadReadiness.js +35 -35
  52. package/templates/Tiltfile.tmpl +35 -35
@@ -1,4 +1,4 @@
1
- apiVersion: v1
2
- kind: Namespace
3
- metadata:
4
- name: observability
1
+ apiVersion: v1
2
+ kind: Namespace
3
+ metadata:
4
+ name: observability
@@ -1,8 +1,8 @@
1
- apiVersion: traefik.io/v1alpha1
2
- kind: TLSStore
3
- metadata:
4
- name: default
5
- namespace: kube-system
6
- spec:
7
- defaultCertificate:
8
- secretName: local-dev-tls
1
+ apiVersion: traefik.io/v1alpha1
2
+ kind: TLSStore
3
+ metadata:
4
+ name: default
5
+ namespace: kube-system
6
+ spec:
7
+ defaultCertificate:
8
+ secretName: local-dev-tls
package/bin/localctl.js CHANGED
@@ -1,2 +1,2 @@
1
- #!/usr/bin/env node
2
- require('../src/index.js');
1
+ #!/usr/bin/env node
2
+ require('../src/index.js');
package/package.json CHANGED
@@ -1,54 +1,61 @@
1
- {
2
- "name": "@localctl/cli",
3
- "version": "0.2.0",
4
- "description": "localctl - local Kubernetes dev platform CLI",
5
- "keywords": [
6
- "kubernetes",
7
- "k3d",
8
- "tilt",
9
- "local-development",
10
- "cli",
11
- "devtools"
12
- ],
13
- "homepage": "https://psilvmoreira.github.io/localctl/",
14
- "bugs": {
15
- "url": "https://github.com/psilvmoreira/localctl/issues"
16
- },
17
- "repository": {
18
- "type": "git",
19
- "url": "git+https://github.com/psilvmoreira/localctl.git",
20
- "directory": "cli"
21
- },
22
- "author": "Pedro Moreira",
23
- "license": "MIT",
24
- "bin": {
25
- "localctl": "bin/localctl.js"
26
- },
27
- "main": "src/index.js",
28
- "files": [
29
- "bin/",
30
- "src/",
31
- "templates/",
32
- "assets/",
33
- "schema/"
34
- ],
35
- "scripts": {
36
- "sync-assets": "node scripts/sync-assets.js",
37
- "prepack": "node scripts/sync-assets.js",
38
- "test": "node scripts/smoke-test.js",
39
- "test:pack": "node scripts/pack-test.js"
40
- },
41
- "dependencies": {
42
- "chalk": "^4.1.2",
43
- "commander": "^11.1.0",
44
- "js-yaml": "^4.1.0",
45
- "zod": "^3.23.8"
46
- },
47
- "engines": {
48
- "node": ">=18"
49
- },
50
- "publishConfig": {
51
- "access": "public",
52
- "provenance": true
53
- }
54
- }
1
+ {
2
+ "name": "@localctl/cli",
3
+ "version": "0.4.0",
4
+ "description": "localctl - local Kubernetes dev platform CLI",
5
+ "keywords": [
6
+ "kubernetes",
7
+ "k3d",
8
+ "tilt",
9
+ "local-development",
10
+ "cli",
11
+ "devtools"
12
+ ],
13
+ "homepage": "https://psilvmoreira.github.io/localctl/",
14
+ "bugs": {
15
+ "url": "https://github.com/psilvmoreira/localctl/issues"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/psilvmoreira/localctl.git",
20
+ "directory": "cli"
21
+ },
22
+ "author": "Pedro Moreira",
23
+ "license": "MIT",
24
+ "bin": {
25
+ "localctl": "bin/localctl.js"
26
+ },
27
+ "main": "src/index.js",
28
+ "files": [
29
+ "bin/",
30
+ "src/",
31
+ "templates/",
32
+ "assets/",
33
+ "schema/"
34
+ ],
35
+ "scripts": {
36
+ "sync-assets": "node scripts/sync-assets.js",
37
+ "prepack": "node scripts/sync-assets.js",
38
+ "test": "node scripts/smoke-test.js",
39
+ "test:pack": "node scripts/pack-test.js"
40
+ },
41
+ "dependencies": {
42
+ "chalk": "^4.1.2",
43
+ "commander": "^11.1.0",
44
+ "js-yaml": "^4.1.0",
45
+ "zod": "^3.23.8"
46
+ },
47
+ "engines": {
48
+ "node": ">=18"
49
+ },
50
+ "localctl": {
51
+ "containerEngines": {
52
+ "podman": "tested",
53
+ "docker": "experimental",
54
+ "rancher-desktop": "experimental"
55
+ }
56
+ },
57
+ "publishConfig": {
58
+ "access": "public",
59
+ "provenance": true
60
+ }
61
+ }
@@ -1,56 +1,56 @@
1
- const { listAddonNames } = require('../lib/addonDirs');
2
- const { enableClusterAddon, disableClusterAddon, clusterAddonInstances } = require('../lib/clusterAddons');
3
- const { ensureClusterUp } = require('../lib/clusterProvision');
4
- const { getActive, clusterNameFor } = require('../lib/profileStore');
5
- const ui = require('../lib/ui');
6
- const logger = require('../lib/logger');
7
-
8
- // Cluster-wide addons apply to whichever project is currently active (see "localctl profiles
9
- // switch") - there's no per-app directory to resolve one from, unlike `app` commands.
10
- function ensureActiveClusterUp() {
11
- const active = getActive();
12
- ensureClusterUp(clusterNameFor(active), active);
13
- }
14
-
15
- module.exports = (program) => {
16
- const addons = program.command('addons').description('Manage cluster-wide addons (logging, etc.) in the active project');
17
-
18
- addons
19
- .command('list')
20
- .description('List available cluster-wide addons')
21
- .action(() => {
22
- listAddonNames('cluster').forEach((n) => console.log(n));
23
- });
24
-
25
- addons
26
- .command('enable <name>')
27
- .description('Enable a cluster-wide addon')
28
- .action((name) => {
29
- ensureActiveClusterUp();
30
- enableClusterAddon(name);
31
- logger.success(`Addon "${name}" applied.`);
32
- });
33
-
34
- addons
35
- .command('disable <name>')
36
- .description('Remove a cluster-wide addon')
37
- .action((name) => {
38
- ensureActiveClusterUp();
39
- disableClusterAddon(name);
40
- logger.success(`Addon "${name}" removed.`);
41
- });
42
-
43
- addons
44
- .command('status')
45
- .description('Show every addon instance in the cluster - infrastructure helpers, separate from your apps')
46
- .action(() => {
47
- ensureActiveClusterUp();
48
- const records = clusterAddonInstances();
49
- if (records.length === 0) {
50
- logger.info('No addon instances found in the cluster.');
51
- return;
52
- }
53
- const rows = records.map((r) => [r.release, r.owner || ui.dim('(cluster-wide)'), r.namespace, r.chart, r.ready]);
54
- ui.printTable(['ADDON', 'OWNER', 'NAMESPACE', 'CHART', 'READY'], rows);
55
- });
56
- };
1
+ const { listAddonNames } = require('../lib/addonDirs');
2
+ const { enableClusterAddon, disableClusterAddon, clusterAddonInstances } = require('../lib/clusterAddons');
3
+ const { ensureClusterUp } = require('../lib/clusterProvision');
4
+ const { getActive, clusterNameFor } = require('../lib/profileStore');
5
+ const ui = require('../lib/ui');
6
+ const logger = require('../lib/logger');
7
+
8
+ // Cluster-wide addons apply to whichever project is currently active (see "localctl profiles
9
+ // switch") - there's no per-app directory to resolve one from, unlike `app` commands.
10
+ function ensureActiveClusterUp() {
11
+ const active = getActive();
12
+ ensureClusterUp(clusterNameFor(active), active);
13
+ }
14
+
15
+ module.exports = (program) => {
16
+ const addons = program.command('addons').description('Manage cluster-wide addons (logging, etc.) in the active project');
17
+
18
+ addons
19
+ .command('list')
20
+ .description('List available cluster-wide addons')
21
+ .action(() => {
22
+ listAddonNames('cluster').forEach((n) => console.log(n));
23
+ });
24
+
25
+ addons
26
+ .command('enable <name>')
27
+ .description('Enable a cluster-wide addon')
28
+ .action((name) => {
29
+ ensureActiveClusterUp();
30
+ enableClusterAddon(name);
31
+ logger.success(`Addon "${name}" applied.`);
32
+ });
33
+
34
+ addons
35
+ .command('disable <name>')
36
+ .description('Remove a cluster-wide addon')
37
+ .action((name) => {
38
+ ensureActiveClusterUp();
39
+ disableClusterAddon(name);
40
+ logger.success(`Addon "${name}" removed.`);
41
+ });
42
+
43
+ addons
44
+ .command('status')
45
+ .description('Show every addon instance in the cluster - infrastructure helpers, separate from your apps')
46
+ .action(() => {
47
+ ensureActiveClusterUp();
48
+ const records = clusterAddonInstances();
49
+ if (records.length === 0) {
50
+ logger.info('No addon instances found in the cluster.');
51
+ return;
52
+ }
53
+ const rows = records.map((r) => [r.release, r.owner || ui.dim('(cluster-wide)'), r.namespace, r.chart, r.ready]);
54
+ ui.printTable(['ADDON', 'OWNER', 'NAMESPACE', 'CHART', 'READY'], rows);
55
+ });
56
+ };
@@ -1,43 +1,44 @@
1
- const { commandExists, capture } = require('../lib/exec');
2
- const { detectEngine } = require('../lib/engine');
3
- const { getActive, clusterNameFor } = require('../lib/profileStore');
4
- const ui = require('../lib/ui');
5
- const logger = require('../lib/logger');
6
-
7
- function check(label, fn) {
8
- try {
9
- const passed = fn();
10
- console.log(` ${passed ? ui.check : ui.cross} ${label}`);
11
- return passed;
12
- } catch (e) {
13
- console.log(` ${ui.cross} ${label} - ${ui.dim(e.message)}`);
14
- return false;
15
- }
16
- }
17
-
18
- module.exports = (program) => {
19
- program
20
- .command('doctor')
21
- .description('Check the local environment for missing/broken pieces')
22
- .action(() => {
23
- logger.step('Checking environment...');
24
- const detected = detectEngine();
25
- check(`container engine available${detected ? ` (${detected.engine})` : ''}`, () => Boolean(detected));
26
- if (detected && detected.engine === 'podman') {
27
- logger.info('Podman detected: "localctl app up" will disable BuildKit automatically for image builds.');
28
- }
29
- check('k3d installed', () => commandExists('k3d'));
30
- check('kubectl installed', () => commandExists('kubectl'));
31
- check('tilt installed', () => commandExists('tilt'));
32
- check('mkcert installed', () => commandExists('mkcert'));
33
- check('helm installed', () => commandExists('helm'));
34
- const active = getActive();
35
- const activeCluster = clusterNameFor(active);
36
- check(`active project's cluster exists ("${active}" -> ${activeCluster})`, () => {
37
- const res = capture('k3d', ['cluster', 'list']);
38
- return res.stdout.split('\n').some((line) => line.trim().split(/\s+/)[0] === activeCluster);
39
- });
40
- check('kubectl context reachable', () => capture('kubectl', ['cluster-info']).status === 0);
41
- logger.step(`Fix any ${ui.cross} above, or run "localctl setup" (or "localctl profiles switch ${active}" for a non-default project) to (re)install/repair.`);
42
- });
43
- };
1
+ const { commandExists, capture } = require('../lib/exec');
2
+ const { detectEngine, warnIfExperimental } = require('../lib/engine');
3
+ const { getActive, clusterNameFor } = require('../lib/profileStore');
4
+ const ui = require('../lib/ui');
5
+ const logger = require('../lib/logger');
6
+
7
+ function check(label, fn) {
8
+ try {
9
+ const passed = fn();
10
+ console.log(` ${passed ? ui.check : ui.cross} ${label}`);
11
+ return passed;
12
+ } catch (e) {
13
+ console.log(` ${ui.cross} ${label} - ${ui.dim(e.message)}`);
14
+ return false;
15
+ }
16
+ }
17
+
18
+ module.exports = (program) => {
19
+ program
20
+ .command('doctor')
21
+ .description('Check the local environment for missing/broken pieces')
22
+ .action(() => {
23
+ logger.step('Checking environment...');
24
+ const detected = detectEngine();
25
+ check(`container engine available${detected ? ` (${detected.engine})` : ''}`, () => Boolean(detected));
26
+ if (detected) warnIfExperimental(detected.engine);
27
+ if (detected && detected.engine === 'podman') {
28
+ logger.info('Podman detected: "localctl app up" will disable BuildKit automatically for image builds.');
29
+ }
30
+ check('k3d installed', () => commandExists('k3d'));
31
+ check('kubectl installed', () => commandExists('kubectl'));
32
+ check('tilt installed', () => commandExists('tilt'));
33
+ check('mkcert installed', () => commandExists('mkcert'));
34
+ check('helm installed', () => commandExists('helm'));
35
+ const active = getActive();
36
+ const activeCluster = clusterNameFor(active);
37
+ check(`active project's cluster exists ("${active}" -> ${activeCluster})`, () => {
38
+ const res = capture('k3d', ['cluster', 'list']);
39
+ return res.stdout.split('\n').some((line) => line.trim().split(/\s+/)[0] === activeCluster);
40
+ });
41
+ check('kubectl context reachable', () => capture('kubectl', ['cluster-info']).status === 0);
42
+ logger.step(`Fix any ${ui.cross} above, or run "localctl setup" (or "localctl profiles switch ${active}" for a non-default project) to (re)install/repair.`);
43
+ });
44
+ };
@@ -1,35 +1,35 @@
1
- const fs = require('fs');
2
- const { loadConfig } = require('../lib/loadConfig');
3
- const { run, isRunning, sleepSync } = require('../lib/exec');
4
- const { ensureClusterUp } = require('../lib/clusterProvision');
5
- const { runFiles } = require('../lib/paths');
6
- const logger = require('../lib/logger');
7
-
8
- module.exports = (program) => {
9
- program
10
- .command('down')
11
- .description('Tear down the app deployed from the current directory (runs "tilt down")')
12
- .action(() => {
13
- const cfg = loadConfig();
14
- ensureClusterUp(cfg.clusterName, cfg.profile);
15
- const { pidFile, portFile, logFile } = runFiles(cfg.name);
16
-
17
- // If `localctl up` started a detached Tilt process for this app, it needs to be stopped
18
- // first - otherwise it keeps live-syncing/reconciling against resources `tilt down` is
19
- // about to delete out from under it.
20
- if (fs.existsSync(pidFile)) {
21
- const pid = parseInt(fs.readFileSync(pidFile, 'utf8'), 10);
22
- if (Number.isFinite(pid) && isRunning(pid)) {
23
- logger.step(`Stopping background Tilt process (pid ${pid})...`);
24
- process.kill(pid, 'SIGTERM');
25
- for (let i = 0; i < 20 && isRunning(pid); i += 1) sleepSync(250);
26
- }
27
- fs.rmSync(pidFile, { force: true });
28
- }
29
- fs.rmSync(portFile, { force: true });
30
- fs.rmSync(logFile, { force: true });
31
-
32
- run('tilt', ['down']);
33
- logger.success(`"${cfg.name}" torn down. Run "localctl hosts sync" if you want to clean up its subdomain too.`);
34
- });
35
- };
1
+ const fs = require('fs');
2
+ const { loadConfig } = require('../lib/loadConfig');
3
+ const { run, isRunning, sleepSync } = require('../lib/exec');
4
+ const { ensureClusterUp } = require('../lib/clusterProvision');
5
+ const { runFiles } = require('../lib/paths');
6
+ const logger = require('../lib/logger');
7
+
8
+ module.exports = (program) => {
9
+ program
10
+ .command('down')
11
+ .description('Tear down the app deployed from the current directory (runs "tilt down")')
12
+ .action(() => {
13
+ const cfg = loadConfig();
14
+ ensureClusterUp(cfg.clusterName, cfg.profile);
15
+ const { pidFile, portFile, logFile } = runFiles(cfg.name);
16
+
17
+ // If `localctl up` started a detached Tilt process for this app, it needs to be stopped
18
+ // first - otherwise it keeps live-syncing/reconciling against resources `tilt down` is
19
+ // about to delete out from under it.
20
+ if (fs.existsSync(pidFile)) {
21
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf8'), 10);
22
+ if (Number.isFinite(pid) && isRunning(pid)) {
23
+ logger.step(`Stopping background Tilt process (pid ${pid})...`);
24
+ process.kill(pid, 'SIGTERM');
25
+ for (let i = 0; i < 20 && isRunning(pid); i += 1) sleepSync(250);
26
+ }
27
+ fs.rmSync(pidFile, { force: true });
28
+ }
29
+ fs.rmSync(portFile, { force: true });
30
+ fs.rmSync(logFile, { force: true });
31
+
32
+ run('tilt', ['down']);
33
+ logger.success(`"${cfg.name}" torn down. Run "localctl hosts sync" if you want to clean up its subdomain too.`);
34
+ });
35
+ };
@@ -1,15 +1,15 @@
1
- const { loadConfig } = require('../lib/loadConfig');
2
- const { ensureClusterUp } = require('../lib/clusterProvision');
3
- const { run } = require('../lib/exec');
4
-
5
- module.exports = (program) => {
6
- program
7
- .command('exec [cmd...]')
8
- .description('Shell into the running app\'s pod (kubectl exec) - defaults to "sh" if no command is given')
9
- .action((cmd) => {
10
- const cfg = loadConfig();
11
- ensureClusterUp(cfg.clusterName, cfg.profile);
12
- const command = cmd && cmd.length ? cmd : ['sh'];
13
- run('kubectl', ['exec', '-it', `deploy/${cfg.name}`, '-n', cfg.namespace, '--', ...command]);
14
- });
15
- };
1
+ const { loadConfig } = require('../lib/loadConfig');
2
+ const { ensureClusterUp } = require('../lib/clusterProvision');
3
+ const { run } = require('../lib/exec');
4
+
5
+ module.exports = (program) => {
6
+ program
7
+ .command('exec [cmd...]')
8
+ .description('Shell into the running app\'s pod (kubectl exec) - defaults to "sh" if no command is given')
9
+ .action((cmd) => {
10
+ const cfg = loadConfig();
11
+ ensureClusterUp(cfg.clusterName, cfg.profile);
12
+ const command = cmd && cmd.length ? cmd : ['sh'];
13
+ run('kubectl', ['exec', '-it', `deploy/${cfg.name}`, '-n', cfg.namespace, '--', ...command]);
14
+ });
15
+ };
@@ -1,12 +1,12 @@
1
- const { loadConfig } = require('../lib/loadConfig');
2
- const { generateManifests } = require('../lib/manifestGen');
3
-
4
- module.exports = (program) => {
5
- program
6
- .command('generate-manifests')
7
- .description('[internal, called by Tiltfile] Render k8s manifests for the app in the current directory to stdout')
8
- .action(() => {
9
- const cfg = loadConfig();
10
- process.stdout.write(generateManifests(cfg));
11
- });
12
- };
1
+ const { loadConfig } = require('../lib/loadConfig');
2
+ const { generateManifests } = require('../lib/manifestGen');
3
+
4
+ module.exports = (program) => {
5
+ program
6
+ .command('generate-manifests')
7
+ .description('[internal, called by Tiltfile] Render k8s manifests for the app in the current directory to stdout')
8
+ .action(() => {
9
+ const cfg = loadConfig();
10
+ process.stdout.write(generateManifests(cfg));
11
+ });
12
+ };
@@ -1,23 +1,23 @@
1
- const { syncHosts, clearHosts, listApps } = require('../lib/hostsFile');
2
- const { DOMAIN } = require('../lib/constants');
3
-
4
- module.exports = (program) => {
5
- const hosts = program.command('hosts').description(`Manage local hosts file entries for *.${DOMAIN} apps`);
6
-
7
- hosts
8
- .command('sync')
9
- .description('Sync the hosts file with all registered app subdomains')
10
- .action(() => syncHosts());
11
-
12
- hosts
13
- .command('list')
14
- .description('List registered subdomains')
15
- .action(() => {
16
- listApps().forEach((s) => console.log(`${s}.${DOMAIN}`));
17
- });
18
-
19
- hosts
20
- .command('clear')
21
- .description(`[used by uninstall] Remove the entire managed *.${DOMAIN} block from the hosts file`)
22
- .action(() => clearHosts());
23
- };
1
+ const { syncHosts, clearHosts, listApps } = require('../lib/hostsFile');
2
+ const { DOMAIN } = require('../lib/constants');
3
+
4
+ module.exports = (program) => {
5
+ const hosts = program.command('hosts').description(`Manage local hosts file entries for *.${DOMAIN} apps`);
6
+
7
+ hosts
8
+ .command('sync')
9
+ .description('Sync the hosts file with all registered app subdomains')
10
+ .action(() => syncHosts());
11
+
12
+ hosts
13
+ .command('list')
14
+ .description('List registered subdomains')
15
+ .action(() => {
16
+ listApps().forEach((s) => console.log(`${s}.${DOMAIN}`));
17
+ });
18
+
19
+ hosts
20
+ .command('clear')
21
+ .description(`[used by uninstall] Remove the entire managed *.${DOMAIN} block from the hosts file`)
22
+ .action(() => clearHosts());
23
+ };
@@ -1,45 +1,45 @@
1
- const fs = require('fs');
2
- const { run } = require('../lib/exec');
3
- const { loadConfig } = require('../lib/loadConfig');
4
- const { ensureClusterUp } = require('../lib/clusterProvision');
5
- const { getActive, clusterNameFor } = require('../lib/profileStore');
6
- const { CONFIG_PATH } = require('../lib/paths');
7
-
8
- module.exports = (program) => {
9
- program
10
- .command('logs [name]')
11
- .option('-n, --namespace <ns>', 'namespace override (only needed outside an app\'s own repo, or if it disagrees with what "localctl app status" shows)')
12
- .option('-f, --follow', 'follow log output')
13
- .description('Tail logs for a deployed app - run inside the app\'s repo to resolve it (and its project\'s namespace prefix) automatically, or pass a name')
14
- .action((name, opts) => {
15
- let targetName = name;
16
- let namespace = opts.namespace;
17
-
18
- if (!namespace) {
19
- if (!targetName && fs.existsSync(CONFIG_PATH)) {
20
- // Directory-resolved, same as `app status`/`up`/`down` - this is what a namespace-scoped
21
- // project needs: its real namespace is "<scope>-<name>", not the bare app name a plain
22
- // `-n <name>` default would guess.
23
- const cfg = loadConfig();
24
- ensureClusterUp(cfg.clusterName, cfg.profile);
25
- targetName = cfg.name;
26
- namespace = cfg.namespace;
27
- } else if (targetName) {
28
- // An explicit name from outside its own repo - no directory to resolve from, so this
29
- // stays a best-effort guess (namespace = name) against the active project's cluster,
30
- // same fallback `app status -a <name>` uses.
31
- const active = getActive();
32
- ensureClusterUp(clusterNameFor(active), active);
33
- namespace = targetName;
34
- }
35
- }
36
-
37
- if (!targetName) {
38
- throw new Error('Run this from inside an app repo, or pass a name (localctl app logs <name>).');
39
- }
40
-
41
- const args = ['logs', `deploy/${targetName}`, '-n', namespace];
42
- if (opts.follow) args.push('-f');
43
- run('kubectl', args);
44
- });
45
- };
1
+ const fs = require('fs');
2
+ const { run } = require('../lib/exec');
3
+ const { loadConfig } = require('../lib/loadConfig');
4
+ const { ensureClusterUp } = require('../lib/clusterProvision');
5
+ const { getActive, clusterNameFor } = require('../lib/profileStore');
6
+ const { CONFIG_PATH } = require('../lib/paths');
7
+
8
+ module.exports = (program) => {
9
+ program
10
+ .command('logs [name]')
11
+ .option('-n, --namespace <ns>', 'namespace override (only needed outside an app\'s own repo, or if it disagrees with what "localctl app status" shows)')
12
+ .option('-f, --follow', 'follow log output')
13
+ .description('Tail logs for a deployed app - run inside the app\'s repo to resolve it (and its project\'s namespace prefix) automatically, or pass a name')
14
+ .action((name, opts) => {
15
+ let targetName = name;
16
+ let namespace = opts.namespace;
17
+
18
+ if (!namespace) {
19
+ if (!targetName && fs.existsSync(CONFIG_PATH)) {
20
+ // Directory-resolved, same as `app status`/`up`/`down` - this is what a namespace-scoped
21
+ // project needs: its real namespace is "<scope>-<name>", not the bare app name a plain
22
+ // `-n <name>` default would guess.
23
+ const cfg = loadConfig();
24
+ ensureClusterUp(cfg.clusterName, cfg.profile);
25
+ targetName = cfg.name;
26
+ namespace = cfg.namespace;
27
+ } else if (targetName) {
28
+ // An explicit name from outside its own repo - no directory to resolve from, so this
29
+ // stays a best-effort guess (namespace = name) against the active project's cluster,
30
+ // same fallback `app status -a <name>` uses.
31
+ const active = getActive();
32
+ ensureClusterUp(clusterNameFor(active), active);
33
+ namespace = targetName;
34
+ }
35
+ }
36
+
37
+ if (!targetName) {
38
+ throw new Error('Run this from inside an app repo, or pass a name (localctl app logs <name>).');
39
+ }
40
+
41
+ const args = ['logs', `deploy/${targetName}`, '-n', namespace];
42
+ if (opts.follow) args.push('-f');
43
+ run('kubectl', args);
44
+ });
45
+ };