@git.zone/tsdocker 1.17.4 → 2.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.
Files changed (37) hide show
  1. package/dist_ts/00_commitinfo_data.js +2 -2
  2. package/dist_ts/classes.dockerfile.d.ts +2 -0
  3. package/dist_ts/classes.dockerfile.js +7 -1
  4. package/dist_ts/classes.globalconfig.d.ts +13 -0
  5. package/dist_ts/classes.globalconfig.js +66 -0
  6. package/dist_ts/classes.sshtunnel.d.ts +23 -0
  7. package/dist_ts/classes.sshtunnel.js +66 -0
  8. package/dist_ts/classes.tsdockermanager.d.ts +21 -1
  9. package/dist_ts/classes.tsdockermanager.js +74 -3
  10. package/dist_ts/interfaces/index.d.ts +15 -7
  11. package/dist_ts/tsdocker.cli.js +140 -33
  12. package/dist_ts/tsdocker.config.d.ts +1 -4
  13. package/dist_ts/tsdocker.config.js +3 -22
  14. package/dist_ts/tsdocker.paths.d.ts +0 -1
  15. package/dist_ts/tsdocker.paths.js +1 -2
  16. package/dist_ts/tsdocker.plugins.d.ts +1 -5
  17. package/dist_ts/tsdocker.plugins.js +2 -6
  18. package/package.json +8 -20
  19. package/readme.hints.md +1 -10
  20. package/readme.md +227 -78
  21. package/ts/00_commitinfo_data.ts +1 -1
  22. package/ts/classes.dockerfile.ts +7 -1
  23. package/ts/classes.globalconfig.ts +76 -0
  24. package/ts/classes.sshtunnel.ts +77 -0
  25. package/ts/classes.tsdockermanager.ts +97 -3
  26. package/ts/interfaces/index.ts +17 -8
  27. package/ts/tsdocker.cli.ts +145 -37
  28. package/ts/tsdocker.config.ts +4 -29
  29. package/ts/tsdocker.paths.ts +0 -1
  30. package/ts/tsdocker.plugins.ts +0 -8
  31. package/assets/Dockerfile +0 -6
  32. package/dist_ts/tsdocker.docker.d.ts +0 -2
  33. package/dist_ts/tsdocker.docker.js +0 -145
  34. package/dist_ts/tsdocker.snippets.d.ts +0 -5
  35. package/dist_ts/tsdocker.snippets.js +0 -26
  36. package/ts/tsdocker.docker.ts +0 -169
  37. package/ts/tsdocker.snippets.ts +0 -34
@@ -0,0 +1,76 @@
1
+ import * as fs from 'fs';
2
+ import * as plugins from './tsdocker.plugins.js';
3
+ import { logger } from './tsdocker.logging.js';
4
+ import type { IGlobalConfig, IRemoteBuilder } from './interfaces/index.js';
5
+
6
+ const CONFIG_DIR = plugins.path.join(
7
+ process.env.HOME || process.env.USERPROFILE || '~',
8
+ '.git.zone',
9
+ 'tsdocker',
10
+ );
11
+ const CONFIG_PATH = plugins.path.join(CONFIG_DIR, 'config.json');
12
+
13
+ const DEFAULT_CONFIG: IGlobalConfig = {
14
+ remoteBuilders: [],
15
+ };
16
+
17
+ export class GlobalConfig {
18
+ static getConfigPath(): string {
19
+ return CONFIG_PATH;
20
+ }
21
+
22
+ static load(): IGlobalConfig {
23
+ try {
24
+ const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
25
+ const parsed = JSON.parse(raw);
26
+ return {
27
+ ...DEFAULT_CONFIG,
28
+ ...parsed,
29
+ };
30
+ } catch {
31
+ return { ...DEFAULT_CONFIG };
32
+ }
33
+ }
34
+
35
+ static save(config: IGlobalConfig): void {
36
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
37
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', 'utf-8');
38
+ }
39
+
40
+ static addBuilder(builder: IRemoteBuilder): void {
41
+ const config = GlobalConfig.load();
42
+ const existing = config.remoteBuilders.findIndex((b) => b.name === builder.name);
43
+ if (existing >= 0) {
44
+ config.remoteBuilders[existing] = builder;
45
+ logger.log('info', `Updated remote builder: ${builder.name}`);
46
+ } else {
47
+ config.remoteBuilders.push(builder);
48
+ logger.log('info', `Added remote builder: ${builder.name}`);
49
+ }
50
+ GlobalConfig.save(config);
51
+ }
52
+
53
+ static removeBuilder(name: string): void {
54
+ const config = GlobalConfig.load();
55
+ const before = config.remoteBuilders.length;
56
+ config.remoteBuilders = config.remoteBuilders.filter((b) => b.name !== name);
57
+ if (config.remoteBuilders.length < before) {
58
+ logger.log('info', `Removed remote builder: ${name}`);
59
+ } else {
60
+ logger.log('warn', `Remote builder not found: ${name}`);
61
+ }
62
+ GlobalConfig.save(config);
63
+ }
64
+
65
+ static getBuilders(): IRemoteBuilder[] {
66
+ return GlobalConfig.load().remoteBuilders;
67
+ }
68
+
69
+ /**
70
+ * Returns remote builders that match any of the requested platforms
71
+ */
72
+ static getBuildersForPlatforms(platforms: string[]): IRemoteBuilder[] {
73
+ const builders = GlobalConfig.getBuilders();
74
+ return builders.filter((b) => platforms.includes(b.platform));
75
+ }
76
+ }
@@ -0,0 +1,77 @@
1
+ import * as plugins from './tsdocker.plugins.js';
2
+ import { logger } from './tsdocker.logging.js';
3
+ import type { IRemoteBuilder } from './interfaces/index.js';
4
+
5
+ const smartshellInstance = new plugins.smartshell.Smartshell({
6
+ executor: 'bash',
7
+ });
8
+
9
+ /**
10
+ * Manages SSH reverse tunnels for remote builder nodes.
11
+ * Opens tunnels so that the local staging registry (localhost:<port>)
12
+ * is accessible as localhost:<port> on each remote machine.
13
+ */
14
+ export class SshTunnelManager {
15
+ private tunnelPids: number[] = [];
16
+
17
+ /**
18
+ * Opens a reverse SSH tunnel to make localPort accessible on the remote machine.
19
+ * ssh -f -N -o StrictHostKeyChecking=no -o ExitOnForwardFailure=yes
20
+ * -R <localPort>:localhost:<localPort> [-i keyPath] user@host
21
+ */
22
+ async openTunnel(builder: IRemoteBuilder, localPort: number): Promise<void> {
23
+ const keyOpt = builder.sshKeyPath ? `-i ${builder.sshKeyPath} ` : '';
24
+ const cmd = [
25
+ 'ssh -f -N',
26
+ '-o StrictHostKeyChecking=no',
27
+ '-o ExitOnForwardFailure=yes',
28
+ `-R ${localPort}:localhost:${localPort}`,
29
+ `${keyOpt}${builder.host}`,
30
+ ].join(' ');
31
+
32
+ logger.log('info', `Opening SSH tunnel to ${builder.host} for port ${localPort}...`);
33
+ const result = await smartshellInstance.exec(cmd);
34
+
35
+ if (result.exitCode !== 0) {
36
+ throw new Error(
37
+ `Failed to open SSH tunnel to ${builder.host}: ${result.stderr || 'unknown error'}`
38
+ );
39
+ }
40
+
41
+ // Find the PID of the tunnel process we just started
42
+ const pidResult = await smartshellInstance.exec(
43
+ `pgrep -f "ssh.*-R ${localPort}:localhost:${localPort}.*${builder.host}" | tail -1`
44
+ );
45
+ if (pidResult.exitCode === 0 && pidResult.stdout.trim()) {
46
+ const pid = parseInt(pidResult.stdout.trim(), 10);
47
+ if (!isNaN(pid)) {
48
+ this.tunnelPids.push(pid);
49
+ logger.log('ok', `SSH tunnel to ${builder.host} established (PID ${pid})`);
50
+ }
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Opens tunnels for all provided remote builders
56
+ */
57
+ async openTunnels(builders: IRemoteBuilder[], localPort: number): Promise<void> {
58
+ for (const builder of builders) {
59
+ await this.openTunnel(builder, localPort);
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Closes all tunnel processes
65
+ */
66
+ async closeAll(): Promise<void> {
67
+ for (const pid of this.tunnelPids) {
68
+ try {
69
+ process.kill(pid, 'SIGTERM');
70
+ logger.log('info', `Closed SSH tunnel (PID ${pid})`);
71
+ } catch {
72
+ // Process may have already exited
73
+ }
74
+ }
75
+ this.tunnelPids = [];
76
+ }
77
+ }
@@ -8,7 +8,9 @@ import { TsDockerCache } from './classes.tsdockercache.js';
8
8
  import { DockerContext } from './classes.dockercontext.js';
9
9
  import { TsDockerSession } from './classes.tsdockersession.js';
10
10
  import { RegistryCopy } from './classes.registrycopy.js';
11
- import type { ITsDockerConfig, IBuildCommandOptions } from './interfaces/index.js';
11
+ import { GlobalConfig } from './classes.globalconfig.js';
12
+ import { SshTunnelManager } from './classes.sshtunnel.js';
13
+ import type { ITsDockerConfig, IBuildCommandOptions, IRemoteBuilder } from './interfaces/index.js';
12
14
 
13
15
  const smartshellInstance = new plugins.smartshell.Smartshell({
14
16
  executor: 'bash',
@@ -24,6 +26,8 @@ export class TsDockerManager {
24
26
  public dockerContext: DockerContext;
25
27
  public session!: TsDockerSession;
26
28
  private dockerfiles: Dockerfile[] = [];
29
+ private activeRemoteBuilders: IRemoteBuilder[] = [];
30
+ private sshTunnelManager?: SshTunnelManager;
27
31
 
28
32
  constructor(config: ITsDockerConfig) {
29
33
  this.config = config;
@@ -235,6 +239,7 @@ export class TsDockerManager {
235
239
  const total = toBuild.length;
236
240
  const overallStart = Date.now();
237
241
  await Dockerfile.startLocalRegistry(this.session, this.dockerContext.contextInfo?.isRootless);
242
+ await this.openRemoteTunnels();
238
243
 
239
244
  try {
240
245
  if (options?.parallel) {
@@ -332,6 +337,7 @@ export class TsDockerManager {
332
337
  }
333
338
  }
334
339
  } finally {
340
+ await this.closeRemoteTunnels();
335
341
  await Dockerfile.stopLocalRegistry(this.session);
336
342
  }
337
343
 
@@ -347,6 +353,8 @@ export class TsDockerManager {
347
353
  isRootless: this.dockerContext.contextInfo?.isRootless,
348
354
  parallel: options?.parallel,
349
355
  parallelConcurrency: options?.parallelConcurrency,
356
+ onRegistryStarted: () => this.openRemoteTunnels(),
357
+ onBeforeRegistryStop: () => this.closeRemoteTunnels(),
350
358
  });
351
359
  }
352
360
 
@@ -373,13 +381,76 @@ export class TsDockerManager {
373
381
  }
374
382
 
375
383
  /**
376
- * Ensures Docker buildx is set up for multi-architecture builds
384
+ * Ensures Docker buildx is set up for multi-architecture builds.
385
+ * When remote builders are configured in the global config, creates a multi-node
386
+ * builder with native nodes instead of relying on QEMU emulation.
377
387
  */
378
388
  private async ensureBuildx(): Promise<void> {
379
389
  const builderName = this.dockerContext.getBuilderName() + (this.session?.config.builderSuffix || '');
380
390
  const platforms = this.config.platforms?.join(', ') || 'default';
381
391
  logger.log('info', `Setting up Docker buildx [${platforms}]...`);
382
392
  logger.log('info', `Builder: ${builderName}`);
393
+
394
+ // Check for remote builders matching our target platforms
395
+ const requestedPlatforms = this.config.platforms || ['linux/amd64'];
396
+ const remoteBuilders = GlobalConfig.getBuildersForPlatforms(requestedPlatforms);
397
+
398
+ if (remoteBuilders.length > 0) {
399
+ await this.ensureBuildxWithRemoteNodes(builderName, requestedPlatforms, remoteBuilders);
400
+ } else {
401
+ await this.ensureBuildxLocal(builderName);
402
+ }
403
+
404
+ logger.log('ok', `Docker buildx ready (builder: ${builderName}, platforms: ${platforms})`);
405
+ }
406
+
407
+ /**
408
+ * Creates a multi-node buildx builder with local + remote SSH nodes.
409
+ */
410
+ private async ensureBuildxWithRemoteNodes(
411
+ builderName: string,
412
+ requestedPlatforms: string[],
413
+ remoteBuilders: IRemoteBuilder[],
414
+ ): Promise<void> {
415
+ const remotePlatforms = new Set(remoteBuilders.map((b) => b.platform));
416
+ const localPlatforms = requestedPlatforms.filter((p) => !remotePlatforms.has(p));
417
+
418
+ logger.log('info', `Remote builders: ${remoteBuilders.map((b) => `${b.name} (${b.platform} @ ${b.host})`).join(', ')}`);
419
+ if (localPlatforms.length > 0) {
420
+ logger.log('info', `Local platforms: ${localPlatforms.join(', ')}`);
421
+ }
422
+
423
+ // Always recreate the builder to ensure correct node topology
424
+ await smartshellInstance.execSilent(`docker buildx rm ${builderName} 2>/dev/null || true`);
425
+
426
+ // Create the local node
427
+ const localPlatformFlag = localPlatforms.length > 0 ? ` --platform ${localPlatforms.join(',')}` : '';
428
+ await smartshellInstance.exec(
429
+ `docker buildx create --name ${builderName} --driver docker-container --driver-opt network=host${localPlatformFlag} --use`
430
+ );
431
+
432
+ // Append remote nodes
433
+ for (const builder of remoteBuilders) {
434
+ logger.log('info', `Appending remote node: ${builder.name} (${builder.platform}) via ssh://${builder.host}`);
435
+ const appendResult = await smartshellInstance.exec(
436
+ `docker buildx create --append --name ${builderName} --driver docker-container --driver-opt network=host --platform ${builder.platform} --node ${builder.name} ssh://${builder.host}`
437
+ );
438
+ if (appendResult.exitCode !== 0) {
439
+ throw new Error(`Failed to append remote builder ${builder.name}: ${appendResult.stderr}`);
440
+ }
441
+ }
442
+
443
+ // Bootstrap all nodes
444
+ await smartshellInstance.exec('docker buildx inspect --bootstrap');
445
+
446
+ // Store active remote builders for SSH tunnel setup during build
447
+ this.activeRemoteBuilders = remoteBuilders;
448
+ }
449
+
450
+ /**
451
+ * Creates a single-node local buildx builder (original behavior, uses QEMU for cross-platform).
452
+ */
453
+ private async ensureBuildxLocal(builderName: string): Promise<void> {
383
454
  const inspectResult = await smartshellInstance.exec(`docker buildx inspect ${builderName} 2>/dev/null`);
384
455
 
385
456
  if (inspectResult.exitCode !== 0) {
@@ -401,7 +472,30 @@ export class TsDockerManager {
401
472
  await smartshellInstance.exec(`docker buildx use ${builderName}`);
402
473
  }
403
474
  }
404
- logger.log('ok', `Docker buildx ready (builder: ${builderName}, platforms: ${platforms})`);
475
+ this.activeRemoteBuilders = [];
476
+ }
477
+
478
+ /**
479
+ * Opens SSH reverse tunnels for remote builders so they can reach the local registry.
480
+ */
481
+ private async openRemoteTunnels(): Promise<void> {
482
+ if (this.activeRemoteBuilders.length === 0) return;
483
+
484
+ this.sshTunnelManager = new SshTunnelManager();
485
+ await this.sshTunnelManager.openTunnels(
486
+ this.activeRemoteBuilders,
487
+ this.session.config.registryPort,
488
+ );
489
+ }
490
+
491
+ /**
492
+ * Closes any active SSH tunnels.
493
+ */
494
+ private async closeRemoteTunnels(): Promise<void> {
495
+ if (this.sshTunnelManager) {
496
+ await this.sshTunnelManager.closeAll();
497
+ this.sshTunnelManager = undefined;
498
+ }
405
499
  }
406
500
 
407
501
  /**
@@ -1,15 +1,7 @@
1
1
  /**
2
2
  * Configuration interface for tsdocker
3
- * Extends legacy config with new Docker build capabilities
4
3
  */
5
4
  export interface ITsDockerConfig {
6
- // Legacy (backward compatible)
7
- baseImage: string;
8
- command: string;
9
- dockerSock: boolean;
10
- keyValueObject: { [key: string]: any };
11
-
12
- // New Docker build config
13
5
  registries?: string[];
14
6
  registryRepoMap?: { [registry: string]: string };
15
7
  buildArgEnvMap?: { [dockerArg: string]: string };
@@ -103,3 +95,20 @@ export interface IDockerContextInfo {
103
95
  dockerHost?: string; // value of DOCKER_HOST env var, if set
104
96
  topology?: 'socket-mount' | 'dind' | 'local';
105
97
  }
98
+
99
+ /**
100
+ * A remote builder node for native cross-platform builds
101
+ */
102
+ export interface IRemoteBuilder {
103
+ name: string; // e.g., "arm64-builder"
104
+ host: string; // e.g., "armbuilder@192.168.190.216"
105
+ platform: string; // e.g., "linux/arm64"
106
+ sshKeyPath?: string; // e.g., "~/.ssh/id_ed25519"
107
+ }
108
+
109
+ /**
110
+ * Global tsdocker configuration stored at ~/.git.zone/tsdocker/config.json
111
+ */
112
+ export interface IGlobalConfig {
113
+ remoteBuilders: IRemoteBuilder[];
114
+ }
@@ -3,27 +3,97 @@ import * as paths from './tsdocker.paths.js';
3
3
 
4
4
  // modules
5
5
  import * as ConfigModule from './tsdocker.config.js';
6
- import * as DockerModule from './tsdocker.docker.js';
7
6
 
8
7
  import { logger, ora } from './tsdocker.logging.js';
9
8
  import { TsDockerManager } from './classes.tsdockermanager.js';
10
9
  import { DockerContext } from './classes.dockercontext.js';
10
+ import { GlobalConfig } from './classes.globalconfig.js';
11
11
  import type { IBuildCommandOptions } from './interfaces/index.js';
12
12
  import { commitinfo } from './00_commitinfo_data.js';
13
13
 
14
14
  const tsdockerCli = new plugins.smartcli.Smartcli();
15
15
  tsdockerCli.addVersion(commitinfo.version);
16
16
 
17
+ const printManPage = () => {
18
+ const manPage = `
19
+ TSDOCKER(1) User Commands TSDOCKER(1)
20
+
21
+ NAME
22
+ tsdocker - build, test, and push Docker images
23
+
24
+ VERSION
25
+ ${commitinfo.version}
26
+
27
+ SYNOPSIS
28
+ tsdocker <command> [options]
29
+
30
+ COMMANDS
31
+ build [patterns...] [flags] Build Dockerfiles in dependency order
32
+ push [patterns...] [flags] Build and push images to registries
33
+ pull <registry-url> Pull images from a registry
34
+ test [flags] Build and run container test scripts
35
+ login Authenticate with configured registries
36
+ list List discovered Dockerfiles
37
+ config <subcommand> [flags] Manage global tsdocker configuration
38
+ clean [-y] [--all] Interactive Docker resource cleanup
39
+
40
+ BUILD / PUSH OPTIONS
41
+ --platform=<p> Target platform (e.g. linux/arm64)
42
+ --timeout=<s> Build timeout in seconds
43
+ --no-cache Rebuild without Docker layer cache
44
+ --cached Skip builds when Dockerfile is unchanged
45
+ --verbose Stream raw docker build output
46
+ --parallel[=<n>] Parallel builds (optional concurrency limit)
47
+ --context=<name> Docker context to use
48
+
49
+ PUSH-ONLY OPTIONS
50
+ --registry=<url> Push to a specific registry
51
+ --no-build Push already-built images (skip build step)
52
+
53
+ CLEAN OPTIONS
54
+ -y Auto-confirm all prompts
55
+ --all Include all images and volumes (not just dangling)
56
+
57
+ CONFIG SUBCOMMANDS
58
+ add-builder Add a remote builder node
59
+ --name=<n> Builder name (e.g. arm64-builder)
60
+ --host=<h> SSH host (e.g. user@192.168.1.100)
61
+ --platform=<p> Platform (e.g. linux/arm64)
62
+ --ssh-key=<path> SSH key path (optional)
63
+ remove-builder Remove a remote builder by name
64
+ --name=<n> Builder name to remove
65
+ list-builders List all configured remote builders
66
+ show Show full global config
67
+
68
+ CONFIGURATION
69
+ Configure via npmextra.json under the "@git.zone/tsdocker" key:
70
+
71
+ registries Array of registry URLs to push to
72
+ registryRepoMap Map of registry URL to repo path overrides
73
+ buildArgEnvMap Map of Docker build-arg names to env var names
74
+ platforms Array of target platforms (default: ["linux/amd64"])
75
+ push Boolean, auto-push after build
76
+ testDir Directory containing test_*.sh scripts
77
+
78
+ Global config is stored at ~/.git.zone/tsdocker/config.json
79
+ and managed via the "config" command.
80
+
81
+ EXAMPLES
82
+ tsdocker build
83
+ tsdocker build Dockerfile_app --platform=linux/arm64
84
+ tsdocker push --registry=ghcr.io
85
+ tsdocker test --verbose
86
+ tsdocker clean -y --all
87
+ tsdocker config add-builder --name=arm64 --host=user@host --platform=linux/arm64
88
+ tsdocker config list-builders
89
+ `;
90
+ console.log(manPage);
91
+ };
92
+
17
93
  export let run = () => {
18
- // Default command: run tests in container (legacy behavior)
19
- tsdockerCli.standardCommand().subscribe(async argvArg => {
20
- const configArg = await ConfigModule.run().then(DockerModule.run);
21
- if (configArg.exitCode === 0) {
22
- logger.log('success', 'container ended all right!');
23
- } else {
24
- logger.log('error', `container ended with error! Exit Code is ${configArg.exitCode}`);
25
- process.exit(1);
26
- }
94
+ // Default command: print man page
95
+ tsdockerCli.standardCommand().subscribe(async () => {
96
+ printManPage();
27
97
  });
28
98
 
29
99
  /**
@@ -229,21 +299,73 @@ export let run = () => {
229
299
  });
230
300
 
231
301
  /**
232
- * this command is executed inside docker and meant for use from outside docker
302
+ * Manage global tsdocker configuration (remote builders, etc.)
303
+ * Usage: tsdocker config <subcommand> [--name=...] [--host=...] [--platform=...] [--ssh-key=...]
233
304
  */
234
- tsdockerCli.addCommand('runinside').subscribe(async argvArg => {
235
- logger.log('ok', 'Allright. We are now in Docker!');
236
- ora.text('now trying to run your specified command');
237
- const configArg = await ConfigModule.run();
238
- const smartshellInstance = new plugins.smartshell.Smartshell({
239
- executor: 'bash'
240
- });
241
- ora.stop();
242
- await smartshellInstance.exec(configArg.command).then(response => {
243
- if (response.exitCode !== 0) {
244
- process.exit(1);
305
+ tsdockerCli.addCommand('config').subscribe(async argvArg => {
306
+ try {
307
+ const subcommand = argvArg._[1] as string;
308
+
309
+ switch (subcommand) {
310
+ case 'add-builder': {
311
+ const name = argvArg.name as string;
312
+ const host = argvArg.host as string;
313
+ const platform = argvArg.platform as string;
314
+ const sshKeyPath = argvArg['ssh-key'] as string | undefined;
315
+
316
+ if (!name || !host || !platform) {
317
+ logger.log('error', 'Required: --name, --host, --platform');
318
+ logger.log('info', 'Usage: tsdocker config add-builder --name=arm64-builder --host=user@host --platform=linux/arm64 [--ssh-key=~/.ssh/id_ed25519]');
319
+ process.exit(1);
320
+ }
321
+
322
+ GlobalConfig.addBuilder({ name, host, platform, sshKeyPath });
323
+ logger.log('success', `Remote builder "${name}" configured: ${platform} via ssh://${host}`);
324
+ break;
325
+ }
326
+
327
+ case 'remove-builder': {
328
+ const name = argvArg.name as string;
329
+ if (!name) {
330
+ logger.log('error', 'Required: --name');
331
+ logger.log('info', 'Usage: tsdocker config remove-builder --name=arm64-builder');
332
+ process.exit(1);
333
+ }
334
+ GlobalConfig.removeBuilder(name);
335
+ logger.log('success', `Remote builder "${name}" removed`);
336
+ break;
337
+ }
338
+
339
+ case 'list-builders': {
340
+ const builders = GlobalConfig.getBuilders();
341
+ if (builders.length === 0) {
342
+ logger.log('info', 'No remote builders configured');
343
+ } else {
344
+ logger.log('info', `${builders.length} remote builder(s):`);
345
+ for (const b of builders) {
346
+ const keyInfo = b.sshKeyPath ? ` (key: ${b.sshKeyPath})` : '';
347
+ logger.log('info', ` ${b.name}: ${b.platform} via ssh://${b.host}${keyInfo}`);
348
+ }
349
+ }
350
+ break;
351
+ }
352
+
353
+ case 'show': {
354
+ const config = GlobalConfig.load();
355
+ logger.log('info', `Config file: ${GlobalConfig.getConfigPath()}`);
356
+ console.log(JSON.stringify(config, null, 2));
357
+ break;
358
+ }
359
+
360
+ default:
361
+ logger.log('error', `Unknown config subcommand: ${subcommand || '(none)'}`);
362
+ logger.log('info', 'Available: add-builder, remove-builder, list-builders, show');
363
+ process.exit(1);
245
364
  }
246
- });
365
+ } catch (err) {
366
+ logger.log('error', `Config failed: ${(err as Error).message}`);
367
+ process.exit(1);
368
+ }
247
369
  });
248
370
 
249
371
  tsdockerCli.addCommand('clean').subscribe(async argvArg => {
@@ -443,19 +565,5 @@ export let run = () => {
443
565
  }
444
566
  });
445
567
 
446
- tsdockerCli.addCommand('vscode').subscribe(async argvArg => {
447
- const smartshellInstance = new plugins.smartshell.Smartshell({
448
- executor: 'bash'
449
- });
450
- logger.log('ok', `Starting vscode in cwd ${paths.cwd}`);
451
- await smartshellInstance.execAndWaitForLine(
452
- `docker run -p 127.0.0.1:8443:8443 -v "${
453
- paths.cwd
454
- }:/home/coder/project" registry.gitlab.com/hosttoday/ht-docker-vscode --allow-http --no-auth`,
455
- /Connected to shared process/
456
- );
457
- await plugins.smartopen.openUrl('testing-vscode.git.zone:8443');
458
- });
459
-
460
568
  tsdockerCli.startParse();
461
569
  };
@@ -1,34 +1,10 @@
1
1
  import * as plugins from './tsdocker.plugins.js';
2
2
  import * as paths from './tsdocker.paths.js';
3
- import * as fs from 'fs';
4
3
  import type { ITsDockerConfig } from './interfaces/index.js';
5
4
 
6
- // Re-export ITsDockerConfig as IConfig for backward compatibility
7
- export type IConfig = ITsDockerConfig & {
8
- exitCode?: number;
9
- };
10
-
11
- const getQenvKeyValueObject = async () => {
12
- let qenvKeyValueObjectArray: { [key: string]: string | number };
13
- if (fs.existsSync(plugins.path.join(paths.cwd, 'qenv.yml'))) {
14
- qenvKeyValueObjectArray = new plugins.qenv.Qenv(paths.cwd, '.nogit/').keyValueObject;
15
- } else {
16
- qenvKeyValueObjectArray = {};
17
- }
18
- return qenvKeyValueObjectArray;
19
- };
20
-
21
- const buildConfig = async (qenvKeyValueObjectArg: { [key: string]: string | number }) => {
5
+ const buildConfig = async (): Promise<ITsDockerConfig> => {
22
6
  const npmextra = new plugins.npmextra.Npmextra(paths.cwd);
23
- const config = npmextra.dataFor<IConfig>('@git.zone/tsdocker', {
24
- // Legacy options (backward compatible)
25
- baseImage: 'hosttoday/ht-docker-node:npmdocker',
26
- init: 'rm -rf node_nodules/ && yarn install',
27
- command: 'npmci npm test',
28
- dockerSock: false,
29
- keyValueObject: qenvKeyValueObjectArg,
30
-
31
- // New Docker build options
7
+ const config = npmextra.dataFor<ITsDockerConfig>('@git.zone/tsdocker', {
32
8
  registries: [],
33
9
  registryRepoMap: {},
34
10
  buildArgEnvMap: {},
@@ -39,7 +15,6 @@ const buildConfig = async (qenvKeyValueObjectArg: { [key: string]: string | numb
39
15
  return config;
40
16
  };
41
17
 
42
- export let run = async (): Promise<IConfig> => {
43
- const config = await getQenvKeyValueObject().then(buildConfig);
44
- return config;
18
+ export let run = async (): Promise<ITsDockerConfig> => {
19
+ return buildConfig();
45
20
  };
@@ -11,4 +11,3 @@ export let cwd = process.cwd();
11
11
  export let packageBase = plugins.path.join(__dirname, '../');
12
12
  export let assets = plugins.path.join(packageBase, 'assets/');
13
13
  fs.mkdirSync(assets, { recursive: true });
14
- export let npmdockerFile = plugins.path.join(cwd, 'npmdocker');
@@ -3,17 +3,13 @@ import * as lik from '@push.rocks/lik';
3
3
  import * as npmextra from '@push.rocks/npmextra';
4
4
  import * as path from 'path';
5
5
  import * as projectinfo from '@push.rocks/projectinfo';
6
- import * as smartpromise from '@push.rocks/smartpromise';
7
- import * as qenv from '@push.rocks/qenv';
8
6
  import * as smartcli from '@push.rocks/smartcli';
9
7
  import { SmartFs, SmartFsProviderNode } from '@push.rocks/smartfs';
10
8
  import * as smartlog from '@push.rocks/smartlog';
11
9
  import * as smartlogDestinationLocal from '@push.rocks/smartlog-destination-local';
12
10
  import * as smartlogSouceOra from '@push.rocks/smartlog-source-ora';
13
- import * as smartopen from '@push.rocks/smartopen';
14
11
  import * as smartinteract from '@push.rocks/smartinteract';
15
12
  import * as smartshell from '@push.rocks/smartshell';
16
- import * as smartstring from '@push.rocks/smartstring';
17
13
 
18
14
  // Create smartfs instance
19
15
  export const smartfs = new SmartFs(new SmartFsProviderNode());
@@ -23,14 +19,10 @@ export {
23
19
  npmextra,
24
20
  path,
25
21
  projectinfo,
26
- smartpromise,
27
- qenv,
28
22
  smartcli,
29
23
  smartinteract,
30
24
  smartlog,
31
25
  smartlogDestinationLocal,
32
26
  smartlogSouceOra,
33
- smartopen,
34
27
  smartshell,
35
- smartstring
36
28
  };
package/assets/Dockerfile DELETED
@@ -1,6 +0,0 @@
1
- FROM hosttoday/ht-docker-node:npmci
2
- RUN yarn global add @git.zone/tsdocker
3
- COPY ./ /workspace
4
- WORKDIR /workspace
5
- ENV CI=true
6
- CMD ["tsdocker","runinside"];
@@ -1,2 +0,0 @@
1
- import type { IConfig } from './tsdocker.config.js';
2
- export declare let run: (configArg: IConfig) => Promise<IConfig>;