gitx.do 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -353
- package/dist/do/logger.d.ts +50 -0
- package/dist/do/logger.d.ts.map +1 -0
- package/dist/do/logger.js +122 -0
- package/dist/do/logger.js.map +1 -0
- package/dist/{durable-object → do}/schema.d.ts +3 -3
- package/dist/do/schema.d.ts.map +1 -0
- package/dist/{durable-object → do}/schema.js +4 -3
- package/dist/do/schema.js.map +1 -0
- package/dist/do/types.d.ts +267 -0
- package/dist/do/types.d.ts.map +1 -0
- package/dist/do/types.js +62 -0
- package/dist/do/types.js.map +1 -0
- package/dist/index.d.ts +15 -415
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -483
- package/dist/index.js.map +1 -1
- package/package.json +13 -21
- package/dist/cli/commands/add.d.ts +0 -174
- package/dist/cli/commands/add.d.ts.map +0 -1
- package/dist/cli/commands/add.js +0 -131
- package/dist/cli/commands/add.js.map +0 -1
- package/dist/cli/commands/blame.d.ts +0 -259
- package/dist/cli/commands/blame.d.ts.map +0 -1
- package/dist/cli/commands/blame.js +0 -609
- package/dist/cli/commands/blame.js.map +0 -1
- package/dist/cli/commands/branch.d.ts +0 -249
- package/dist/cli/commands/branch.d.ts.map +0 -1
- package/dist/cli/commands/branch.js +0 -693
- package/dist/cli/commands/branch.js.map +0 -1
- package/dist/cli/commands/commit.d.ts +0 -182
- package/dist/cli/commands/commit.d.ts.map +0 -1
- package/dist/cli/commands/commit.js +0 -437
- package/dist/cli/commands/commit.js.map +0 -1
- package/dist/cli/commands/diff.d.ts +0 -464
- package/dist/cli/commands/diff.d.ts.map +0 -1
- package/dist/cli/commands/diff.js +0 -958
- package/dist/cli/commands/diff.js.map +0 -1
- package/dist/cli/commands/log.d.ts +0 -239
- package/dist/cli/commands/log.d.ts.map +0 -1
- package/dist/cli/commands/log.js +0 -535
- package/dist/cli/commands/log.js.map +0 -1
- package/dist/cli/commands/merge.d.ts +0 -106
- package/dist/cli/commands/merge.d.ts.map +0 -1
- package/dist/cli/commands/merge.js +0 -55
- package/dist/cli/commands/merge.js.map +0 -1
- package/dist/cli/commands/review.d.ts +0 -457
- package/dist/cli/commands/review.d.ts.map +0 -1
- package/dist/cli/commands/review.js +0 -533
- package/dist/cli/commands/review.js.map +0 -1
- package/dist/cli/commands/status.d.ts +0 -269
- package/dist/cli/commands/status.d.ts.map +0 -1
- package/dist/cli/commands/status.js +0 -493
- package/dist/cli/commands/status.js.map +0 -1
- package/dist/cli/commands/web.d.ts +0 -199
- package/dist/cli/commands/web.d.ts.map +0 -1
- package/dist/cli/commands/web.js +0 -696
- package/dist/cli/commands/web.js.map +0 -1
- package/dist/cli/fs-adapter.d.ts +0 -656
- package/dist/cli/fs-adapter.d.ts.map +0 -1
- package/dist/cli/fs-adapter.js +0 -1179
- package/dist/cli/fs-adapter.js.map +0 -1
- package/dist/cli/fsx-cli-adapter.d.ts +0 -359
- package/dist/cli/fsx-cli-adapter.d.ts.map +0 -1
- package/dist/cli/fsx-cli-adapter.js +0 -619
- package/dist/cli/fsx-cli-adapter.js.map +0 -1
- package/dist/cli/index.d.ts +0 -387
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -523
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/ui/components/DiffView.d.ts +0 -7
- package/dist/cli/ui/components/DiffView.d.ts.map +0 -1
- package/dist/cli/ui/components/DiffView.js +0 -11
- package/dist/cli/ui/components/DiffView.js.map +0 -1
- package/dist/cli/ui/components/ErrorDisplay.d.ts +0 -6
- package/dist/cli/ui/components/ErrorDisplay.d.ts.map +0 -1
- package/dist/cli/ui/components/ErrorDisplay.js +0 -11
- package/dist/cli/ui/components/ErrorDisplay.js.map +0 -1
- package/dist/cli/ui/components/FuzzySearch.d.ts +0 -9
- package/dist/cli/ui/components/FuzzySearch.d.ts.map +0 -1
- package/dist/cli/ui/components/FuzzySearch.js +0 -12
- package/dist/cli/ui/components/FuzzySearch.js.map +0 -1
- package/dist/cli/ui/components/LoadingSpinner.d.ts +0 -6
- package/dist/cli/ui/components/LoadingSpinner.d.ts.map +0 -1
- package/dist/cli/ui/components/LoadingSpinner.js +0 -10
- package/dist/cli/ui/components/LoadingSpinner.js.map +0 -1
- package/dist/cli/ui/components/NavigationList.d.ts +0 -9
- package/dist/cli/ui/components/NavigationList.d.ts.map +0 -1
- package/dist/cli/ui/components/NavigationList.js +0 -11
- package/dist/cli/ui/components/NavigationList.js.map +0 -1
- package/dist/cli/ui/components/ScrollableContent.d.ts +0 -8
- package/dist/cli/ui/components/ScrollableContent.d.ts.map +0 -1
- package/dist/cli/ui/components/ScrollableContent.js +0 -11
- package/dist/cli/ui/components/ScrollableContent.js.map +0 -1
- package/dist/cli/ui/components/index.d.ts +0 -7
- package/dist/cli/ui/components/index.d.ts.map +0 -1
- package/dist/cli/ui/components/index.js +0 -9
- package/dist/cli/ui/components/index.js.map +0 -1
- package/dist/cli/ui/terminal-ui.d.ts +0 -52
- package/dist/cli/ui/terminal-ui.d.ts.map +0 -1
- package/dist/cli/ui/terminal-ui.js +0 -121
- package/dist/cli/ui/terminal-ui.js.map +0 -1
- package/dist/do/BashModule.d.ts +0 -871
- package/dist/do/BashModule.d.ts.map +0 -1
- package/dist/do/BashModule.js +0 -1143
- package/dist/do/BashModule.js.map +0 -1
- package/dist/do/FsModule.d.ts +0 -601
- package/dist/do/FsModule.d.ts.map +0 -1
- package/dist/do/FsModule.js +0 -1120
- package/dist/do/FsModule.js.map +0 -1
- package/dist/do/GitModule.d.ts +0 -635
- package/dist/do/GitModule.d.ts.map +0 -1
- package/dist/do/GitModule.js +0 -781
- package/dist/do/GitModule.js.map +0 -1
- package/dist/do/GitRepoDO.d.ts +0 -281
- package/dist/do/GitRepoDO.d.ts.map +0 -1
- package/dist/do/GitRepoDO.js +0 -479
- package/dist/do/GitRepoDO.js.map +0 -1
- package/dist/do/bash-ast.d.ts +0 -246
- package/dist/do/bash-ast.d.ts.map +0 -1
- package/dist/do/bash-ast.js +0 -888
- package/dist/do/bash-ast.js.map +0 -1
- package/dist/do/container-executor.d.ts +0 -491
- package/dist/do/container-executor.d.ts.map +0 -1
- package/dist/do/container-executor.js +0 -730
- package/dist/do/container-executor.js.map +0 -1
- package/dist/do/index.d.ts +0 -53
- package/dist/do/index.d.ts.map +0 -1
- package/dist/do/index.js +0 -91
- package/dist/do/index.js.map +0 -1
- package/dist/do/tiered-storage.d.ts +0 -403
- package/dist/do/tiered-storage.d.ts.map +0 -1
- package/dist/do/tiered-storage.js +0 -689
- package/dist/do/tiered-storage.js.map +0 -1
- package/dist/do/withBash.d.ts +0 -231
- package/dist/do/withBash.d.ts.map +0 -1
- package/dist/do/withBash.js +0 -244
- package/dist/do/withBash.js.map +0 -1
- package/dist/do/withFs.d.ts +0 -237
- package/dist/do/withFs.d.ts.map +0 -1
- package/dist/do/withFs.js +0 -387
- package/dist/do/withFs.js.map +0 -1
- package/dist/do/withGit.d.ts +0 -180
- package/dist/do/withGit.d.ts.map +0 -1
- package/dist/do/withGit.js +0 -271
- package/dist/do/withGit.js.map +0 -1
- package/dist/durable-object/object-store.d.ts +0 -633
- package/dist/durable-object/object-store.d.ts.map +0 -1
- package/dist/durable-object/object-store.js +0 -1161
- package/dist/durable-object/object-store.js.map +0 -1
- package/dist/durable-object/schema.d.ts.map +0 -1
- package/dist/durable-object/schema.js.map +0 -1
- package/dist/durable-object/wal.d.ts +0 -416
- package/dist/durable-object/wal.d.ts.map +0 -1
- package/dist/durable-object/wal.js +0 -445
- package/dist/durable-object/wal.js.map +0 -1
- package/dist/mcp/adapter.d.ts +0 -772
- package/dist/mcp/adapter.d.ts.map +0 -1
- package/dist/mcp/adapter.js +0 -895
- package/dist/mcp/adapter.js.map +0 -1
- package/dist/mcp/sandbox/miniflare-evaluator.d.ts +0 -22
- package/dist/mcp/sandbox/miniflare-evaluator.d.ts.map +0 -1
- package/dist/mcp/sandbox/miniflare-evaluator.js +0 -140
- package/dist/mcp/sandbox/miniflare-evaluator.js.map +0 -1
- package/dist/mcp/sandbox/object-store-proxy.d.ts +0 -32
- package/dist/mcp/sandbox/object-store-proxy.d.ts.map +0 -1
- package/dist/mcp/sandbox/object-store-proxy.js +0 -30
- package/dist/mcp/sandbox/object-store-proxy.js.map +0 -1
- package/dist/mcp/sandbox/template.d.ts +0 -17
- package/dist/mcp/sandbox/template.d.ts.map +0 -1
- package/dist/mcp/sandbox/template.js +0 -71
- package/dist/mcp/sandbox/template.js.map +0 -1
- package/dist/mcp/sandbox.d.ts +0 -764
- package/dist/mcp/sandbox.d.ts.map +0 -1
- package/dist/mcp/sandbox.js +0 -1362
- package/dist/mcp/sandbox.js.map +0 -1
- package/dist/mcp/sdk-adapter.d.ts +0 -835
- package/dist/mcp/sdk-adapter.d.ts.map +0 -1
- package/dist/mcp/sdk-adapter.js +0 -974
- package/dist/mcp/sdk-adapter.js.map +0 -1
- package/dist/mcp/tools/do.d.ts +0 -32
- package/dist/mcp/tools/do.d.ts.map +0 -1
- package/dist/mcp/tools/do.js +0 -115
- package/dist/mcp/tools/do.js.map +0 -1
- package/dist/mcp/tools.d.ts +0 -548
- package/dist/mcp/tools.d.ts.map +0 -1
- package/dist/mcp/tools.js +0 -1934
- package/dist/mcp/tools.js.map +0 -1
- package/dist/ops/blame.d.ts +0 -551
- package/dist/ops/blame.d.ts.map +0 -1
- package/dist/ops/blame.js +0 -1037
- package/dist/ops/blame.js.map +0 -1
- package/dist/ops/branch.d.ts +0 -766
- package/dist/ops/branch.d.ts.map +0 -1
- package/dist/ops/branch.js +0 -950
- package/dist/ops/branch.js.map +0 -1
- package/dist/ops/commit-traversal.d.ts +0 -349
- package/dist/ops/commit-traversal.d.ts.map +0 -1
- package/dist/ops/commit-traversal.js +0 -821
- package/dist/ops/commit-traversal.js.map +0 -1
- package/dist/ops/commit.d.ts +0 -555
- package/dist/ops/commit.d.ts.map +0 -1
- package/dist/ops/commit.js +0 -826
- package/dist/ops/commit.js.map +0 -1
- package/dist/ops/merge-base.d.ts +0 -397
- package/dist/ops/merge-base.d.ts.map +0 -1
- package/dist/ops/merge-base.js +0 -691
- package/dist/ops/merge-base.js.map +0 -1
- package/dist/ops/merge.d.ts +0 -855
- package/dist/ops/merge.d.ts.map +0 -1
- package/dist/ops/merge.js +0 -1551
- package/dist/ops/merge.js.map +0 -1
- package/dist/ops/tag.d.ts +0 -247
- package/dist/ops/tag.d.ts.map +0 -1
- package/dist/ops/tag.js +0 -649
- package/dist/ops/tag.js.map +0 -1
- package/dist/ops/tree-builder.d.ts +0 -178
- package/dist/ops/tree-builder.d.ts.map +0 -1
- package/dist/ops/tree-builder.js +0 -271
- package/dist/ops/tree-builder.js.map +0 -1
- package/dist/ops/tree-diff.d.ts +0 -291
- package/dist/ops/tree-diff.d.ts.map +0 -1
- package/dist/ops/tree-diff.js +0 -705
- package/dist/ops/tree-diff.js.map +0 -1
- package/dist/pack/delta.d.ts +0 -248
- package/dist/pack/delta.d.ts.map +0 -1
- package/dist/pack/delta.js +0 -736
- package/dist/pack/delta.js.map +0 -1
- package/dist/pack/format.d.ts +0 -446
- package/dist/pack/format.d.ts.map +0 -1
- package/dist/pack/format.js +0 -572
- package/dist/pack/format.js.map +0 -1
- package/dist/pack/full-generation.d.ts +0 -612
- package/dist/pack/full-generation.d.ts.map +0 -1
- package/dist/pack/full-generation.js +0 -1378
- package/dist/pack/full-generation.js.map +0 -1
- package/dist/pack/generation.d.ts +0 -441
- package/dist/pack/generation.d.ts.map +0 -1
- package/dist/pack/generation.js +0 -707
- package/dist/pack/generation.js.map +0 -1
- package/dist/pack/index.d.ts +0 -502
- package/dist/pack/index.d.ts.map +0 -1
- package/dist/pack/index.js +0 -833
- package/dist/pack/index.js.map +0 -1
- package/dist/refs/branch.d.ts +0 -668
- package/dist/refs/branch.d.ts.map +0 -1
- package/dist/refs/branch.js +0 -897
- package/dist/refs/branch.js.map +0 -1
- package/dist/refs/storage.d.ts +0 -833
- package/dist/refs/storage.d.ts.map +0 -1
- package/dist/refs/storage.js +0 -1023
- package/dist/refs/storage.js.map +0 -1
- package/dist/refs/tag.d.ts +0 -860
- package/dist/refs/tag.d.ts.map +0 -1
- package/dist/refs/tag.js +0 -996
- package/dist/refs/tag.js.map +0 -1
- package/dist/storage/backend.d.ts +0 -425
- package/dist/storage/backend.d.ts.map +0 -1
- package/dist/storage/backend.js +0 -41
- package/dist/storage/backend.js.map +0 -1
- package/dist/storage/fsx-adapter.d.ts +0 -204
- package/dist/storage/fsx-adapter.d.ts.map +0 -1
- package/dist/storage/fsx-adapter.js +0 -470
- package/dist/storage/fsx-adapter.js.map +0 -1
- package/dist/storage/lru-cache.d.ts +0 -691
- package/dist/storage/lru-cache.d.ts.map +0 -1
- package/dist/storage/lru-cache.js +0 -813
- package/dist/storage/lru-cache.js.map +0 -1
- package/dist/storage/object-index.d.ts +0 -585
- package/dist/storage/object-index.d.ts.map +0 -1
- package/dist/storage/object-index.js +0 -532
- package/dist/storage/object-index.js.map +0 -1
- package/dist/storage/r2-pack.d.ts +0 -1257
- package/dist/storage/r2-pack.d.ts.map +0 -1
- package/dist/storage/r2-pack.js +0 -1770
- package/dist/storage/r2-pack.js.map +0 -1
- package/dist/tiered/cdc-pipeline.d.ts +0 -1888
- package/dist/tiered/cdc-pipeline.d.ts.map +0 -1
- package/dist/tiered/cdc-pipeline.js +0 -1880
- package/dist/tiered/cdc-pipeline.js.map +0 -1
- package/dist/tiered/migration.d.ts +0 -1104
- package/dist/tiered/migration.d.ts.map +0 -1
- package/dist/tiered/migration.js +0 -1214
- package/dist/tiered/migration.js.map +0 -1
- package/dist/tiered/parquet-writer.d.ts +0 -1145
- package/dist/tiered/parquet-writer.d.ts.map +0 -1
- package/dist/tiered/parquet-writer.js +0 -1183
- package/dist/tiered/parquet-writer.js.map +0 -1
- package/dist/tiered/read-path.d.ts +0 -835
- package/dist/tiered/read-path.d.ts.map +0 -1
- package/dist/tiered/read-path.js +0 -487
- package/dist/tiered/read-path.js.map +0 -1
- package/dist/types/capability.d.ts +0 -1385
- package/dist/types/capability.d.ts.map +0 -1
- package/dist/types/capability.js +0 -36
- package/dist/types/capability.js.map +0 -1
- package/dist/types/index.d.ts +0 -13
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -18
- package/dist/types/index.js.map +0 -1
- package/dist/types/objects.d.ts +0 -692
- package/dist/types/objects.d.ts.map +0 -1
- package/dist/types/objects.js +0 -837
- package/dist/types/objects.js.map +0 -1
- package/dist/types/storage.d.ts +0 -603
- package/dist/types/storage.d.ts.map +0 -1
- package/dist/types/storage.js +0 -191
- package/dist/types/storage.js.map +0 -1
- package/dist/types/worker-loader.d.ts +0 -60
- package/dist/types/worker-loader.d.ts.map +0 -1
- package/dist/types/worker-loader.js +0 -62
- package/dist/types/worker-loader.js.map +0 -1
- package/dist/utils/hash.d.ts +0 -197
- package/dist/utils/hash.d.ts.map +0 -1
- package/dist/utils/hash.js +0 -268
- package/dist/utils/hash.js.map +0 -1
- package/dist/utils/sha1.d.ts +0 -290
- package/dist/utils/sha1.d.ts.map +0 -1
- package/dist/utils/sha1.js +0 -582
- package/dist/utils/sha1.js.map +0 -1
- package/dist/wire/capabilities.d.ts +0 -1044
- package/dist/wire/capabilities.d.ts.map +0 -1
- package/dist/wire/capabilities.js +0 -941
- package/dist/wire/capabilities.js.map +0 -1
- package/dist/wire/path-security.d.ts +0 -157
- package/dist/wire/path-security.d.ts.map +0 -1
- package/dist/wire/path-security.js +0 -307
- package/dist/wire/path-security.js.map +0 -1
- package/dist/wire/pkt-line.d.ts +0 -345
- package/dist/wire/pkt-line.d.ts.map +0 -1
- package/dist/wire/pkt-line.js +0 -381
- package/dist/wire/pkt-line.js.map +0 -1
- package/dist/wire/receive-pack.d.ts +0 -1059
- package/dist/wire/receive-pack.d.ts.map +0 -1
- package/dist/wire/receive-pack.js +0 -1414
- package/dist/wire/receive-pack.js.map +0 -1
- package/dist/wire/smart-http.d.ts +0 -799
- package/dist/wire/smart-http.d.ts.map +0 -1
- package/dist/wire/smart-http.js +0 -945
- package/dist/wire/smart-http.js.map +0 -1
- package/dist/wire/upload-pack.d.ts +0 -727
- package/dist/wire/upload-pack.d.ts.map +0 -1
- package/dist/wire/upload-pack.js +0 -1138
- package/dist/wire/upload-pack.js.map +0 -1
package/dist/do/BashModule.js
DELETED
|
@@ -1,1143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview BashModule for Durable Object Integration
|
|
3
|
-
*
|
|
4
|
-
* This module provides a BashModule class that integrates with dotdo's $ WorkflowContext,
|
|
5
|
-
* providing $.bash.exec(), $.bash.run(), and bash execution functionality.
|
|
6
|
-
*
|
|
7
|
-
* The module depends on FsModule for file system operations during command execution,
|
|
8
|
-
* enabling sandboxed bash operations within the DO's virtual filesystem.
|
|
9
|
-
*
|
|
10
|
-
* Features:
|
|
11
|
-
* - AST-based safety analysis for command parsing
|
|
12
|
-
* - Configurable command blocking and confirmation requirements
|
|
13
|
-
* - Support for database-backed execution policies
|
|
14
|
-
*
|
|
15
|
-
* @module do/BashModule
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```typescript
|
|
19
|
-
* import { BashModule } from 'gitx.do/do'
|
|
20
|
-
*
|
|
21
|
-
* class MyDO extends DO {
|
|
22
|
-
* bash = new BashModule({
|
|
23
|
-
* executor: myExecutor,
|
|
24
|
-
* fs: this.$.fs
|
|
25
|
-
* })
|
|
26
|
-
*
|
|
27
|
-
* async buildProject() {
|
|
28
|
-
* const result = await this.bash.exec('npm', ['run', 'build'])
|
|
29
|
-
* if (result.exitCode !== 0) {
|
|
30
|
-
* throw new Error(`Build failed: ${result.stderr}`)
|
|
31
|
-
* }
|
|
32
|
-
* }
|
|
33
|
-
* }
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
// Import AST-based safety analysis
|
|
37
|
-
import { parseBashCommand, analyzeASTSafety, } from './bash-ast';
|
|
38
|
-
// ============================================================================
|
|
39
|
-
// BashModule Class
|
|
40
|
-
// ============================================================================
|
|
41
|
-
/**
|
|
42
|
-
* BashModule class for integration with dotdo's $ WorkflowContext.
|
|
43
|
-
*
|
|
44
|
-
* @description
|
|
45
|
-
* Provides bash execution functionality as a capability module that integrates
|
|
46
|
-
* with dotdo's Durable Object framework. The module:
|
|
47
|
-
*
|
|
48
|
-
* - Depends on FsModule for file system operations during execution
|
|
49
|
-
* - Delegates actual command execution to a configurable executor
|
|
50
|
-
* - Provides safety analysis and command blocking
|
|
51
|
-
* - Supports both exec (wait for completion) and spawn (streaming) modes
|
|
52
|
-
*
|
|
53
|
-
* @example
|
|
54
|
-
* ```typescript
|
|
55
|
-
* // In a Durable Object
|
|
56
|
-
* class MyDO extends DO {
|
|
57
|
-
* private bash: BashModule
|
|
58
|
-
*
|
|
59
|
-
* constructor(state: DurableObjectState, env: Env) {
|
|
60
|
-
* super(state, env)
|
|
61
|
-
* this.bash = new BashModule({
|
|
62
|
-
* executor: containerExecutor,
|
|
63
|
-
* fs: this.$.fs,
|
|
64
|
-
* cwd: '/app'
|
|
65
|
-
* })
|
|
66
|
-
* }
|
|
67
|
-
*
|
|
68
|
-
* async fetch(request: Request) {
|
|
69
|
-
* // Execute a command
|
|
70
|
-
* const result = await this.bash.exec('npm', ['install'])
|
|
71
|
-
*
|
|
72
|
-
* // Run a script
|
|
73
|
-
* await this.bash.run(`
|
|
74
|
-
* set -e
|
|
75
|
-
* npm run build
|
|
76
|
-
* npm run test
|
|
77
|
-
* `)
|
|
78
|
-
*
|
|
79
|
-
* return new Response('OK')
|
|
80
|
-
* }
|
|
81
|
-
* }
|
|
82
|
-
* ```
|
|
83
|
-
*/
|
|
84
|
-
export class BashModule {
|
|
85
|
-
/**
|
|
86
|
-
* Capability module name for identification.
|
|
87
|
-
*/
|
|
88
|
-
name = 'bash';
|
|
89
|
-
/**
|
|
90
|
-
* The executor used for running commands.
|
|
91
|
-
*/
|
|
92
|
-
executor;
|
|
93
|
-
/**
|
|
94
|
-
* Filesystem capability for file operations.
|
|
95
|
-
*/
|
|
96
|
-
fs;
|
|
97
|
-
/**
|
|
98
|
-
* Default working directory.
|
|
99
|
-
*/
|
|
100
|
-
defaultCwd;
|
|
101
|
-
/**
|
|
102
|
-
* Default timeout in milliseconds.
|
|
103
|
-
*/
|
|
104
|
-
defaultTimeout;
|
|
105
|
-
/**
|
|
106
|
-
* List of blocked commands.
|
|
107
|
-
*/
|
|
108
|
-
blockedCommands;
|
|
109
|
-
/**
|
|
110
|
-
* Whether to require confirmation for dangerous commands.
|
|
111
|
-
*/
|
|
112
|
-
requireConfirmation;
|
|
113
|
-
/**
|
|
114
|
-
* Database storage for persistence.
|
|
115
|
-
*/
|
|
116
|
-
storage;
|
|
117
|
-
/**
|
|
118
|
-
* Policy name for database operations.
|
|
119
|
-
*/
|
|
120
|
-
policyName;
|
|
121
|
-
/**
|
|
122
|
-
* Database row ID for this policy.
|
|
123
|
-
*/
|
|
124
|
-
policyId;
|
|
125
|
-
/**
|
|
126
|
-
* Allowed command patterns (regex).
|
|
127
|
-
*/
|
|
128
|
-
allowedPatterns = [];
|
|
129
|
-
/**
|
|
130
|
-
* Denied command patterns (regex).
|
|
131
|
-
*/
|
|
132
|
-
deniedPatterns = [];
|
|
133
|
-
/**
|
|
134
|
-
* Maximum concurrent executions.
|
|
135
|
-
*/
|
|
136
|
-
maxConcurrent = 5;
|
|
137
|
-
/**
|
|
138
|
-
* Whether the policy is enabled.
|
|
139
|
-
*/
|
|
140
|
-
enabled = true;
|
|
141
|
-
/**
|
|
142
|
-
* Whether to use AST-based safety analysis.
|
|
143
|
-
*/
|
|
144
|
-
useAST;
|
|
145
|
-
/**
|
|
146
|
-
* Commands considered dangerous and requiring confirmation.
|
|
147
|
-
*/
|
|
148
|
-
static DANGEROUS_COMMANDS = new Set([
|
|
149
|
-
'rm',
|
|
150
|
-
'rmdir',
|
|
151
|
-
'dd',
|
|
152
|
-
'mkfs',
|
|
153
|
-
'fdisk',
|
|
154
|
-
'format',
|
|
155
|
-
'shutdown',
|
|
156
|
-
'reboot',
|
|
157
|
-
'halt',
|
|
158
|
-
'poweroff',
|
|
159
|
-
'init',
|
|
160
|
-
'kill',
|
|
161
|
-
'killall',
|
|
162
|
-
'pkill',
|
|
163
|
-
'chmod',
|
|
164
|
-
'chown',
|
|
165
|
-
'chgrp',
|
|
166
|
-
'mount',
|
|
167
|
-
'umount',
|
|
168
|
-
'mkswap',
|
|
169
|
-
'swapon',
|
|
170
|
-
'swapoff',
|
|
171
|
-
]);
|
|
172
|
-
/**
|
|
173
|
-
* Critical patterns that should ALWAYS be blocked, regardless of confirmation.
|
|
174
|
-
* These patterns represent commands that could cause catastrophic, irreversible damage.
|
|
175
|
-
*/
|
|
176
|
-
static CRITICAL_PATTERNS = [
|
|
177
|
-
/\brm\s+(-[rfvI]+\s+)*\/\s*$/, // rm -rf /
|
|
178
|
-
/\brm\s+(-[rfvI]+\s+)*\/\*/, // rm -rf /*
|
|
179
|
-
/\brm\s+.*--no-preserve-root/, // rm with --no-preserve-root
|
|
180
|
-
/:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:/, // fork bomb
|
|
181
|
-
/\.\(\)\s*\{\s*\.\s*\|\s*\.\s*&\s*\}\s*;\s*\./, // fork bomb variant
|
|
182
|
-
/\bdd\s+.*if=\/dev\/(u?random|zero)\s+.*of=\/dev\/[hs]d[a-z]/, // dd to disk
|
|
183
|
-
/\bdd\s+.*of=\/dev\/[hs]d[a-z].*if=\/dev\/(u?random|zero)/, // dd to disk (reversed)
|
|
184
|
-
/\bmkfs(\.\w+)?\s+(-[a-zA-Z]+\s+)*\/dev\/[hs]d[a-z]/, // mkfs on disk
|
|
185
|
-
/>\s*\/dev\/[hs]d[a-z]/, // redirect to disk device
|
|
186
|
-
/echo\s+[cso]\s*>\s*\/proc\/sysrq-trigger/, // kernel panic trigger
|
|
187
|
-
/\bcurl\s+.*\|\s*(ba)?sh\b/, // curl piped to shell
|
|
188
|
-
/\bwget\s+.*\|\s*(ba)?sh\b/, // wget piped to shell
|
|
189
|
-
];
|
|
190
|
-
/**
|
|
191
|
-
* Dangerous flag patterns (require confirmation but can be executed with confirm).
|
|
192
|
-
*/
|
|
193
|
-
static DANGEROUS_PATTERNS = [
|
|
194
|
-
/rm\s+(-[rf]+\s+)*\/\w/, // rm with path starting from root (but not root itself)
|
|
195
|
-
/rm\s+(-[rf]+\s+)*\*/, // rm with wildcard
|
|
196
|
-
/>\s*\/dev\//, // redirect to device
|
|
197
|
-
/dd\s+.*of=\/dev/, // dd to device
|
|
198
|
-
/chmod\s+777/, // overly permissive chmod
|
|
199
|
-
];
|
|
200
|
-
/**
|
|
201
|
-
* Create a new BashModule instance.
|
|
202
|
-
*
|
|
203
|
-
* @param options - Configuration options
|
|
204
|
-
*
|
|
205
|
-
* @example
|
|
206
|
-
* ```typescript
|
|
207
|
-
* const bash = new BashModule({
|
|
208
|
-
* executor: containerExecutor,
|
|
209
|
-
* fs: workflowContext.fs,
|
|
210
|
-
* cwd: '/app'
|
|
211
|
-
* })
|
|
212
|
-
* ```
|
|
213
|
-
*/
|
|
214
|
-
constructor(options = {}) {
|
|
215
|
-
this.executor = options.executor;
|
|
216
|
-
this.fs = options.fs;
|
|
217
|
-
this.defaultCwd = options.cwd ?? '/';
|
|
218
|
-
this.defaultTimeout = options.defaultTimeout ?? 30000;
|
|
219
|
-
this.blockedCommands = new Set(options.blockedCommands ?? []);
|
|
220
|
-
this.requireConfirmation = options.requireConfirmation ?? true;
|
|
221
|
-
this.storage = options.storage;
|
|
222
|
-
this.policyName = options.policyName ?? 'default';
|
|
223
|
-
this.useAST = options.useAST ?? true;
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Optional initialization hook.
|
|
227
|
-
* Called when the module is first loaded.
|
|
228
|
-
* When storage is provided, loads or creates the execution policy from the database.
|
|
229
|
-
*/
|
|
230
|
-
async initialize() {
|
|
231
|
-
if (!this.storage)
|
|
232
|
-
return;
|
|
233
|
-
// Try to load existing policy
|
|
234
|
-
const existingRows = this.storage.sql.exec('SELECT * FROM exec WHERE name = ?', this.policyName).toArray();
|
|
235
|
-
if (existingRows.length > 0) {
|
|
236
|
-
// Load existing settings
|
|
237
|
-
const row = existingRows[0];
|
|
238
|
-
this.policyId = row.id;
|
|
239
|
-
this.loadFromRow(row);
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
// Create new policy with current settings
|
|
243
|
-
await this.persistPolicy();
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Load settings from a database row.
|
|
248
|
-
*/
|
|
249
|
-
loadFromRow(row) {
|
|
250
|
-
// Parse blocked commands from JSON string
|
|
251
|
-
if (row.blocked_commands) {
|
|
252
|
-
try {
|
|
253
|
-
const commands = JSON.parse(row.blocked_commands);
|
|
254
|
-
this.blockedCommands = new Set(commands);
|
|
255
|
-
}
|
|
256
|
-
catch {
|
|
257
|
-
this.blockedCommands = new Set();
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
this.requireConfirmation = row.require_confirmation === 1;
|
|
261
|
-
this.defaultTimeout = row.default_timeout;
|
|
262
|
-
this.defaultCwd = row.default_cwd;
|
|
263
|
-
this.maxConcurrent = row.max_concurrent;
|
|
264
|
-
this.enabled = row.enabled === 1;
|
|
265
|
-
// Parse allowed patterns
|
|
266
|
-
if (row.allowed_patterns) {
|
|
267
|
-
try {
|
|
268
|
-
const patterns = JSON.parse(row.allowed_patterns);
|
|
269
|
-
this.allowedPatterns = patterns.map(p => new RegExp(p));
|
|
270
|
-
}
|
|
271
|
-
catch {
|
|
272
|
-
this.allowedPatterns = [];
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
// Parse denied patterns
|
|
276
|
-
if (row.denied_patterns) {
|
|
277
|
-
try {
|
|
278
|
-
const patterns = JSON.parse(row.denied_patterns);
|
|
279
|
-
this.deniedPatterns = patterns.map(p => new RegExp(p));
|
|
280
|
-
}
|
|
281
|
-
catch {
|
|
282
|
-
this.deniedPatterns = [];
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
/**
|
|
287
|
-
* Persist current policy settings to the database.
|
|
288
|
-
*/
|
|
289
|
-
async persistPolicy() {
|
|
290
|
-
if (!this.storage)
|
|
291
|
-
return;
|
|
292
|
-
const now = Date.now();
|
|
293
|
-
const blockedCommandsJson = JSON.stringify(Array.from(this.blockedCommands));
|
|
294
|
-
const allowedPatternsJson = this.allowedPatterns.length > 0
|
|
295
|
-
? JSON.stringify(this.allowedPatterns.map(p => p.source))
|
|
296
|
-
: null;
|
|
297
|
-
const deniedPatternsJson = this.deniedPatterns.length > 0
|
|
298
|
-
? JSON.stringify(this.deniedPatterns.map(p => p.source))
|
|
299
|
-
: null;
|
|
300
|
-
if (this.policyId) {
|
|
301
|
-
// Update existing policy
|
|
302
|
-
this.storage.sql.exec(`UPDATE exec SET
|
|
303
|
-
blocked_commands = ?,
|
|
304
|
-
require_confirmation = ?,
|
|
305
|
-
default_timeout = ?,
|
|
306
|
-
default_cwd = ?,
|
|
307
|
-
allowed_patterns = ?,
|
|
308
|
-
denied_patterns = ?,
|
|
309
|
-
max_concurrent = ?,
|
|
310
|
-
enabled = ?,
|
|
311
|
-
updated_at = ?
|
|
312
|
-
WHERE id = ?`, blockedCommandsJson, this.requireConfirmation ? 1 : 0, this.defaultTimeout, this.defaultCwd, allowedPatternsJson, deniedPatternsJson, this.maxConcurrent, this.enabled ? 1 : 0, now, this.policyId);
|
|
313
|
-
}
|
|
314
|
-
else {
|
|
315
|
-
// Insert new policy
|
|
316
|
-
this.storage.sql.exec(`INSERT INTO exec (
|
|
317
|
-
name, blocked_commands, require_confirmation, default_timeout,
|
|
318
|
-
default_cwd, allowed_patterns, denied_patterns, max_concurrent,
|
|
319
|
-
enabled, created_at, updated_at
|
|
320
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, this.policyName, blockedCommandsJson, this.requireConfirmation ? 1 : 0, this.defaultTimeout, this.defaultCwd, allowedPatternsJson, deniedPatternsJson, this.maxConcurrent, this.enabled ? 1 : 0, now, now);
|
|
321
|
-
// Get the inserted row ID
|
|
322
|
-
const insertedRows = this.storage.sql.exec('SELECT id FROM exec WHERE name = ?', this.policyName).toArray();
|
|
323
|
-
if (insertedRows.length > 0) {
|
|
324
|
-
this.policyId = insertedRows[0].id;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Optional cleanup hook.
|
|
330
|
-
* Called when the capability is unloaded.
|
|
331
|
-
*/
|
|
332
|
-
async dispose() {
|
|
333
|
-
// Cleanup logic if needed
|
|
334
|
-
}
|
|
335
|
-
/**
|
|
336
|
-
* Check if FsCapability is available.
|
|
337
|
-
*
|
|
338
|
-
* @returns True if FsCapability is configured
|
|
339
|
-
*/
|
|
340
|
-
get hasFsCapability() {
|
|
341
|
-
return this.fs !== undefined;
|
|
342
|
-
}
|
|
343
|
-
/**
|
|
344
|
-
* Check if an executor is available.
|
|
345
|
-
*
|
|
346
|
-
* @returns True if an executor is configured
|
|
347
|
-
*/
|
|
348
|
-
get hasExecutor() {
|
|
349
|
-
return this.executor !== undefined;
|
|
350
|
-
}
|
|
351
|
-
/**
|
|
352
|
-
* Execute a command and wait for completion.
|
|
353
|
-
*
|
|
354
|
-
* @param command - The command to execute (e.g., 'git', 'npm', 'ls')
|
|
355
|
-
* @param args - Optional array of command arguments
|
|
356
|
-
* @param options - Optional execution options
|
|
357
|
-
* @returns Promise resolving to the execution result
|
|
358
|
-
*
|
|
359
|
-
* @example
|
|
360
|
-
* ```typescript
|
|
361
|
-
* // Simple command
|
|
362
|
-
* const result = await bash.exec('ls')
|
|
363
|
-
*
|
|
364
|
-
* // With arguments
|
|
365
|
-
* const result = await bash.exec('git', ['status', '--short'])
|
|
366
|
-
*
|
|
367
|
-
* // With options
|
|
368
|
-
* const result = await bash.exec('npm', ['install'], {
|
|
369
|
-
* cwd: '/app',
|
|
370
|
-
* timeout: 60000
|
|
371
|
-
* })
|
|
372
|
-
* ```
|
|
373
|
-
*/
|
|
374
|
-
async exec(command, args, options) {
|
|
375
|
-
// Build full command string
|
|
376
|
-
const fullCommand = args && args.length > 0
|
|
377
|
-
? `${command} ${args.map(a => this.escapeArg(a)).join(' ')}`
|
|
378
|
-
: command;
|
|
379
|
-
// Check if command is blocked
|
|
380
|
-
const baseCommand = this.extractBaseCommand(command);
|
|
381
|
-
if (this.blockedCommands.has(baseCommand)) {
|
|
382
|
-
return {
|
|
383
|
-
command: fullCommand,
|
|
384
|
-
stdout: '',
|
|
385
|
-
stderr: `Command '${baseCommand}' is blocked`,
|
|
386
|
-
exitCode: 1,
|
|
387
|
-
blocked: true,
|
|
388
|
-
blockReason: `Command '${baseCommand}' is in the blocked list`
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
// Check safety
|
|
392
|
-
const safety = this.analyze(fullCommand);
|
|
393
|
-
// Critical commands are ALWAYS blocked, regardless of confirmation
|
|
394
|
-
if (safety.safetyLevel === 'critical') {
|
|
395
|
-
return {
|
|
396
|
-
command: fullCommand,
|
|
397
|
-
stdout: '',
|
|
398
|
-
stderr: safety.reason ?? 'Critical command is always blocked',
|
|
399
|
-
exitCode: 1,
|
|
400
|
-
blocked: true,
|
|
401
|
-
blockReason: safety.reason ?? 'Critical command cannot be executed even with confirmation'
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
// Dangerous commands require confirmation
|
|
405
|
-
if (safety.dangerous && this.requireConfirmation && !options?.confirm) {
|
|
406
|
-
return {
|
|
407
|
-
command: fullCommand,
|
|
408
|
-
stdout: '',
|
|
409
|
-
stderr: safety.reason ?? 'Command requires confirmation',
|
|
410
|
-
exitCode: 1,
|
|
411
|
-
blocked: true,
|
|
412
|
-
blockReason: safety.reason ?? 'Dangerous command requires confirmation'
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
// Dry run mode
|
|
416
|
-
if (options?.dryRun) {
|
|
417
|
-
return {
|
|
418
|
-
command: fullCommand,
|
|
419
|
-
stdout: `[dry-run] Would execute: ${fullCommand}`,
|
|
420
|
-
stderr: '',
|
|
421
|
-
exitCode: 0
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
// Check for executor
|
|
425
|
-
if (!this.executor) {
|
|
426
|
-
return {
|
|
427
|
-
command: fullCommand,
|
|
428
|
-
stdout: '',
|
|
429
|
-
stderr: 'No executor configured',
|
|
430
|
-
exitCode: 1
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
// Merge options with defaults
|
|
434
|
-
const execOptions = {
|
|
435
|
-
timeout: this.defaultTimeout,
|
|
436
|
-
cwd: this.defaultCwd,
|
|
437
|
-
...options
|
|
438
|
-
};
|
|
439
|
-
// Execute the command
|
|
440
|
-
try {
|
|
441
|
-
return await this.executor.execute(fullCommand, execOptions);
|
|
442
|
-
}
|
|
443
|
-
catch (error) {
|
|
444
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
445
|
-
return {
|
|
446
|
-
command: fullCommand,
|
|
447
|
-
stdout: '',
|
|
448
|
-
stderr: errorMessage,
|
|
449
|
-
exitCode: 1
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
/**
|
|
454
|
-
* Spawn a command for streaming execution.
|
|
455
|
-
*
|
|
456
|
-
* @param command - The command to spawn
|
|
457
|
-
* @param args - Optional array of command arguments
|
|
458
|
-
* @param options - Optional spawn options including stream callbacks
|
|
459
|
-
* @returns Promise resolving to a spawn handle
|
|
460
|
-
*
|
|
461
|
-
* @example
|
|
462
|
-
* ```typescript
|
|
463
|
-
* const handle = await bash.spawn('tail', ['-f', '/var/log/app.log'], {
|
|
464
|
-
* onStdout: (chunk) => console.log(chunk),
|
|
465
|
-
* onStderr: (chunk) => console.error(chunk)
|
|
466
|
-
* })
|
|
467
|
-
*
|
|
468
|
-
* // Later, stop the process
|
|
469
|
-
* handle.kill()
|
|
470
|
-
*
|
|
471
|
-
* // Wait for it to finish
|
|
472
|
-
* const result = await handle.done
|
|
473
|
-
* ```
|
|
474
|
-
*/
|
|
475
|
-
async spawn(command, args, options) {
|
|
476
|
-
if (!this.executor?.spawn) {
|
|
477
|
-
throw new Error('Spawn not supported by this executor');
|
|
478
|
-
}
|
|
479
|
-
// Check if command is blocked
|
|
480
|
-
const baseCommand = this.extractBaseCommand(command);
|
|
481
|
-
if (this.blockedCommands.has(baseCommand)) {
|
|
482
|
-
throw new Error(`Command '${baseCommand}' is blocked`);
|
|
483
|
-
}
|
|
484
|
-
// Check safety
|
|
485
|
-
const fullCommand = args && args.length > 0
|
|
486
|
-
? `${command} ${args.join(' ')}`
|
|
487
|
-
: command;
|
|
488
|
-
const safety = this.analyze(fullCommand);
|
|
489
|
-
// Critical commands are ALWAYS blocked, regardless of confirmation
|
|
490
|
-
if (safety.safetyLevel === 'critical') {
|
|
491
|
-
throw new Error(safety.reason ?? 'Critical command cannot be executed even with confirmation');
|
|
492
|
-
}
|
|
493
|
-
// Dangerous commands require confirmation
|
|
494
|
-
if (safety.dangerous && this.requireConfirmation && !options?.confirm) {
|
|
495
|
-
throw new Error(safety.reason ?? 'Dangerous command requires confirmation');
|
|
496
|
-
}
|
|
497
|
-
return this.executor.spawn(command, args, options);
|
|
498
|
-
}
|
|
499
|
-
/**
|
|
500
|
-
* Run a shell script.
|
|
501
|
-
*
|
|
502
|
-
* @param script - The bash script to execute
|
|
503
|
-
* @param options - Optional execution options
|
|
504
|
-
* @returns Promise resolving to the execution result
|
|
505
|
-
*
|
|
506
|
-
* @example
|
|
507
|
-
* ```typescript
|
|
508
|
-
* const result = await bash.run(`
|
|
509
|
-
* set -e
|
|
510
|
-
* cd /app
|
|
511
|
-
* npm install
|
|
512
|
-
* npm run build
|
|
513
|
-
* `)
|
|
514
|
-
* ```
|
|
515
|
-
*/
|
|
516
|
-
async run(script, options) {
|
|
517
|
-
// Analyze script safety first (before dry-run check)
|
|
518
|
-
const safety = this.analyze(script);
|
|
519
|
-
// Critical commands are ALWAYS blocked, regardless of confirmation or dry-run
|
|
520
|
-
if (safety.safetyLevel === 'critical') {
|
|
521
|
-
return {
|
|
522
|
-
command: script,
|
|
523
|
-
stdout: '',
|
|
524
|
-
stderr: safety.reason ?? 'Critical command is always blocked',
|
|
525
|
-
exitCode: 1,
|
|
526
|
-
blocked: true,
|
|
527
|
-
blockReason: safety.reason ?? 'Critical command cannot be executed even with confirmation'
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
// Dry run mode
|
|
531
|
-
if (options?.dryRun) {
|
|
532
|
-
return {
|
|
533
|
-
command: script,
|
|
534
|
-
stdout: `[dry-run] Would execute script:\n${script}`,
|
|
535
|
-
stderr: '',
|
|
536
|
-
exitCode: 0
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
// Check for executor
|
|
540
|
-
if (!this.executor) {
|
|
541
|
-
return {
|
|
542
|
-
command: script,
|
|
543
|
-
stdout: '',
|
|
544
|
-
stderr: 'No executor configured',
|
|
545
|
-
exitCode: 1
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
// Dangerous commands require confirmation
|
|
549
|
-
if (safety.dangerous && this.requireConfirmation && !options?.confirm) {
|
|
550
|
-
return {
|
|
551
|
-
command: script,
|
|
552
|
-
stdout: '',
|
|
553
|
-
stderr: safety.reason ?? 'Script requires confirmation',
|
|
554
|
-
exitCode: 1,
|
|
555
|
-
blocked: true,
|
|
556
|
-
blockReason: safety.reason ?? 'Dangerous script requires confirmation'
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
|
-
// Execute the script
|
|
560
|
-
const execOptions = {
|
|
561
|
-
timeout: this.defaultTimeout,
|
|
562
|
-
cwd: this.defaultCwd,
|
|
563
|
-
...options
|
|
564
|
-
};
|
|
565
|
-
try {
|
|
566
|
-
return await this.executor.execute(script, execOptions);
|
|
567
|
-
}
|
|
568
|
-
catch (error) {
|
|
569
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
570
|
-
return {
|
|
571
|
-
command: script,
|
|
572
|
-
stdout: '',
|
|
573
|
-
stderr: errorMessage,
|
|
574
|
-
exitCode: 1
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
/**
|
|
579
|
-
* Analyze a command for safety.
|
|
580
|
-
*
|
|
581
|
-
* Uses AST-based analysis by default for more accurate command parsing
|
|
582
|
-
* and safety classification. Falls back to regex-based analysis if
|
|
583
|
-
* useAST is disabled.
|
|
584
|
-
*
|
|
585
|
-
* @param input - The command or script to analyze
|
|
586
|
-
* @returns Safety analysis result
|
|
587
|
-
*
|
|
588
|
-
* @example
|
|
589
|
-
* ```typescript
|
|
590
|
-
* const analysis = bash.analyze('rm -rf /')
|
|
591
|
-
* if (analysis.dangerous) {
|
|
592
|
-
* console.warn(analysis.reason)
|
|
593
|
-
* }
|
|
594
|
-
* ```
|
|
595
|
-
*/
|
|
596
|
-
analyze(input) {
|
|
597
|
-
// Use AST-based analysis if enabled
|
|
598
|
-
if (this.useAST) {
|
|
599
|
-
return this.analyzeWithAST(input);
|
|
600
|
-
}
|
|
601
|
-
// Fall back to regex-based analysis
|
|
602
|
-
return this.analyzeWithRegex(input);
|
|
603
|
-
}
|
|
604
|
-
/**
|
|
605
|
-
* Analyze a command using AST-based parsing.
|
|
606
|
-
*
|
|
607
|
-
* Parses the command into an AST and inspects nodes for safety issues.
|
|
608
|
-
* This provides more accurate analysis than regex patterns because it
|
|
609
|
-
* understands command structure, arguments, and pipelines.
|
|
610
|
-
*
|
|
611
|
-
* @param input - The command or script to analyze
|
|
612
|
-
* @returns Safety analysis result with AST details
|
|
613
|
-
* @internal
|
|
614
|
-
*/
|
|
615
|
-
analyzeWithAST(input) {
|
|
616
|
-
try {
|
|
617
|
-
const ast = parseBashCommand(input);
|
|
618
|
-
const astAnalysis = analyzeASTSafety(ast, this.blockedCommands, input);
|
|
619
|
-
return {
|
|
620
|
-
dangerous: astAnalysis.dangerous,
|
|
621
|
-
safetyLevel: astAnalysis.safetyLevel,
|
|
622
|
-
reason: astAnalysis.reason,
|
|
623
|
-
commands: astAnalysis.commands,
|
|
624
|
-
impact: astAnalysis.impact,
|
|
625
|
-
issues: astAnalysis.issues,
|
|
626
|
-
usedAST: true,
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
catch {
|
|
630
|
-
// If AST parsing fails, fall back to regex analysis
|
|
631
|
-
return this.analyzeWithRegex(input);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
/**
|
|
635
|
-
* Analyze a command using regex patterns.
|
|
636
|
-
*
|
|
637
|
-
* This is the fallback analysis method when AST parsing is disabled
|
|
638
|
-
* or fails. It uses simple pattern matching.
|
|
639
|
-
*
|
|
640
|
-
* @param input - The command or script to analyze
|
|
641
|
-
* @returns Safety analysis result
|
|
642
|
-
* @internal
|
|
643
|
-
*/
|
|
644
|
-
analyzeWithRegex(input) {
|
|
645
|
-
const commands = this.extractCommands(input);
|
|
646
|
-
let dangerous = false;
|
|
647
|
-
let reason;
|
|
648
|
-
let impact = 'none';
|
|
649
|
-
let safetyLevel = 'safe';
|
|
650
|
-
// Check for critical patterns first (these are ALWAYS blocked)
|
|
651
|
-
for (const pattern of BashModule.CRITICAL_PATTERNS) {
|
|
652
|
-
if (pattern.test(input)) {
|
|
653
|
-
dangerous = true;
|
|
654
|
-
reason = `Critical command pattern detected - cannot be executed`;
|
|
655
|
-
impact = 'critical';
|
|
656
|
-
safetyLevel = 'critical';
|
|
657
|
-
break;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
// Check for blocked commands (highest priority after critical)
|
|
661
|
-
if (!dangerous) {
|
|
662
|
-
for (const cmd of commands) {
|
|
663
|
-
if (this.blockedCommands.has(cmd)) {
|
|
664
|
-
dangerous = true;
|
|
665
|
-
reason = `Command '${cmd}' is blocked`;
|
|
666
|
-
impact = 'critical';
|
|
667
|
-
safetyLevel = 'dangerous';
|
|
668
|
-
break;
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
// Check for dangerous patterns (critical impact - check before DANGEROUS_COMMANDS)
|
|
673
|
-
if (!dangerous) {
|
|
674
|
-
for (const pattern of BashModule.DANGEROUS_PATTERNS) {
|
|
675
|
-
if (pattern.test(input)) {
|
|
676
|
-
dangerous = true;
|
|
677
|
-
reason = `Command matches dangerous pattern: ${pattern.source}`;
|
|
678
|
-
impact = 'critical';
|
|
679
|
-
safetyLevel = 'dangerous';
|
|
680
|
-
break;
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
// Check for dangerous commands (high impact)
|
|
685
|
-
if (!dangerous) {
|
|
686
|
-
for (const cmd of commands) {
|
|
687
|
-
if (BashModule.DANGEROUS_COMMANDS.has(cmd)) {
|
|
688
|
-
dangerous = true;
|
|
689
|
-
reason = `Command '${cmd}' is potentially dangerous`;
|
|
690
|
-
impact = 'high';
|
|
691
|
-
safetyLevel = 'dangerous';
|
|
692
|
-
break;
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
// Determine impact based on commands
|
|
697
|
-
if (!dangerous) {
|
|
698
|
-
if (commands.some(c => ['cat', 'ls', 'pwd', 'echo', 'head', 'tail', 'wc'].includes(c))) {
|
|
699
|
-
impact = 'none';
|
|
700
|
-
}
|
|
701
|
-
else if (commands.some(c => ['touch', 'mkdir', 'cp'].includes(c))) {
|
|
702
|
-
impact = 'low';
|
|
703
|
-
}
|
|
704
|
-
else if (commands.some(c => ['mv', 'sed', 'awk'].includes(c))) {
|
|
705
|
-
impact = 'medium';
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
return {
|
|
709
|
-
dangerous,
|
|
710
|
-
safetyLevel,
|
|
711
|
-
reason,
|
|
712
|
-
commands,
|
|
713
|
-
impact,
|
|
714
|
-
usedAST: false,
|
|
715
|
-
};
|
|
716
|
-
}
|
|
717
|
-
/**
|
|
718
|
-
* Check if a command is dangerous.
|
|
719
|
-
*
|
|
720
|
-
* @param input - The command to check
|
|
721
|
-
* @returns Object indicating if dangerous and why
|
|
722
|
-
*
|
|
723
|
-
* @example
|
|
724
|
-
* ```typescript
|
|
725
|
-
* const check = bash.isDangerous('rm -rf /')
|
|
726
|
-
* if (check.dangerous) {
|
|
727
|
-
* console.warn(check.reason)
|
|
728
|
-
* }
|
|
729
|
-
* ```
|
|
730
|
-
*/
|
|
731
|
-
isDangerous(input) {
|
|
732
|
-
const analysis = this.analyze(input);
|
|
733
|
-
return {
|
|
734
|
-
dangerous: analysis.dangerous,
|
|
735
|
-
reason: analysis.reason
|
|
736
|
-
};
|
|
737
|
-
}
|
|
738
|
-
/**
|
|
739
|
-
* Add a command to the blocked list.
|
|
740
|
-
* Persists the change to the database if storage is configured.
|
|
741
|
-
*
|
|
742
|
-
* @param command - Command to block
|
|
743
|
-
*/
|
|
744
|
-
block(command) {
|
|
745
|
-
this.blockedCommands.add(command);
|
|
746
|
-
// Persist to database asynchronously
|
|
747
|
-
this.persistPolicy().catch(() => {
|
|
748
|
-
// Silently ignore persistence errors
|
|
749
|
-
});
|
|
750
|
-
}
|
|
751
|
-
/**
|
|
752
|
-
* Remove a command from the blocked list.
|
|
753
|
-
* Persists the change to the database if storage is configured.
|
|
754
|
-
*
|
|
755
|
-
* @param command - Command to unblock
|
|
756
|
-
*/
|
|
757
|
-
unblock(command) {
|
|
758
|
-
this.blockedCommands.delete(command);
|
|
759
|
-
// Persist to database asynchronously
|
|
760
|
-
this.persistPolicy().catch(() => {
|
|
761
|
-
// Silently ignore persistence errors
|
|
762
|
-
});
|
|
763
|
-
}
|
|
764
|
-
/**
|
|
765
|
-
* Get the list of blocked commands.
|
|
766
|
-
*
|
|
767
|
-
* @returns Array of blocked command names
|
|
768
|
-
*/
|
|
769
|
-
getBlockedCommands() {
|
|
770
|
-
return Array.from(this.blockedCommands);
|
|
771
|
-
}
|
|
772
|
-
/**
|
|
773
|
-
* Get the current execution policy.
|
|
774
|
-
*
|
|
775
|
-
* @returns Current policy configuration
|
|
776
|
-
*/
|
|
777
|
-
getPolicy() {
|
|
778
|
-
return {
|
|
779
|
-
name: this.policyName,
|
|
780
|
-
blockedCommands: Array.from(this.blockedCommands),
|
|
781
|
-
requireConfirmation: this.requireConfirmation,
|
|
782
|
-
defaultTimeout: this.defaultTimeout,
|
|
783
|
-
defaultCwd: this.defaultCwd,
|
|
784
|
-
allowedPatterns: this.allowedPatterns.map(p => p.source),
|
|
785
|
-
deniedPatterns: this.deniedPatterns.map(p => p.source),
|
|
786
|
-
maxConcurrent: this.maxConcurrent,
|
|
787
|
-
enabled: this.enabled
|
|
788
|
-
};
|
|
789
|
-
}
|
|
790
|
-
/**
|
|
791
|
-
* Update the execution policy.
|
|
792
|
-
* Persists the changes to the database if storage is configured.
|
|
793
|
-
*
|
|
794
|
-
* @param policy - Partial policy configuration to update
|
|
795
|
-
*/
|
|
796
|
-
async updatePolicy(policy) {
|
|
797
|
-
if (policy.blockedCommands !== undefined) {
|
|
798
|
-
this.blockedCommands = new Set(policy.blockedCommands);
|
|
799
|
-
}
|
|
800
|
-
if (policy.requireConfirmation !== undefined) {
|
|
801
|
-
this.requireConfirmation = policy.requireConfirmation;
|
|
802
|
-
}
|
|
803
|
-
if (policy.defaultTimeout !== undefined) {
|
|
804
|
-
this.defaultTimeout = policy.defaultTimeout;
|
|
805
|
-
}
|
|
806
|
-
if (policy.defaultCwd !== undefined) {
|
|
807
|
-
this.defaultCwd = policy.defaultCwd;
|
|
808
|
-
}
|
|
809
|
-
if (policy.allowedPatterns !== undefined) {
|
|
810
|
-
this.allowedPatterns = policy.allowedPatterns.map(p => new RegExp(p));
|
|
811
|
-
}
|
|
812
|
-
if (policy.deniedPatterns !== undefined) {
|
|
813
|
-
this.deniedPatterns = policy.deniedPatterns.map(p => new RegExp(p));
|
|
814
|
-
}
|
|
815
|
-
if (policy.maxConcurrent !== undefined) {
|
|
816
|
-
this.maxConcurrent = policy.maxConcurrent;
|
|
817
|
-
}
|
|
818
|
-
if (policy.enabled !== undefined) {
|
|
819
|
-
this.enabled = policy.enabled;
|
|
820
|
-
}
|
|
821
|
-
await this.persistPolicy();
|
|
822
|
-
}
|
|
823
|
-
/**
|
|
824
|
-
* Check if the policy is enabled.
|
|
825
|
-
*
|
|
826
|
-
* @returns True if the policy is enabled
|
|
827
|
-
*/
|
|
828
|
-
isEnabled() {
|
|
829
|
-
return this.enabled;
|
|
830
|
-
}
|
|
831
|
-
/**
|
|
832
|
-
* Check if database storage is available.
|
|
833
|
-
*
|
|
834
|
-
* @returns True if storage is configured
|
|
835
|
-
*/
|
|
836
|
-
hasStorage() {
|
|
837
|
-
return this.storage !== undefined;
|
|
838
|
-
}
|
|
839
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
840
|
-
// Private Helper Methods
|
|
841
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
842
|
-
/**
|
|
843
|
-
* Extract the base command name from a command string.
|
|
844
|
-
*/
|
|
845
|
-
extractBaseCommand(command) {
|
|
846
|
-
const parts = command.trim().split(/\s+/);
|
|
847
|
-
const first = parts[0] ?? '';
|
|
848
|
-
// Handle paths like /usr/bin/rm
|
|
849
|
-
const name = first.split('/').pop() ?? first;
|
|
850
|
-
return name;
|
|
851
|
-
}
|
|
852
|
-
/**
|
|
853
|
-
* Extract all command names from a script.
|
|
854
|
-
*/
|
|
855
|
-
extractCommands(input) {
|
|
856
|
-
const commands = [];
|
|
857
|
-
// Split by common separators
|
|
858
|
-
const segments = input.split(/[;&|]+/);
|
|
859
|
-
for (const segment of segments) {
|
|
860
|
-
const trimmed = segment.trim();
|
|
861
|
-
if (!trimmed)
|
|
862
|
-
continue;
|
|
863
|
-
// Skip comments
|
|
864
|
-
if (trimmed.startsWith('#'))
|
|
865
|
-
continue;
|
|
866
|
-
// Get the first word
|
|
867
|
-
const match = trimmed.match(/^(\S+)/);
|
|
868
|
-
if (match) {
|
|
869
|
-
const cmd = match[1];
|
|
870
|
-
// Handle paths
|
|
871
|
-
const name = cmd.split('/').pop() ?? cmd;
|
|
872
|
-
// Skip shell keywords
|
|
873
|
-
if (!['if', 'then', 'else', 'fi', 'for', 'do', 'done', 'while', 'until', 'case', 'esac', 'function'].includes(name)) {
|
|
874
|
-
commands.push(name);
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
return commands;
|
|
879
|
-
}
|
|
880
|
-
/**
|
|
881
|
-
* Escape an argument for safe shell use.
|
|
882
|
-
*/
|
|
883
|
-
escapeArg(arg) {
|
|
884
|
-
// If the argument contains no special characters, return as-is
|
|
885
|
-
if (/^[a-zA-Z0-9._\-/=]+$/.test(arg)) {
|
|
886
|
-
return arg;
|
|
887
|
-
}
|
|
888
|
-
// Otherwise, single-quote escape
|
|
889
|
-
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
890
|
-
}
|
|
891
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
892
|
-
// Tagged Template Literal Support
|
|
893
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
894
|
-
/**
|
|
895
|
-
* Tagged template literal for safe bash command execution.
|
|
896
|
-
*
|
|
897
|
-
* This method allows using template literal syntax for bash commands with
|
|
898
|
-
* automatic variable interpolation and escaping. Variables are safely
|
|
899
|
-
* escaped to prevent shell injection attacks.
|
|
900
|
-
*
|
|
901
|
-
* @param strings - Template literal string parts
|
|
902
|
-
* @param values - Interpolated values
|
|
903
|
-
* @returns Promise resolving to the execution result
|
|
904
|
-
*
|
|
905
|
-
* @example
|
|
906
|
-
* ```typescript
|
|
907
|
-
* // Simple usage
|
|
908
|
-
* const result = await this.$.bash`ls -la`
|
|
909
|
-
*
|
|
910
|
-
* // With interpolation
|
|
911
|
-
* const dir = '/tmp/my folder'
|
|
912
|
-
* const result = await this.$.bash`ls -la ${dir}`
|
|
913
|
-
*
|
|
914
|
-
* // With multiple variables
|
|
915
|
-
* const src = 'file.txt'
|
|
916
|
-
* const dest = 'backup/file.txt'
|
|
917
|
-
* const result = await this.$.bash`cp ${src} ${dest}`
|
|
918
|
-
* ```
|
|
919
|
-
*/
|
|
920
|
-
tag(strings, ...values) {
|
|
921
|
-
const command = this.buildCommandFromTemplate(strings, values);
|
|
922
|
-
return this.run(command);
|
|
923
|
-
}
|
|
924
|
-
/**
|
|
925
|
-
* Build a command string from template literal parts with safe escaping.
|
|
926
|
-
*
|
|
927
|
-
* @param strings - Template literal string parts
|
|
928
|
-
* @param values - Interpolated values
|
|
929
|
-
* @returns The constructed command string with escaped values
|
|
930
|
-
* @internal
|
|
931
|
-
*/
|
|
932
|
-
buildCommandFromTemplate(strings, values) {
|
|
933
|
-
let result = '';
|
|
934
|
-
for (let i = 0; i < strings.length; i++) {
|
|
935
|
-
result += strings[i];
|
|
936
|
-
if (i < values.length) {
|
|
937
|
-
const value = values[i];
|
|
938
|
-
result += this.escapeTemplateValue(value);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
return result;
|
|
942
|
-
}
|
|
943
|
-
/**
|
|
944
|
-
* Escape a template literal value for safe shell interpolation.
|
|
945
|
-
*
|
|
946
|
-
* Handles various types of values:
|
|
947
|
-
* - null/undefined: empty string
|
|
948
|
-
* - string: escaped with single quotes if needed
|
|
949
|
-
* - number/boolean: converted to string directly
|
|
950
|
-
* - array: each element escaped and joined with spaces
|
|
951
|
-
* - object: JSON stringified and escaped
|
|
952
|
-
*
|
|
953
|
-
* @param value - The value to escape
|
|
954
|
-
* @returns The escaped string representation
|
|
955
|
-
* @internal
|
|
956
|
-
*/
|
|
957
|
-
escapeTemplateValue(value) {
|
|
958
|
-
// Handle null/undefined
|
|
959
|
-
if (value === null || value === undefined) {
|
|
960
|
-
return '';
|
|
961
|
-
}
|
|
962
|
-
// Handle numbers and booleans - safe to use directly
|
|
963
|
-
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
964
|
-
return String(value);
|
|
965
|
-
}
|
|
966
|
-
// Handle arrays - escape each element and join
|
|
967
|
-
if (Array.isArray(value)) {
|
|
968
|
-
return value.map(v => this.escapeTemplateValue(v)).join(' ');
|
|
969
|
-
}
|
|
970
|
-
// Handle objects (except arrays) - JSON stringify and escape
|
|
971
|
-
if (typeof value === 'object') {
|
|
972
|
-
return this.escapeShellString(JSON.stringify(value));
|
|
973
|
-
}
|
|
974
|
-
// Handle strings
|
|
975
|
-
return this.escapeShellString(String(value));
|
|
976
|
-
}
|
|
977
|
-
/**
|
|
978
|
-
* Escape a string for safe shell use.
|
|
979
|
-
*
|
|
980
|
-
* Uses single-quote escaping which is the safest form of escaping
|
|
981
|
-
* for bash. Single quotes prevent all special character interpretation
|
|
982
|
-
* except for the single quote itself.
|
|
983
|
-
*
|
|
984
|
-
* @param str - The string to escape
|
|
985
|
-
* @returns The escaped string
|
|
986
|
-
* @internal
|
|
987
|
-
*/
|
|
988
|
-
escapeShellString(str) {
|
|
989
|
-
// If the string is empty, return empty quoted string
|
|
990
|
-
if (str === '') {
|
|
991
|
-
return "''";
|
|
992
|
-
}
|
|
993
|
-
// If the string contains no special characters, return as-is
|
|
994
|
-
// This is more readable for simple cases like file paths without spaces
|
|
995
|
-
if (/^[a-zA-Z0-9._\-/=@:]+$/.test(str)) {
|
|
996
|
-
return str;
|
|
997
|
-
}
|
|
998
|
-
// Otherwise, use single-quote escaping
|
|
999
|
-
// Single quotes prevent all interpretation except ' itself
|
|
1000
|
-
// To include a single quote, we end the quoted string, add an escaped quote, and start a new quoted string
|
|
1001
|
-
// 'It'\''s' -> It's
|
|
1002
|
-
return `'${str.replace(/'/g, "'\\''")}'`;
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
// ============================================================================
|
|
1006
|
-
// Factory Functions
|
|
1007
|
-
// ============================================================================
|
|
1008
|
-
/**
|
|
1009
|
-
* Create a BashModule instance with the given options.
|
|
1010
|
-
*
|
|
1011
|
-
* @param options - Configuration options for the module
|
|
1012
|
-
* @returns A new BashModule instance
|
|
1013
|
-
*
|
|
1014
|
-
* @example
|
|
1015
|
-
* ```typescript
|
|
1016
|
-
* import { createBashModule } from 'gitx.do/do'
|
|
1017
|
-
*
|
|
1018
|
-
* const bash = createBashModule({
|
|
1019
|
-
* executor: containerExecutor,
|
|
1020
|
-
* fs: workflowContext.fs,
|
|
1021
|
-
* cwd: '/app'
|
|
1022
|
-
* })
|
|
1023
|
-
* ```
|
|
1024
|
-
*/
|
|
1025
|
-
export function createBashModule(options = {}) {
|
|
1026
|
-
return new BashModule(options);
|
|
1027
|
-
}
|
|
1028
|
-
/**
|
|
1029
|
-
* Create a callable BashModule instance that supports tagged template literals.
|
|
1030
|
-
*
|
|
1031
|
-
* This factory creates a BashModule wrapped in a Proxy that allows both:
|
|
1032
|
-
* - Standard method calls: `bash.exec('ls', ['-la'])`
|
|
1033
|
-
* - Tagged template syntax: `bash\`ls -la ${dir}\``
|
|
1034
|
-
*
|
|
1035
|
-
* The tagged template syntax automatically escapes interpolated values
|
|
1036
|
-
* to prevent shell injection attacks.
|
|
1037
|
-
*
|
|
1038
|
-
* @param options - Configuration options for the module
|
|
1039
|
-
* @returns A callable BashModule instance
|
|
1040
|
-
*
|
|
1041
|
-
* @example
|
|
1042
|
-
* ```typescript
|
|
1043
|
-
* import { createCallableBashModule } from 'gitx.do/do'
|
|
1044
|
-
*
|
|
1045
|
-
* // In a Durable Object
|
|
1046
|
-
* class MyDO extends DO {
|
|
1047
|
-
* bash = createCallableBashModule({
|
|
1048
|
-
* executor: containerExecutor,
|
|
1049
|
-
* fs: this.$.fs,
|
|
1050
|
-
* cwd: '/app'
|
|
1051
|
-
* })
|
|
1052
|
-
*
|
|
1053
|
-
* async listFiles(dir: string) {
|
|
1054
|
-
* // Use tagged template syntax
|
|
1055
|
-
* const result = await this.bash`ls -la ${dir}`
|
|
1056
|
-
* return result.stdout
|
|
1057
|
-
* }
|
|
1058
|
-
*
|
|
1059
|
-
* async runWithArgs() {
|
|
1060
|
-
* // Or use regular methods
|
|
1061
|
-
* const result = await this.bash.exec('npm', ['install'])
|
|
1062
|
-
* return result
|
|
1063
|
-
* }
|
|
1064
|
-
* }
|
|
1065
|
-
* ```
|
|
1066
|
-
*
|
|
1067
|
-
* @example
|
|
1068
|
-
* ```typescript
|
|
1069
|
-
* // Handle special characters safely
|
|
1070
|
-
* const filename = "file with 'quotes' and spaces"
|
|
1071
|
-
* const result = await bash`cat ${filename}`
|
|
1072
|
-
* // Executes: cat 'file with '\''quotes'\'' and spaces'
|
|
1073
|
-
* ```
|
|
1074
|
-
*/
|
|
1075
|
-
export function createCallableBashModule(options = {}) {
|
|
1076
|
-
const module = new BashModule(options);
|
|
1077
|
-
// Create a function that calls the tag method
|
|
1078
|
-
const tagFn = (strings, ...values) => {
|
|
1079
|
-
return module.tag(strings, ...values);
|
|
1080
|
-
};
|
|
1081
|
-
// Create a Proxy that makes the module callable
|
|
1082
|
-
return new Proxy(tagFn, {
|
|
1083
|
-
// Forward property access to the module
|
|
1084
|
-
get(target, prop, receiver) {
|
|
1085
|
-
if (prop in module) {
|
|
1086
|
-
const value = module[prop];
|
|
1087
|
-
// Bind methods to the module
|
|
1088
|
-
if (typeof value === 'function') {
|
|
1089
|
-
return value.bind(module);
|
|
1090
|
-
}
|
|
1091
|
-
return value;
|
|
1092
|
-
}
|
|
1093
|
-
return Reflect.get(target, prop, receiver);
|
|
1094
|
-
},
|
|
1095
|
-
// Forward property setting to the module
|
|
1096
|
-
set(target, prop, value) {
|
|
1097
|
-
if (prop in module) {
|
|
1098
|
-
module[prop] = value;
|
|
1099
|
-
return true;
|
|
1100
|
-
}
|
|
1101
|
-
return Reflect.set(target, prop, value);
|
|
1102
|
-
},
|
|
1103
|
-
// Forward has checks to the module
|
|
1104
|
-
has(target, prop) {
|
|
1105
|
-
return prop in module || Reflect.has(target, prop);
|
|
1106
|
-
},
|
|
1107
|
-
// Make instanceof work
|
|
1108
|
-
getPrototypeOf() {
|
|
1109
|
-
return BashModule.prototype;
|
|
1110
|
-
},
|
|
1111
|
-
// Forward apply to the tag function
|
|
1112
|
-
apply(target, _thisArg, args) {
|
|
1113
|
-
return target(...args);
|
|
1114
|
-
},
|
|
1115
|
-
});
|
|
1116
|
-
}
|
|
1117
|
-
// ============================================================================
|
|
1118
|
-
// Type Guards
|
|
1119
|
-
// ============================================================================
|
|
1120
|
-
/**
|
|
1121
|
-
* Check if a value is a BashModule instance.
|
|
1122
|
-
*
|
|
1123
|
-
* @param value - Value to check
|
|
1124
|
-
* @returns True if value is a BashModule
|
|
1125
|
-
*/
|
|
1126
|
-
export function isBashModule(value) {
|
|
1127
|
-
return value instanceof BashModule;
|
|
1128
|
-
}
|
|
1129
|
-
/**
|
|
1130
|
-
* Check if a value is a CallableBashModule.
|
|
1131
|
-
*
|
|
1132
|
-
* @param value - Value to check
|
|
1133
|
-
* @returns True if value is a CallableBashModule
|
|
1134
|
-
*/
|
|
1135
|
-
export function isCallableBashModule(value) {
|
|
1136
|
-
if (typeof value !== 'function')
|
|
1137
|
-
return false;
|
|
1138
|
-
if (!('name' in value))
|
|
1139
|
-
return false;
|
|
1140
|
-
const maybeBash = value;
|
|
1141
|
-
return maybeBash.name === 'bash' && 'exec' in value && typeof maybeBash.exec === 'function';
|
|
1142
|
-
}
|
|
1143
|
-
//# sourceMappingURL=BashModule.js.map
|