daytona 0.155.0 → 0.157.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 885fbb7e696fd3a33436ce01439b91c7a430dae30a86ae233eff51fa54a5cca5
4
- data.tar.gz: a495d5928f1ae9ec8147aee393767a686ba28897212fecc9ebd19517440ff9e9
3
+ metadata.gz: 0e4b054d1b0090d6425189733b471adfb8de35c814cbebfe9ba1df64dd7cb37e
4
+ data.tar.gz: 69fdfe1572c89aa67bf804a2bd751edb2b73f8c6d2b52add512f79ec0459b5ec
5
5
  SHA512:
6
- metadata.gz: 7ffdad63ef44ff5cb1dd8b577163e68779c2e196680a36a478cc33b4c831e536150e0ed5d1c718c7021a51aee73daf01fb31209eb7a6d5a046618585b4d859ed
7
- data.tar.gz: 4346643bac10c6cbbcbe860ca9ea0eaafd54e286c4dac53ec8ec98e85431816b272aa2b64dbc22de702c6fda08f998e7dd4e2e7d519055e13b16595b5f131c2e
6
+ metadata.gz: 249970151c4a5c4043ebff0bcaf3c885ed723b7b0742b6016af95c4bbdfa3e8d23c287936ce32a0b7b4dfc2bc6ab0261b75e8fc29421485528fda670f44960c5
7
+ data.tar.gz: 396607d006c45ea9add617beba268f15c0176af2778249b1c94414c9ab2e80f0be831df2cfe7c520c61173f79e15ca1d70b827e24c3c8a1760d07ceba229bb6d
@@ -5,15 +5,17 @@ require 'base64'
5
5
  module Daytona
6
6
  class SandboxJsCodeToolbox
7
7
  def get_run_command(code, params = nil)
8
+ # Prepend argv fix: node - places '-' at argv[1]; splice it out to match legacy node -e behaviour
8
9
  # Encode the provided code in base64
9
- base64_code = Base64.strict_encode64(code)
10
+ base64_code = Base64.strict_encode64("process.argv.splice(1, 1);\n" + code)
10
11
 
11
12
  # Build command-line arguments string
12
13
  argv = ''
13
14
  argv = params.argv.join(' ') if params&.argv && !params.argv.empty?
14
15
 
15
- # Combine everything into the final command for JavaScript
16
- " sh -c 'echo #{base64_code} | base64 --decode | node -e \"$(cat)\" #{argv} 2>&1 | grep -vE \"npm notice\"' "
16
+ # Pipe the base64-encoded code via stdin to avoid OS ARG_MAX limits on large payloads
17
+ # Use node - to read from stdin (node /dev/stdin does not work when stdin is a pipe)
18
+ "printf '%s' '#{base64_code}' | base64 -d | node - #{argv}"
17
19
  end
18
20
  end
19
21
  end
@@ -10,22 +10,21 @@ module Daytona
10
10
  # @param params [Daytona::CodeRunParams, nil] Optional parameters for code execution
11
11
  # @return [String] The command to run the Python code
12
12
  def get_run_command(code, params = nil)
13
- encoded_code = Base64.encode64(code)
13
+ encoded_code = Base64.strict_encode64(code)
14
14
 
15
15
  # Override plt.show() method if matplotlib is imported
16
16
  if matplotlib_imported?(code)
17
- encoded_code = Base64.encode64(
17
+ encoded_code = Base64.strict_encode64(
18
18
  Base64.decode64(PYTHON_CODE_WRAPPER).gsub('{encoded_code}', encoded_code)
19
19
  )
20
20
  end
21
21
 
22
22
  argv = params&.argv&.join(' ') || ''
23
23
 
24
- # Execute the bootstrapper code directly
24
+ # Pipe the base64-encoded code via stdin to avoid OS ARG_MAX limits on large payloads
25
+ # printf is a shell builtin that does not invoke execve(), so the base64 string bypasses the kernel ARG_MAX limit
25
26
  # Use -u flag to ensure unbuffered output for real-time error reporting
26
-
27
- " sh -c 'python3 -u -c \"exec(__import__(\\\"base64\\\").b64decode(\\\"\\\"\\\"#{encoded_code}\\\"\\\"\\\")" \
28
- ".decode())\" #{argv}' "
27
+ "printf '%s' '#{encoded_code}' | base64 -d | python3 -u - #{argv}"
29
28
  end
30
29
 
31
30
  private
@@ -10,14 +10,21 @@ module Daytona
10
10
  # @param params [Daytona::CodeRunParams, nil] Optional parameters for code execution
11
11
  # @return [String] The command to run the TypeScript code
12
12
  def get_run_command(code, params = nil)
13
- encoded_code = Base64.encode64(code)
13
+ # Prepend argv fix: ts-node places the script path at argv[1]; splice it out to match legacy node -e behaviour
14
+ encoded_code = Base64.strict_encode64("process.argv.splice(1, 1);\n" + code)
14
15
 
15
16
  argv = params&.argv&.join(' ') || ''
16
17
 
17
- # Execute TypeScript code using ts-node with ESM support
18
- " sh -c 'echo #{encoded_code} | base64 --decode | npx ts-node -O " \
19
- "\"{\\\"module\\\":\\\"CommonJS\\\"}\" -e \"$(cat)\" x #{argv} 2>&1 | grep -vE " \
20
- "\"npm notice\"' "
18
+ # Pipe the base64-encoded code via stdin to avoid OS ARG_MAX limits on large payloads
19
+ # ts-node does not support - for stdin; use shell PID ($$) for the temp file — each code_run spawns its own
20
+ # shell process so $$ is unique across concurrent calls; cleaned up before exit
21
+ # npm_config_loglevel=error suppresses npm notice/warn output at source, preserving streaming and real errors
22
+ '_f=/tmp/dtn_$$.ts; ' \
23
+ "printf '%s' '#{encoded_code}' | base64 -d > \"$_f\"; " \
24
+ "npm_config_loglevel=error npx ts-node -T --ignore-diagnostics 5107 -O '{\"module\":\"CommonJS\"}' \"$_f\" #{argv}; " \
25
+ '_dtn_ec=$?; ' \
26
+ 'rm -f "$_f"; ' \
27
+ 'exit $_dtn_ec'
21
28
  end
22
29
  end
23
30
  end
@@ -52,46 +52,47 @@ module Daytona
52
52
  target: nil,
53
53
  _experimental: nil
54
54
  )
55
- # Load environment variables from .env and .env.local files
56
- # Files are loaded from the current working directory (where the code is executed)
57
- load_env_files
58
-
59
- @api_key = api_key || ENV.fetch('DAYTONA_API_KEY', nil)
60
- @jwt_token = jwt_token || ENV.fetch('DAYTONA_JWT_TOKEN', nil)
61
- @api_url = api_url || ENV.fetch('DAYTONA_API_URL', API_URL)
62
- @target = target || ENV.fetch('DAYTONA_TARGET', nil)
63
- @organization_id = organization_id || ENV.fetch('DAYTONA_ORGANIZATION_ID', nil)
55
+ @env_reader = daytona_env_reader
56
+
57
+ @api_key = api_key || @env_reader.call('DAYTONA_API_KEY')
58
+ @jwt_token = jwt_token || @env_reader.call('DAYTONA_JWT_TOKEN')
59
+ @api_url = api_url || @env_reader.call('DAYTONA_API_URL') || API_URL
60
+ @target = target || @env_reader.call('DAYTONA_TARGET')
61
+ @organization_id = organization_id || @env_reader.call('DAYTONA_ORGANIZATION_ID')
64
62
  @_experimental = _experimental
65
63
  end
66
64
 
67
- private
65
+ # Reads a DAYTONA_-prefixed environment variable using the same precedence
66
+ # as the Config initializer: runtime ENV first, then .env.local, then .env.
67
+ # Only names starting with DAYTONA_ are accepted.
68
+ #
69
+ # @param name [String] The environment variable name. Must start with DAYTONA_.
70
+ # @return [String, nil] The value of the environment variable, or nil if not set.
71
+ # @raise [ArgumentError] If name does not start with DAYTONA_.
72
+ def read_env(name)
73
+ @env_reader.call(name)
74
+ end
68
75
 
69
- # Load only Daytona-specific environment variables from .env and .env.local files
70
- # Only loads variables that are not already set in the runtime environment
71
- # .env.local overrides .env
72
- # Files are loaded from the current working directory
73
- def load_env_files
74
- # Daytona-specific variables we want to load
75
- daytona_vars = %w[
76
- DAYTONA_API_KEY
77
- DAYTONA_API_URL
78
- DAYTONA_TARGET
79
- DAYTONA_JWT_TOKEN
80
- DAYTONA_ORGANIZATION_ID
81
- ]
76
+ private
82
77
 
78
+ # Returns a lambda that looks up DAYTONA_-prefixed env vars without writing to ENV.
79
+ # Files are parsed once; lookups check runtime env first, then .env.local, then .env.
80
+ def daytona_env_reader
81
+ file_vars = {}
83
82
  env_file = File.join(Dir.pwd, '.env')
83
+ file_vars.merge!(daytona_filter(Dotenv.parse(env_file))) if File.exist?(env_file)
84
84
  env_local_file = File.join(Dir.pwd, '.env.local')
85
+ file_vars.merge!(daytona_filter(Dotenv.parse(env_local_file))) if File.exist?(env_local_file)
85
86
 
86
- # Parse .env files using dotenv (doesn't set ENV automatically)
87
- env_from_file = {}
88
- env_from_file.merge!(Dotenv.parse(env_file)) if File.exist?(env_file)
89
- env_from_file.merge!(Dotenv.parse(env_local_file)) if File.exist?(env_local_file)
87
+ lambda do |name|
88
+ raise ArgumentError, "Variable must start with 'DAYTONA_', got '#{name}'" unless name.start_with?('DAYTONA_')
90
89
 
91
- # Only set Daytona-specific variables that aren't already in runtime
92
- daytona_vars.each do |var|
93
- ENV[var] = env_from_file[var] if env_from_file.key?(var) && !ENV.key?(var)
90
+ ENV.key?(name) ? ENV[name] : file_vars[name]
94
91
  end
95
92
  end
93
+
94
+ def daytona_filter(env_hash)
95
+ env_hash.select { |k, _| k.start_with?('DAYTONA_') }
96
+ end
96
97
  end
97
98
  end
@@ -33,7 +33,7 @@ module Daytona
33
33
  @config = config
34
34
  ensure_access_token_defined
35
35
 
36
- otel_enabled = config._experimental&.dig('otel_enabled') || ENV['DAYTONA_EXPERIMENTAL_OTEL_ENABLED'] == 'true'
36
+ otel_enabled = config._experimental&.dig('otel_enabled') || config.read_env('DAYTONA_EXPERIMENTAL_OTEL_ENABLED') == 'true'
37
37
  @otel_state = (::Daytona.init_otel(Sdk::VERSION) if otel_enabled)
38
38
 
39
39
  @api_client = build_api_client
@@ -149,10 +149,13 @@ module Daytona
149
149
  raise Sdk::Error, 'auto_archive_interval must be a non-negative integer'
150
150
  end
151
151
 
152
+ labels = params.labels&.dup || {}
153
+ labels[LABEL_CODE_TOOLBOX_LANGUAGE] = params.language.to_s if params.language
154
+
152
155
  create_sandbox = DaytonaApiClient::CreateSandbox.new(
153
156
  user: params.os_user,
154
157
  env: params.env_vars || {},
155
- labels: params.labels,
158
+ labels: labels,
156
159
  public: params.public,
157
160
  target: config.target,
158
161
  auto_stop_interval: params.auto_stop_interval,
@@ -54,21 +54,23 @@ module Daytona
54
54
  #
55
55
  # # Command with timeout
56
56
  # result = sandbox.process.exec("sleep 10", timeout: 5)
57
- def exec(command:, cwd: nil, env: nil, timeout: nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
58
- command = "echo '#{Base64.encode64(command)}' | base64 -d | sh"
59
-
57
+ def exec(command:, cwd: nil, env: nil, timeout: nil) # rubocop:disable Metrics/MethodLength
60
58
  if env && !env.empty?
59
+ env.each_key do |key|
60
+ unless key.match?(/\A[A-Za-z_][A-Za-z0-9_]*\z/)
61
+ raise ArgumentError,
62
+ "Invalid environment variable name: '#{key}'"
63
+ end
64
+ end
61
65
  safe_env_exports = env.map do |key, value|
62
- "export #{key}=$(echo '#{Base64.encode64(value)}' | base64 -d)"
63
- end.join(';')
66
+ "export #{key}=\"$(echo '#{Base64.strict_encode64(value)}' | base64 -d)\""
67
+ end.join('; ')
64
68
  command = "#{safe_env_exports}; #{command}"
65
69
  end
66
70
 
67
- command = "sh -c \"#{command}\""
68
-
69
71
  response = toolbox_api.execute_command(DaytonaToolboxApiClient::ExecuteRequest.new(command:, cwd:, timeout:))
70
72
  # Post-process the output to extract ExecutionArtifacts
71
- artifacts = parse_output(response.result.split("\n"))
73
+ artifacts = parse_output(response.result.split("\n", -1))
72
74
 
73
75
  # Create new response with processed output and charts
74
76
  ExecuteResponse.new(
@@ -546,15 +548,17 @@ module Daytona
546
548
  # @return [Daytona::ExecutionArtifacts] The artifacts from the command execution
547
549
  def parse_output(lines)
548
550
  artifacts = ExecutionArtifacts.new('', [])
551
+ stdout_lines = []
549
552
 
550
553
  lines.each do |line|
551
554
  if line.start_with?(ARTIFACT_PREFIX)
552
555
  parse_json_line(line:, artifacts:)
553
556
  else
554
- artifacts.stdout += "#{line}\n"
557
+ stdout_lines << line
555
558
  end
556
559
  end
557
560
 
561
+ artifacts.stdout = stdout_lines.join("\n")
558
562
  artifacts
559
563
  end
560
564
 
@@ -365,13 +365,14 @@ module Daytona
365
365
  # Stops the Sandbox and waits for it to be stopped.
366
366
  #
367
367
  # @param timeout [Numeric] Maximum wait time in seconds (defaults to 60 s).
368
+ # @param force [Boolean] If true, uses SIGKILL instead of SIGTERM (defaults to false).
368
369
  # @return [void]
369
- def stop(timeout = DEFAULT_TIMEOUT) # rubocop:disable Metrics/MethodLength
370
+ def stop(timeout = DEFAULT_TIMEOUT, force: false) # rubocop:disable Metrics/MethodLength
370
371
  with_timeout(
371
372
  timeout:,
372
373
  message: "Sandbox #{id} failed to become stopped within the #{timeout} seconds timeout period",
373
374
  setup: proc {
374
- sandbox_api.stop_sandbox(id)
375
+ sandbox_api.stop_sandbox(id, { force: force })
375
376
  refresh
376
377
  }
377
378
  ) do
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Daytona
4
4
  module Sdk
5
- VERSION = '0.155.0'
5
+ VERSION = '0.157.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: daytona
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.155.0
4
+ version: 0.157.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daytona Platforms Inc.
@@ -85,28 +85,28 @@ dependencies:
85
85
  requirements:
86
86
  - - '='
87
87
  - !ruby/object:Gem::Version
88
- version: 0.155.0
88
+ version: 0.157.0
89
89
  type: :runtime
90
90
  prerelease: false
91
91
  version_requirements: !ruby/object:Gem::Requirement
92
92
  requirements:
93
93
  - - '='
94
94
  - !ruby/object:Gem::Version
95
- version: 0.155.0
95
+ version: 0.157.0
96
96
  - !ruby/object:Gem::Dependency
97
97
  name: daytona_toolbox_api_client
98
98
  requirement: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - '='
101
101
  - !ruby/object:Gem::Version
102
- version: 0.155.0
102
+ version: 0.157.0
103
103
  type: :runtime
104
104
  prerelease: false
105
105
  version_requirements: !ruby/object:Gem::Requirement
106
106
  requirements:
107
107
  - - '='
108
108
  - !ruby/object:Gem::Version
109
- version: 0.155.0
109
+ version: 0.157.0
110
110
  - !ruby/object:Gem::Dependency
111
111
  name: dotenv
112
112
  requirement: !ruby/object:Gem::Requirement