dirsql 0.3.58__tar.gz → 0.3.59__tar.gz

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 (124) hide show
  1. {dirsql-0.3.58 → dirsql-0.3.59}/Cargo.lock +1 -1
  2. {dirsql-0.3.58 → dirsql-0.3.59}/PKG-INFO +1 -1
  3. {dirsql-0.3.58 → dirsql-0.3.59}/docs/cli/config.md +23 -5
  4. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/Cargo.toml +1 -1
  5. {dirsql-0.3.58/packages/rust → dirsql-0.3.59/packages/python}/docs/cli/config.md +23 -5
  6. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/e2e-attestation.json +2 -2
  7. {dirsql-0.3.58/packages/python → dirsql-0.3.59/packages/rust}/docs/cli/config.md +23 -5
  8. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/bin/dirsql.rs +14 -4
  9. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/cli/mod.rs +45 -4
  10. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/cli/router.rs +10 -15
  11. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/command.rs +5 -0
  12. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/config.rs +118 -15
  13. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/lib.rs +27 -10
  14. {dirsql-0.3.58 → dirsql-0.3.59}/Cargo.toml +0 -0
  15. {dirsql-0.3.58 → dirsql-0.3.59}/README.md +0 -0
  16. {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/__init__.py +0 -0
  17. {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/_async.py +0 -0
  18. {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/_dirsql.pyi +0 -0
  19. {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/cli/__init__.py +0 -0
  20. {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/cli/binary_path.py +0 -0
  21. {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/cli/interpret/__init__.py +0 -0
  22. {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/cli/is_windows.py +0 -0
  23. {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/cli/main.py +0 -0
  24. {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/cli/resolve_config_extensions.py +0 -0
  25. {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/py.typed +0 -0
  26. {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/resolve_config_extensions.py +0 -0
  27. {dirsql-0.3.58 → dirsql-0.3.59}/dirsql/resolve_extension.py +0 -0
  28. {dirsql-0.3.58 → dirsql-0.3.59}/docs/.claude/CLAUDE.md +0 -0
  29. {dirsql-0.3.58 → dirsql-0.3.59}/docs/.vitepress/config.ts +0 -0
  30. {dirsql-0.3.58 → dirsql-0.3.59}/docs/.vitepress/theme/index.ts +0 -0
  31. {dirsql-0.3.58 → dirsql-0.3.59}/docs/.vitepress/theme/lang.ts +0 -0
  32. {dirsql-0.3.58 → dirsql-0.3.59}/docs/AGENTS.md +0 -0
  33. {dirsql-0.3.58 → dirsql-0.3.59}/docs/api/index.md +0 -0
  34. {dirsql-0.3.58 → dirsql-0.3.59}/docs/cli/http-api.md +0 -0
  35. {dirsql-0.3.58 → dirsql-0.3.59}/docs/cli/index.md +0 -0
  36. {dirsql-0.3.58 → dirsql-0.3.59}/docs/cli/init.md +0 -0
  37. {dirsql-0.3.58 → dirsql-0.3.59}/docs/cli/server.md +0 -0
  38. {dirsql-0.3.58 → dirsql-0.3.59}/docs/getting-started.md +0 -0
  39. {dirsql-0.3.58 → dirsql-0.3.59}/docs/guide/async.md +0 -0
  40. {dirsql-0.3.58 → dirsql-0.3.59}/docs/guide/crdt.md +0 -0
  41. {dirsql-0.3.58 → dirsql-0.3.59}/docs/guide/persistence.md +0 -0
  42. {dirsql-0.3.58 → dirsql-0.3.59}/docs/guide/querying.md +0 -0
  43. {dirsql-0.3.58 → dirsql-0.3.59}/docs/guide/tables.md +0 -0
  44. {dirsql-0.3.58 → dirsql-0.3.59}/docs/guide/watching.md +0 -0
  45. {dirsql-0.3.58 → dirsql-0.3.59}/docs/index.md +0 -0
  46. {dirsql-0.3.58 → dirsql-0.3.59}/docs/migrations.md +0 -0
  47. {dirsql-0.3.58 → dirsql-0.3.59}/docs/package.json +0 -0
  48. {dirsql-0.3.58 → dirsql-0.3.59}/docs/playwright.config.ts +0 -0
  49. {dirsql-0.3.58 → dirsql-0.3.59}/docs/pnpm-lock.yaml +0 -0
  50. {dirsql-0.3.58 → dirsql-0.3.59}/docs/pnpm-workspace.yaml +0 -0
  51. {dirsql-0.3.58 → dirsql-0.3.59}/docs/tests/integration/home.spec.ts +0 -0
  52. {dirsql-0.3.58 → dirsql-0.3.59}/docs/tests/integration/language-flag.spec.ts +0 -0
  53. {dirsql-0.3.58 → dirsql-0.3.59}/docs/tests/integration/sidebar.spec.ts +0 -0
  54. {dirsql-0.3.58 → dirsql-0.3.59}/docs/tests/unit/config.test.ts +0 -0
  55. {dirsql-0.3.58 → dirsql-0.3.59}/docs/tests/unit/lang.test.ts +0 -0
  56. {dirsql-0.3.58 → dirsql-0.3.59}/docs/vitest.config.ts +0 -0
  57. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/README.md +0 -0
  58. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/conftest.py +0 -0
  59. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/.claude/CLAUDE.md +0 -0
  60. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/.vitepress/config.ts +0 -0
  61. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/.vitepress/theme/index.ts +0 -0
  62. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
  63. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/AGENTS.md +0 -0
  64. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/api/index.md +0 -0
  65. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/cli/http-api.md +0 -0
  66. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/cli/index.md +0 -0
  67. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/cli/init.md +0 -0
  68. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/cli/server.md +0 -0
  69. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/getting-started.md +0 -0
  70. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/guide/async.md +0 -0
  71. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/guide/crdt.md +0 -0
  72. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/guide/persistence.md +0 -0
  73. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/guide/querying.md +0 -0
  74. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/guide/tables.md +0 -0
  75. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/guide/watching.md +0 -0
  76. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/index.md +0 -0
  77. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/migrations.md +0 -0
  78. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/package.json +0 -0
  79. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/playwright.config.ts +0 -0
  80. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/pnpm-lock.yaml +0 -0
  81. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/pnpm-workspace.yaml +0 -0
  82. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/tests/integration/home.spec.ts +0 -0
  83. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/tests/integration/language-flag.spec.ts +0 -0
  84. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/tests/integration/sidebar.spec.ts +0 -0
  85. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/tests/unit/config.test.ts +0 -0
  86. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/tests/unit/lang.test.ts +0 -0
  87. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/docs/vitest.config.ts +0 -0
  88. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/src/lib.rs +0 -0
  89. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/tests/__init__.py +0 -0
  90. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/tests/binding/__init__.py +0 -0
  91. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/tests/conftest.py +0 -0
  92. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/tests/e2e/__init__.py +0 -0
  93. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/tests/integration/__init__.py +0 -0
  94. {dirsql-0.3.58 → dirsql-0.3.59}/packages/python/tests/smoke/__init__.py +0 -0
  95. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/Cargo.toml +0 -0
  96. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/README.md +0 -0
  97. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/benches/db_bench.rs +0 -0
  98. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/benches/differ_bench.rs +0 -0
  99. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/benches/matcher_bench.rs +0 -0
  100. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/benches/scanner_bench.rs +0 -0
  101. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/api/index.md +0 -0
  102. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/cli/http-api.md +0 -0
  103. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/cli/index.md +0 -0
  104. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/cli/init.md +0 -0
  105. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/cli/server.md +0 -0
  106. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/getting-started.md +0 -0
  107. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/guide/async.md +0 -0
  108. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/guide/crdt.md +0 -0
  109. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/guide/persistence.md +0 -0
  110. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/guide/querying.md +0 -0
  111. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/guide/tables.md +0 -0
  112. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/guide/watching.md +0 -0
  113. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/index.md +0 -0
  114. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/docs/migrations.md +0 -0
  115. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/cli/init.rs +0 -0
  116. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/cli/serialize.rs +0 -0
  117. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/cli/server.rs +0 -0
  118. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/db.rs +0 -0
  119. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/differ.rs +0 -0
  120. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/matcher.rs +0 -0
  121. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/persist.rs +0 -0
  122. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/scanner.rs +0 -0
  123. {dirsql-0.3.58 → dirsql-0.3.59}/packages/rust/src/watcher.rs +0 -0
  124. {dirsql-0.3.58 → dirsql-0.3.59}/pyproject.toml +0 -0
@@ -500,7 +500,7 @@ dependencies = [
500
500
 
501
501
  [[package]]
502
502
  name = "dirsql-py-ext"
503
- version = "0.3.58"
503
+ version = "0.3.59"
504
504
  dependencies = [
505
505
  "dirsql",
506
506
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.3.58
3
+ Version: 0.3.59
4
4
  Requires-Dist: pytest>=8 ; extra == 'dev'
5
5
  Requires-Dist: pytest-describe>=2 ; extra == 'dev'
6
6
  Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
@@ -244,6 +244,13 @@ file is skipped (it contributes no rows) and a one-line warning naming the file
244
244
  and the error is written to stderr. One bad file never aborts the scan; the
245
245
  other files' rows are indexed normally.
246
246
 
247
+ **Timeout.** Each run is bounded by the default 30-second timeout. When a
248
+ command legitimately needs longer — it downloads a model on first run, or calls
249
+ a rate-limited API — raise the bound with the global
250
+ [`[dirsql].hook-timeout`](#command-execution) key (positive whole seconds). A
251
+ run exceeding the bound is killed and handled with the per-file error isolation
252
+ above (the file is skipped with a stderr warning).
253
+
247
254
  See [Command execution](#command-execution) for the full contract (argv
248
255
  splitting, injection safety, cwd, environment, timeout, and output framing).
249
256
 
@@ -279,7 +286,8 @@ key and the `{"sql": …}` contract returns.
279
286
  **On failure** — a non-zero exit, a timeout, or a spawn error — the request
280
287
  returns `500 Internal Server Error` with the command's stderr tail in the JSON
281
288
  `error` body. The command runs in the config file's directory and is bounded by
282
- a fixed **30-second** timeout.
289
+ a **30-second** timeout by default; a slow translator (e.g. LLM-backed) can
290
+ raise it with the global [`[dirsql].hook-timeout`](#command-execution) key.
283
291
 
284
292
  #### The hook owns SQL safety
285
293
 
@@ -361,7 +369,8 @@ returns.
361
369
  **On failure** — a non-zero exit, a timeout, or a spawn error — the request
362
370
  returns `500 Internal Server Error` with the command's stderr tail in the JSON
363
371
  `error` body. The command runs in the config file's directory and is bounded by
364
- a fixed **30-second** timeout.
372
+ a **30-second** timeout by default; raise it with the global
373
+ [`[dirsql].hook-timeout`](#command-execution) key.
365
374
 
366
375
  See [Command execution](#command-execution) for the full contract (argv
367
376
  splitting, injection safety, cwd, environment, timeout, and output framing).
@@ -409,9 +418,18 @@ Config keys that run an external command — today `on-file`, `pre-query`, and
409
418
  - **Output framing.** The command's result is the **last non-empty line of
410
419
  stdout**; any log/chatter lines above it are ignored. stderr is never data —
411
420
  it is captured only to enrich error messages.
412
- - **Timeout.** Each command run is bounded by a fixed **30-second** timeout (no
413
- per-table override yet); a command that exceeds it is killed and treated as a
414
- failure.
421
+ - **Timeout.** Each command run is bounded by a **30-second** timeout by
422
+ default; a command that exceeds it is killed and treated as a failure. Raise
423
+ (or tighten) it for **all** hooks — `on-file`, `pre-query`, and `post-query`
424
+ alike — with a single global `[dirsql].hook-timeout` key, in positive whole
425
+ seconds:
426
+
427
+ ```toml
428
+ [dirsql]
429
+ hook-timeout = 300
430
+ ```
431
+
432
+ Zero and negative values are rejected as a config error.
415
433
  - **Errors.** A non-zero exit, a timeout, a spawn failure, or output that does
416
434
  not parse as expected is a per-file failure: the file is skipped with a
417
435
  stderr warning and the scan continues.
@@ -4,7 +4,7 @@ name = "dirsql-py-ext"
4
4
  # pypi/maturin handler can rewrite it via `write-version` before
5
5
  # `maturin build`. `pyproject.toml` declares `dynamic = ["version"]`
6
6
  # and maturin reads this field. Mirrors `packages/rust/Cargo.toml`.
7
- version = "0.3.58"
7
+ version = "0.3.59"
8
8
  edition.workspace = true
9
9
  publish = false
10
10
  readme = "README.md"
@@ -244,6 +244,13 @@ file is skipped (it contributes no rows) and a one-line warning naming the file
244
244
  and the error is written to stderr. One bad file never aborts the scan; the
245
245
  other files' rows are indexed normally.
246
246
 
247
+ **Timeout.** Each run is bounded by the default 30-second timeout. When a
248
+ command legitimately needs longer — it downloads a model on first run, or calls
249
+ a rate-limited API — raise the bound with the global
250
+ [`[dirsql].hook-timeout`](#command-execution) key (positive whole seconds). A
251
+ run exceeding the bound is killed and handled with the per-file error isolation
252
+ above (the file is skipped with a stderr warning).
253
+
247
254
  See [Command execution](#command-execution) for the full contract (argv
248
255
  splitting, injection safety, cwd, environment, timeout, and output framing).
249
256
 
@@ -279,7 +286,8 @@ key and the `{"sql": …}` contract returns.
279
286
  **On failure** — a non-zero exit, a timeout, or a spawn error — the request
280
287
  returns `500 Internal Server Error` with the command's stderr tail in the JSON
281
288
  `error` body. The command runs in the config file's directory and is bounded by
282
- a fixed **30-second** timeout.
289
+ a **30-second** timeout by default; a slow translator (e.g. LLM-backed) can
290
+ raise it with the global [`[dirsql].hook-timeout`](#command-execution) key.
283
291
 
284
292
  #### The hook owns SQL safety
285
293
 
@@ -361,7 +369,8 @@ returns.
361
369
  **On failure** — a non-zero exit, a timeout, or a spawn error — the request
362
370
  returns `500 Internal Server Error` with the command's stderr tail in the JSON
363
371
  `error` body. The command runs in the config file's directory and is bounded by
364
- a fixed **30-second** timeout.
372
+ a **30-second** timeout by default; raise it with the global
373
+ [`[dirsql].hook-timeout`](#command-execution) key.
365
374
 
366
375
  See [Command execution](#command-execution) for the full contract (argv
367
376
  splitting, injection safety, cwd, environment, timeout, and output framing).
@@ -409,9 +418,18 @@ Config keys that run an external command — today `on-file`, `pre-query`, and
409
418
  - **Output framing.** The command's result is the **last non-empty line of
410
419
  stdout**; any log/chatter lines above it are ignored. stderr is never data —
411
420
  it is captured only to enrich error messages.
412
- - **Timeout.** Each command run is bounded by a fixed **30-second** timeout (no
413
- per-table override yet); a command that exceeds it is killed and treated as a
414
- failure.
421
+ - **Timeout.** Each command run is bounded by a **30-second** timeout by
422
+ default; a command that exceeds it is killed and treated as a failure. Raise
423
+ (or tighten) it for **all** hooks — `on-file`, `pre-query`, and `post-query`
424
+ alike — with a single global `[dirsql].hook-timeout` key, in positive whole
425
+ seconds:
426
+
427
+ ```toml
428
+ [dirsql]
429
+ hook-timeout = 300
430
+ ```
431
+
432
+ Zero and negative values are rejected as a config error.
415
433
  - **Errors.** A non-zero exit, a timeout, a spawn failure, or output that does
416
434
  not parse as expected is a per-file failure: the file is skipped with a
417
435
  stderr warning and the scan continues.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "command": "uv run python -m pytest tests/e2e/ -x -q",
3
- "ran_at": 1783118080,
3
+ "ran_at": 1783120355,
4
4
  "exit_code": 0,
5
- "commit": "b45791b9ad04fbac360618f3ff885e5f84a8289e"
5
+ "commit": "c460b738d83c83b4c76b10cd967e3641cbe35c7c"
6
6
  }
@@ -244,6 +244,13 @@ file is skipped (it contributes no rows) and a one-line warning naming the file
244
244
  and the error is written to stderr. One bad file never aborts the scan; the
245
245
  other files' rows are indexed normally.
246
246
 
247
+ **Timeout.** Each run is bounded by the default 30-second timeout. When a
248
+ command legitimately needs longer — it downloads a model on first run, or calls
249
+ a rate-limited API — raise the bound with the global
250
+ [`[dirsql].hook-timeout`](#command-execution) key (positive whole seconds). A
251
+ run exceeding the bound is killed and handled with the per-file error isolation
252
+ above (the file is skipped with a stderr warning).
253
+
247
254
  See [Command execution](#command-execution) for the full contract (argv
248
255
  splitting, injection safety, cwd, environment, timeout, and output framing).
249
256
 
@@ -279,7 +286,8 @@ key and the `{"sql": …}` contract returns.
279
286
  **On failure** — a non-zero exit, a timeout, or a spawn error — the request
280
287
  returns `500 Internal Server Error` with the command's stderr tail in the JSON
281
288
  `error` body. The command runs in the config file's directory and is bounded by
282
- a fixed **30-second** timeout.
289
+ a **30-second** timeout by default; a slow translator (e.g. LLM-backed) can
290
+ raise it with the global [`[dirsql].hook-timeout`](#command-execution) key.
283
291
 
284
292
  #### The hook owns SQL safety
285
293
 
@@ -361,7 +369,8 @@ returns.
361
369
  **On failure** — a non-zero exit, a timeout, or a spawn error — the request
362
370
  returns `500 Internal Server Error` with the command's stderr tail in the JSON
363
371
  `error` body. The command runs in the config file's directory and is bounded by
364
- a fixed **30-second** timeout.
372
+ a **30-second** timeout by default; raise it with the global
373
+ [`[dirsql].hook-timeout`](#command-execution) key.
365
374
 
366
375
  See [Command execution](#command-execution) for the full contract (argv
367
376
  splitting, injection safety, cwd, environment, timeout, and output framing).
@@ -409,9 +418,18 @@ Config keys that run an external command — today `on-file`, `pre-query`, and
409
418
  - **Output framing.** The command's result is the **last non-empty line of
410
419
  stdout**; any log/chatter lines above it are ignored. stderr is never data —
411
420
  it is captured only to enrich error messages.
412
- - **Timeout.** Each command run is bounded by a fixed **30-second** timeout (no
413
- per-table override yet); a command that exceeds it is killed and treated as a
414
- failure.
421
+ - **Timeout.** Each command run is bounded by a **30-second** timeout by
422
+ default; a command that exceeds it is killed and treated as a failure. Raise
423
+ (or tighten) it for **all** hooks — `on-file`, `pre-query`, and `post-query`
424
+ alike — with a single global `[dirsql].hook-timeout` key, in positive whole
425
+ seconds:
426
+
427
+ ```toml
428
+ [dirsql]
429
+ hook-timeout = 300
430
+ ```
431
+
432
+ Zero and negative values are rejected as a config error.
415
433
  - **Errors.** A non-zero exit, a timeout, a spawn failure, or output that does
416
434
  not parse as expected is a per-file failure: the file is skipped with a
417
435
  stderr warning and the scan continues.
@@ -228,9 +228,14 @@ fn load_pre_query(cli: &Cli) -> Option<PreQuery> {
228
228
  return None;
229
229
  }
230
230
  let resolved = config_path.canonicalize().ok()?;
231
- let command = dirsql::config::load_config(&resolved).ok()?.pre_query?;
231
+ let config = dirsql::config::load_config(&resolved).ok()?;
232
+ let command = config.pre_query?;
232
233
  let config_dir = resolved.parent()?.to_path_buf();
233
- Some(PreQuery::new(command, config_dir))
234
+ let mut pre_query = PreQuery::new(command, config_dir);
235
+ if let Some(timeout) = config.hook_timeout {
236
+ pre_query = pre_query.with_timeout(timeout);
237
+ }
238
+ Some(pre_query)
234
239
  }
235
240
 
236
241
  /// Extract the server-wide `post-query` hook from the config, if any.
@@ -245,9 +250,14 @@ fn load_post_query(cli: &Cli) -> Option<PostQuery> {
245
250
  return None;
246
251
  }
247
252
  let resolved = config_path.canonicalize().ok()?;
248
- let command = dirsql::config::load_config(&resolved).ok()?.post_query?;
253
+ let config = dirsql::config::load_config(&resolved).ok()?;
254
+ let command = config.post_query?;
249
255
  let config_dir = resolved.parent()?.to_path_buf();
250
- Some(PostQuery::new(command, config_dir))
256
+ let mut post_query = PostQuery::new(command, config_dir);
257
+ if let Some(timeout) = config.hook_timeout {
258
+ post_query = post_query.with_timeout(timeout);
259
+ }
260
+ Some(post_query)
251
261
  }
252
262
 
253
263
  /// Zero-config fallback. When no `.dirsql.toml` is found, dirsql indexes the
@@ -27,6 +27,7 @@ use tokio::task::JoinError;
27
27
  use tokio::task::JoinHandle;
28
28
 
29
29
  use crate::DirSQL;
30
+ use crate::command::DEFAULT_COMMAND_TIMEOUT;
30
31
 
31
32
  pub mod init;
32
33
  pub mod router;
@@ -51,16 +52,29 @@ pub struct PreQuery {
51
52
  pub command: String,
52
53
  /// The command's working directory — the config file's parent.
53
54
  pub config_dir: PathBuf,
55
+ /// Per-run timeout. Defaults to the shared 30-second
56
+ /// [`DEFAULT_COMMAND_TIMEOUT`]; override it via [`Self::with_timeout`]
57
+ /// (the CLI wires the global `[dirsql].hook-timeout` here, #351).
58
+ pub timeout: Duration,
54
59
  }
55
60
 
56
61
  impl PreQuery {
57
- /// Build a [`PreQuery`] from a command template and its working directory.
62
+ /// Build a [`PreQuery`] from a command template and its working directory,
63
+ /// with the default 30-second timeout.
58
64
  pub fn new(command: impl Into<String>, config_dir: impl Into<PathBuf>) -> Self {
59
65
  Self {
60
66
  command: command.into(),
61
67
  config_dir: config_dir.into(),
68
+ timeout: DEFAULT_COMMAND_TIMEOUT,
62
69
  }
63
70
  }
71
+
72
+ /// Override the per-run timeout (from the global `[dirsql].hook-timeout`).
73
+ /// A run exceeding it is killed and the request returns 500.
74
+ pub fn with_timeout(mut self, timeout: Duration) -> Self {
75
+ self.timeout = timeout;
76
+ self
77
+ }
64
78
  }
65
79
 
66
80
  /// A server-wide `post-query` command hook, carrying the command template plus
@@ -76,16 +90,29 @@ pub struct PostQuery {
76
90
  pub command: String,
77
91
  /// The command's working directory — the config file's parent.
78
92
  pub config_dir: PathBuf,
93
+ /// Per-run timeout. Defaults to the shared 30-second
94
+ /// [`DEFAULT_COMMAND_TIMEOUT`]; override it via [`Self::with_timeout`]
95
+ /// (the CLI wires the global `[dirsql].hook-timeout` here, #351).
96
+ pub timeout: Duration,
79
97
  }
80
98
 
81
99
  impl PostQuery {
82
- /// Build a [`PostQuery`] from a command template and its working directory.
100
+ /// Build a [`PostQuery`] from a command template and its working directory,
101
+ /// with the default 30-second timeout.
83
102
  pub fn new(command: impl Into<String>, config_dir: impl Into<PathBuf>) -> Self {
84
103
  Self {
85
104
  command: command.into(),
86
105
  config_dir: config_dir.into(),
106
+ timeout: DEFAULT_COMMAND_TIMEOUT,
87
107
  }
88
108
  }
109
+
110
+ /// Override the per-run timeout (from the global `[dirsql].hook-timeout`).
111
+ /// A run exceeding it is killed and the request returns 500.
112
+ pub fn with_timeout(mut self, timeout: Duration) -> Self {
113
+ self.timeout = timeout;
114
+ self
115
+ }
89
116
  }
90
117
 
91
118
  /// Configure how the server binds. Defaults to `localhost:7117` with a
@@ -258,10 +285,17 @@ mod tests {
258
285
  #[test]
259
286
  fn pre_query_constructor_carries_command_and_dir() {
260
287
  // `PreQuery::new` is pure data plumbing: the command template and the
261
- // working directory it will run in.
288
+ // working directory it will run in, with the shared default timeout.
262
289
  let pq = PreQuery::new("to_sql.py {args}", "/proj");
263
290
  assert_eq!(pq.command, "to_sql.py {args}");
264
291
  assert_eq!(pq.config_dir, PathBuf::from("/proj"));
292
+ assert_eq!(pq.timeout, Duration::from_secs(30));
293
+ }
294
+
295
+ #[test]
296
+ fn pre_query_with_timeout_overrides_the_default() {
297
+ let pq = PreQuery::new("cmd {args}", "/proj").with_timeout(Duration::from_secs(60));
298
+ assert_eq!(pq.timeout, Duration::from_secs(60));
265
299
  }
266
300
 
267
301
  #[test]
@@ -275,10 +309,17 @@ mod tests {
275
309
  #[test]
276
310
  fn post_query_constructor_carries_command_and_dir() {
277
311
  // `PostQuery::new` is pure data plumbing: the command template and the
278
- // working directory it will run in.
312
+ // working directory it will run in, with the shared default timeout.
279
313
  let pq = PostQuery::new("jq '{results: .}'", "/proj");
280
314
  assert_eq!(pq.command, "jq '{results: .}'");
281
315
  assert_eq!(pq.config_dir, PathBuf::from("/proj"));
316
+ assert_eq!(pq.timeout, Duration::from_secs(30));
317
+ }
318
+
319
+ #[test]
320
+ fn post_query_with_timeout_overrides_the_default() {
321
+ let pq = PostQuery::new("reshape {args}", "/proj").with_timeout(Duration::from_secs(60));
322
+ assert_eq!(pq.timeout, Duration::from_secs(60));
282
323
  }
283
324
 
284
325
  #[test]
@@ -21,15 +21,6 @@ use super::{AppState, PostQuery, PreQuery};
21
21
  use crate::command::{Placeholder, run_command};
22
22
  use crate::{DirSQL, DirSqlError};
23
23
 
24
- /// Fixed timeout for a server-wide `pre-query` command. There is no override
25
- /// key yet; this module constant is the documented current default (mirrors
26
- /// `on-file`'s `ON_FILE_TIMEOUT`).
27
- const PRE_QUERY_TIMEOUT: Duration = Duration::from_secs(30);
28
-
29
- /// Fixed timeout for a server-wide `post-query` command (mirrors
30
- /// `PRE_QUERY_TIMEOUT`).
31
- const POST_QUERY_TIMEOUT: Duration = Duration::from_secs(30);
32
-
33
24
  /// Cap on the serialized result payload passed as the `{args}` argv token.
34
25
  /// Beyond this, `{args}` is emptied and the operator is directed to stdin
35
26
  /// (which always carries the full payload) — comfortably under Linux's 128 KiB
@@ -155,15 +146,17 @@ fn parse_sql_body(body: &str) -> Result<String, Response> {
155
146
  async fn run_pre_query(pq: &PreQuery, raw_body: String) -> Result<String, Response> {
156
147
  let command = pq.command.clone();
157
148
  let config_dir = pq.config_dir.clone();
149
+ let timeout = pq.timeout;
158
150
  // `run_command` is blocking — it spawns a child and joins drain threads —
159
- // so run it off the async runtime. It enforces `PRE_QUERY_TIMEOUT`
160
- // internally, so no outer `tokio::time::timeout` is needed.
151
+ // so run it off the async runtime. It enforces the hook's timeout
152
+ // (the global `[dirsql].hook-timeout`, default 30s) internally, so no outer
153
+ // `tokio::time::timeout` is needed.
161
154
  let outcome = tokio::task::spawn_blocking(move || {
162
155
  run_command(
163
156
  &command,
164
157
  &[Placeholder::new("args", &raw_body)],
165
158
  &config_dir,
166
- PRE_QUERY_TIMEOUT,
159
+ timeout,
167
160
  None,
168
161
  )
169
162
  })
@@ -198,9 +191,11 @@ async fn run_post_query(
198
191
  .map_err(|err| error_response(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?;
199
192
  let command = pq.command.clone();
200
193
  let config_dir = pq.config_dir.clone();
194
+ let timeout = pq.timeout;
201
195
  // `run_command` is blocking — it spawns a child and joins drain threads —
202
- // so run it off the async runtime. It enforces `POST_QUERY_TIMEOUT`
203
- // internally, so no outer `tokio::time::timeout` is needed.
196
+ // so run it off the async runtime. It enforces the hook's timeout
197
+ // (the global `[dirsql].hook-timeout`, default 30s) internally, so no outer
198
+ // `tokio::time::timeout` is needed.
204
199
  let outcome = tokio::task::spawn_blocking(move || {
205
200
  let args_value = if payload.len() <= POST_QUERY_ARGS_MAX {
206
201
  payload.clone()
@@ -217,7 +212,7 @@ async fn run_post_query(
217
212
  &command,
218
213
  &[Placeholder::new("args", &args_value)],
219
214
  &config_dir,
220
- POST_QUERY_TIMEOUT,
215
+ timeout,
221
216
  Some(payload.as_bytes()),
222
217
  )
223
218
  })
@@ -36,6 +36,11 @@ use std::time::Duration;
36
36
 
37
37
  use wait_timeout::ChildExt;
38
38
 
39
+ /// The default timeout for every command-backed event (`on-file`,
40
+ /// `pre-query`, `post-query`) when the config declares no override
41
+ /// (`[dirsql].hook-timeout`, positive whole seconds).
42
+ pub const DEFAULT_COMMAND_TIMEOUT: Duration = Duration::from_secs(30);
43
+
39
44
  /// A named placeholder substituted into a command's argv.
40
45
  ///
41
46
  /// `name` is the bare identifier (no braces): a `name` of `path` matches the
@@ -1,4 +1,5 @@
1
1
  use std::path::{Path, PathBuf};
2
+ use std::time::Duration;
2
3
 
3
4
  use serde::Deserialize;
4
5
 
@@ -20,6 +21,9 @@ pub enum ConfigError {
20
21
  #[error("Field '{0}' must not be empty")]
21
22
  EmptyField(&'static str),
22
23
 
24
+ #[error("Field '{field}' must be a positive number of seconds, got {value}")]
25
+ InvalidTimeout { field: &'static str, value: i64 },
26
+
23
27
  #[error("Cannot combine an empty list of configs")]
24
28
  NoConfigs,
25
29
 
@@ -70,7 +74,7 @@ impl std::fmt::Display for Source {
70
74
  /// returned unchanged. List-shaped config (`[[table]]`, `[[dirsql.extension]]`,
71
75
  /// `ignore`) concatenates in input order. A table-name collision anywhere in
72
76
  /// the combined set errors, naming both sources. Single-valued keys (`root`,
73
- /// `persist`, `persist_path`, `pre-query`, `post-query`) defined by more than
77
+ /// `persist`, `persist_path`, `pre-query`, `post-query`, `hook-timeout`) defined by more than
74
78
  /// one config error, naming both sources — no silent shadowing, no precedence;
75
79
  /// defined in exactly one config they merge through unchanged. (`persist`
76
80
  /// counts as defined only when `true`: the parsed [`Config`] cannot
@@ -100,6 +104,7 @@ pub fn combine_configs(configs: &[(Source, Config)]) -> Result<Config> {
100
104
  let mut persist_path: Option<(&Source, PathBuf)> = None;
101
105
  let mut pre_query: Option<(&Source, String)> = None;
102
106
  let mut post_query: Option<(&Source, String)> = None;
107
+ let mut hook_timeout: Option<(&Source, Duration)> = None;
103
108
 
104
109
  for (source, config) in configs {
105
110
  for table in &config.tables {
@@ -136,6 +141,12 @@ pub fn combine_configs(configs: &[(Source, Config)]) -> Result<Config> {
136
141
  config.post_query.as_ref(),
137
142
  source,
138
143
  )?;
144
+ merge_single(
145
+ "hook-timeout",
146
+ &mut hook_timeout,
147
+ config.hook_timeout.as_ref(),
148
+ source,
149
+ )?;
139
150
  if config.persist {
140
151
  if let Some(prior) = persist {
141
152
  return Err(ConfigError::ConflictingKey {
@@ -157,6 +168,7 @@ pub fn combine_configs(configs: &[(Source, Config)]) -> Result<Config> {
157
168
  extensions,
158
169
  pre_query: pre_query.map(|(_, value)| value),
159
170
  post_query: post_query.map(|(_, value)| value),
171
+ hook_timeout: hook_timeout.map(|(_, value)| value),
160
172
  })
161
173
  }
162
174
 
@@ -216,6 +228,11 @@ pub struct Config {
216
228
  /// the rows as-is. See `dirsql::command` for the execution contract. Only
217
229
  /// the CLI server consults this; the SDK ignores it.
218
230
  pub post_query: Option<String>,
231
+ /// Optional timeout for every command-backed hook — `on-file`, `pre-query`,
232
+ /// and `post-query` alike (`[dirsql].hook-timeout`, positive seconds). One
233
+ /// global bound rather than a per-hook knob. When absent, hooks fall back to
234
+ /// the shared 30-second default ([`crate::command::DEFAULT_COMMAND_TIMEOUT`]).
235
+ pub hook_timeout: Option<Duration>,
219
236
  }
220
237
 
221
238
  /// A SQLite extension to load at startup.
@@ -265,7 +282,7 @@ struct RawConfig {
265
282
  table: Option<Vec<RawTable>>,
266
283
  }
267
284
 
268
- #[derive(Deserialize)]
285
+ #[derive(Deserialize, Default)]
269
286
  struct RawDirsql {
270
287
  root: Option<PathBuf>,
271
288
  ignore: Option<Vec<String>>,
@@ -276,6 +293,8 @@ struct RawDirsql {
276
293
  pre_query: Option<String>,
277
294
  #[serde(rename = "post-query")]
278
295
  post_query: Option<String>,
296
+ #[serde(rename = "hook-timeout")]
297
+ hook_timeout: Option<i64>,
279
298
  }
280
299
 
281
300
  #[derive(Deserialize)]
@@ -303,19 +322,16 @@ pub fn load_config(path: &Path) -> Result<Config> {
303
322
  pub fn load_config_str(content: &str) -> Result<Config> {
304
323
  let raw: RawConfig = toml::from_str(content)?;
305
324
 
306
- let (root, ignore, persist, persist_path, raw_extensions, raw_pre_query, raw_post_query) =
307
- match raw.dirsql {
308
- Some(d) => (
309
- d.root,
310
- d.ignore.unwrap_or_default(),
311
- d.persist.unwrap_or(false),
312
- d.persist_path,
313
- d.extension.unwrap_or_default(),
314
- d.pre_query,
315
- d.post_query,
316
- ),
317
- None => (None, Vec::new(), false, None, Vec::new(), None, None),
318
- };
325
+ // A missing `[dirsql]` section behaves as an all-defaults one.
326
+ let d = raw.dirsql.unwrap_or_default();
327
+ let root = d.root;
328
+ let ignore = d.ignore.unwrap_or_default();
329
+ let persist = d.persist.unwrap_or(false);
330
+ let persist_path = d.persist_path;
331
+ let raw_extensions = d.extension.unwrap_or_default();
332
+ let raw_pre_query = d.pre_query;
333
+ let raw_post_query = d.post_query;
334
+ let hook_timeout = parse_timeout_secs("hook-timeout", d.hook_timeout)?;
319
335
 
320
336
  // A present-but-empty `pre-query = ""` is as unusable as a missing key:
321
337
  // reject it at parse time rather than spawning an empty command later
@@ -384,9 +400,23 @@ pub fn load_config_str(content: &str) -> Result<Config> {
384
400
  extensions,
385
401
  pre_query,
386
402
  post_query,
403
+ hook_timeout,
387
404
  })
388
405
  }
389
406
 
407
+ /// Validate an optional timeout config value (whole seconds) and convert it
408
+ /// to a [`Duration`]. Zero and negative values are as unusable as a garbage
409
+ /// string — reject them at parse time with the offending field's name rather
410
+ /// than silently producing a command that can never run (`0`) or a bogus
411
+ /// cast (negative).
412
+ fn parse_timeout_secs(field: &'static str, raw: Option<i64>) -> Result<Option<Duration>> {
413
+ match raw {
414
+ Some(secs) if secs <= 0 => Err(ConfigError::InvalidTimeout { field, value: secs }),
415
+ Some(secs) => Ok(Some(Duration::from_secs(secs as u64))),
416
+ None => Ok(None),
417
+ }
418
+ }
419
+
390
420
  #[cfg(test)]
391
421
  mod tests {
392
422
  use super::*;
@@ -1141,6 +1171,79 @@ glob = "c/*.json"
1141
1171
  );
1142
1172
  }
1143
1173
 
1174
+ #[test]
1175
+ fn hook_timeout_parses_to_duration_seconds() {
1176
+ let toml = r#"
1177
+ [dirsql]
1178
+ hook-timeout = 300
1179
+ "#;
1180
+ let config = load_config_str(toml).unwrap();
1181
+ assert_eq!(config.hook_timeout, Some(Duration::from_secs(300)));
1182
+ }
1183
+
1184
+ #[test]
1185
+ fn hook_timeout_absent_is_none() {
1186
+ let toml = r#"
1187
+ [[table]]
1188
+ ddl = "CREATE TABLE t (x TEXT)"
1189
+ glob = "*.json"
1190
+ "#;
1191
+ let config = load_config_str(toml).unwrap();
1192
+ assert!(config.hook_timeout.is_none());
1193
+ }
1194
+
1195
+ #[test]
1196
+ fn hook_timeout_zero_errors() {
1197
+ let toml = r#"
1198
+ [dirsql]
1199
+ hook-timeout = 0
1200
+ "#;
1201
+ let err = load_config_str(toml).unwrap_err();
1202
+ assert!(
1203
+ matches!(
1204
+ err,
1205
+ ConfigError::InvalidTimeout {
1206
+ field: "hook-timeout",
1207
+ value: 0
1208
+ }
1209
+ ),
1210
+ "got: {err:?}"
1211
+ );
1212
+ }
1213
+
1214
+ #[test]
1215
+ fn hook_timeout_negative_errors() {
1216
+ let toml = r#"
1217
+ [dirsql]
1218
+ hook-timeout = -5
1219
+ "#;
1220
+ let err = load_config_str(toml).unwrap_err();
1221
+ assert!(
1222
+ matches!(
1223
+ err,
1224
+ ConfigError::InvalidTimeout {
1225
+ field: "hook-timeout",
1226
+ value: -5
1227
+ }
1228
+ ),
1229
+ "got: {err:?}"
1230
+ );
1231
+ }
1232
+
1233
+ #[test]
1234
+ fn invalid_timeout_error_names_the_field_and_value() {
1235
+ // The message is the user's only pointer to the bad key — it must name
1236
+ // both the field and the offending value.
1237
+ let err = ConfigError::InvalidTimeout {
1238
+ field: "hook-timeout",
1239
+ value: -1,
1240
+ };
1241
+ assert_eq!(
1242
+ err.to_string(),
1243
+ "Field 'hook-timeout' must be a positive number of seconds, got -1"
1244
+ );
1245
+ }
1246
+
1144
1247
  #[test]
1145
1248
  fn extension_empty_path_errors() {
1146
1249
  // An empty `path = ""` is as unusable as a missing key — it must be
@@ -1457,10 +1457,6 @@ fn relative_path(root: &Path, path: &Path) -> String {
1457
1457
  .to_string()
1458
1458
  }
1459
1459
 
1460
- /// Fixed timeout for an `on-file` command. There is no per-table timeout key
1461
- /// yet (#327); this module constant is the documented current default.
1462
- const ON_FILE_TIMEOUT: Duration = Duration::from_secs(30);
1463
-
1464
1460
  /// Build [`Table`] objects from a parsed config.
1465
1461
  ///
1466
1462
  /// A plain config-defined table produces one row per matched file built
@@ -1475,13 +1471,17 @@ const ON_FILE_TIMEOUT: Duration = Duration::from_secs(30);
1475
1471
  /// array of row objects on stdout, which becomes the file's rows (filesystem
1476
1472
  /// facts are still merged on top, user values winning). `config_dir` is the
1477
1473
  /// command's working directory (the config file's parent) and `root` is the
1478
- /// resolved index root used to compute the `{path}` placeholder.
1474
+ /// resolved index root used to compute the `{path}` placeholder. Each run is
1475
+ /// bounded by the global `[dirsql].hook-timeout` key when present (#351),
1476
+ /// falling back to the shared 30-second default
1477
+ /// ([`command::DEFAULT_COMMAND_TIMEOUT`]).
1479
1478
  fn build_tables_from_config(
1480
1479
  cfg: &config::Config,
1481
1480
  config_dir: &Path,
1482
1481
  root: &Path,
1483
1482
  ) -> Result<Vec<Table>> {
1484
1483
  let mut tables = Vec::with_capacity(cfg.tables.len());
1484
+ let timeout = cfg.hook_timeout.unwrap_or(command::DEFAULT_COMMAND_TIMEOUT);
1485
1485
 
1486
1486
  for table_cfg in &cfg.tables {
1487
1487
  let mut table = match &table_cfg.on_file {
@@ -1495,7 +1495,9 @@ fn build_tables_from_config(
1495
1495
  Table::new(
1496
1496
  table_cfg.ddl.clone(),
1497
1497
  table_cfg.glob.clone(),
1498
- move |abs_path: &str| run_on_file(&command, abs_path, &config_dir, &root),
1498
+ move |abs_path: &str| {
1499
+ run_on_file(&command, abs_path, &config_dir, &root, timeout)
1500
+ },
1499
1501
  )
1500
1502
  }
1501
1503
  None => Table::new(
@@ -1527,8 +1529,15 @@ fn build_tables_from_config(
1527
1529
  /// Per-file isolation: any failure — a spawn/exit/timeout error from
1528
1530
  /// [`command::run_command`], or output that is not a JSON array of objects —
1529
1531
  /// is logged to stderr and yields no rows (`vec![]`). Returning `Err` here
1530
- /// would abort the whole scan, so it never does.
1531
- fn run_on_file(command: &str, abs_path: &str, config_dir: &Path, root: &Path) -> Vec<Row> {
1532
+ /// would abort the whole scan, so it never does. `timeout` bounds the run —
1533
+ /// the table's configured value, or the shared default.
1534
+ fn run_on_file(
1535
+ command: &str,
1536
+ abs_path: &str,
1537
+ config_dir: &Path,
1538
+ root: &Path,
1539
+ timeout: Duration,
1540
+ ) -> Vec<Row> {
1532
1541
  let abs = Path::new(abs_path);
1533
1542
  let rel = abs
1534
1543
  .strip_prefix(root)
@@ -1540,7 +1549,7 @@ fn run_on_file(command: &str, abs_path: &str, config_dir: &Path, root: &Path) ->
1540
1549
  Placeholder::new("root", root.to_string_lossy().into_owned()),
1541
1550
  ];
1542
1551
 
1543
- match command::run_command(command, &placeholders, config_dir, ON_FILE_TIMEOUT, None) {
1552
+ match command::run_command(command, &placeholders, config_dir, timeout, None) {
1544
1553
  Ok(output) => match parse_command_rows(&output.payload) {
1545
1554
  Ok(rows) => rows,
1546
1555
  Err(message) => {
@@ -3097,6 +3106,7 @@ mod internal_tests {
3097
3106
  &abs.to_string_lossy(),
3098
3107
  dir.path(),
3099
3108
  dir.path(),
3109
+ command::DEFAULT_COMMAND_TIMEOUT,
3100
3110
  );
3101
3111
  assert_eq!(rows.len(), 1);
3102
3112
  assert_eq!(rows[0]["n"], Value::Integer(1));
@@ -3112,6 +3122,7 @@ mod internal_tests {
3112
3122
  "/outside/f.txt",
3113
3123
  dir.path(),
3114
3124
  dir.path(),
3125
+ command::DEFAULT_COMMAND_TIMEOUT,
3115
3126
  );
3116
3127
  assert!(rows.is_empty());
3117
3128
  }
@@ -3120,7 +3131,13 @@ mod internal_tests {
3120
3131
  #[test]
3121
3132
  fn run_on_file_returns_no_rows_on_non_json_output() {
3122
3133
  let dir = TempDir::new().unwrap();
3123
- let rows = run_on_file("echo not-json", "/outside/f.txt", dir.path(), dir.path());
3134
+ let rows = run_on_file(
3135
+ "echo not-json",
3136
+ "/outside/f.txt",
3137
+ dir.path(),
3138
+ dir.path(),
3139
+ command::DEFAULT_COMMAND_TIMEOUT,
3140
+ );
3124
3141
  assert!(rows.is_empty());
3125
3142
  }
3126
3143
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes