kiriya 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -0
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/dist/src/config/clean-rules.js +39 -0
- package/dist/src/config/config-keys.js +24 -0
- package/dist/src/config/dependency-directories.js +16 -0
- package/dist/src/config/modules.js +38 -0
- package/dist/src/config/protected-paths.js +32 -0
- package/dist/src/config/rebuildable-directories.js +20 -0
- package/dist/src/core/application/command-registry.js +57 -0
- package/dist/src/core/application/config-values.js +33 -0
- package/dist/src/core/application/path-guard.js +69 -0
- package/dist/src/core/application/paths.js +77 -0
- package/dist/src/core/application/plugin-loader.js +100 -0
- package/dist/src/core/application/process-ending.js +28 -0
- package/dist/src/core/application/root-scope.js +123 -0
- package/dist/src/core/application/safety.js +26 -0
- package/dist/src/core/application/text-input.js +44 -0
- package/dist/src/core/application/walk.js +34 -0
- package/dist/src/core/domain/command.js +8 -0
- package/dist/src/core/domain/encodings.js +70 -0
- package/dist/src/core/domain/errors.js +49 -0
- package/dist/src/core/domain/glob.js +57 -0
- package/dist/src/core/domain/global-options.js +10 -0
- package/dist/src/core/domain/input-schema.js +54 -0
- package/dist/src/core/domain/message.js +7 -0
- package/dist/src/core/domain/module.js +2 -0
- package/dist/src/core/domain/names.js +22 -0
- package/dist/src/core/domain/ports/clipboard.js +2 -0
- package/dist/src/core/domain/ports/clock.js +2 -0
- package/dist/src/core/domain/ports/command-catalog.js +2 -0
- package/dist/src/core/domain/ports/compression.js +2 -0
- package/dist/src/core/domain/ports/config-store.js +2 -0
- package/dist/src/core/domain/ports/confirmation.js +2 -0
- package/dist/src/core/domain/ports/environment.js +2 -0
- package/dist/src/core/domain/ports/file-content.js +2 -0
- package/dist/src/core/domain/ports/file-system.js +2 -0
- package/dist/src/core/domain/ports/hasher.js +2 -0
- package/dist/src/core/domain/ports/network.js +2 -0
- package/dist/src/core/domain/ports/opener.js +2 -0
- package/dist/src/core/domain/ports/passthrough.js +2 -0
- package/dist/src/core/domain/ports/plugin-inventory.js +2 -0
- package/dist/src/core/domain/ports/plugin-source.js +2 -0
- package/dist/src/core/domain/ports/port-table.js +2 -0
- package/dist/src/core/domain/ports/process-runner.js +2 -0
- package/dist/src/core/domain/ports/process-table.js +2 -0
- package/dist/src/core/domain/ports/protected-paths.js +2 -0
- package/dist/src/core/domain/ports/random-source.js +2 -0
- package/dist/src/core/domain/ports/standard-input.js +2 -0
- package/dist/src/core/domain/ports/system-info.js +2 -0
- package/dist/src/core/domain/ports/trash.js +2 -0
- package/dist/src/core/domain/secrets.js +59 -0
- package/dist/src/core/domain/targets.js +41 -0
- package/dist/src/core/domain/text-case.js +39 -0
- package/dist/src/core/domain/values/bytes.js +13 -0
- package/dist/src/core/domain/view.js +2 -0
- package/dist/src/core/infrastructure/node/can-listen.js +10 -0
- package/dist/src/core/infrastructure/node/closed-standard-input.adapter.js +12 -0
- package/dist/src/core/infrastructure/node/config-location.js +27 -0
- package/dist/src/core/infrastructure/node/fs-errors.js +22 -0
- package/dist/src/core/infrastructure/node/json-config-store.adapter.js +69 -0
- package/dist/src/core/infrastructure/node/node-compression.adapter.js +82 -0
- package/dist/src/core/infrastructure/node/node-environment.adapter.js +21 -0
- package/dist/src/core/infrastructure/node/node-file-content.adapter.js +99 -0
- package/dist/src/core/infrastructure/node/node-file-system.adapter.js +169 -0
- package/dist/src/core/infrastructure/node/node-hasher.adapter.js +32 -0
- package/dist/src/core/infrastructure/node/node-network.adapter.js +168 -0
- package/dist/src/core/infrastructure/node/node-plugin-source.adapter.js +89 -0
- package/dist/src/core/infrastructure/node/node-process-runner.adapter.js +48 -0
- package/dist/src/core/infrastructure/node/node-random-source.adapter.js +7 -0
- package/dist/src/core/infrastructure/node/node-standard-input.adapter.js +24 -0
- package/dist/src/core/infrastructure/node/node-system-info.adapter.js +63 -0
- package/dist/src/core/infrastructure/node/process-signals.js +51 -0
- package/dist/src/core/infrastructure/node/run-program.js +65 -0
- package/dist/src/core/infrastructure/node/system-clock.adapter.js +22 -0
- package/dist/src/core/infrastructure/node/typed-programs.js +45 -0
- package/dist/src/core/infrastructure/platform/linux/freedesktop-trash.adapter.js +115 -0
- package/dist/src/core/infrastructure/platform/linux/linux-clipboard.adapter.js +70 -0
- package/dist/src/core/infrastructure/platform/linux/linux-opener.adapter.js +21 -0
- package/dist/src/core/infrastructure/platform/linux/linux-port-table.adapter.js +97 -0
- package/dist/src/core/infrastructure/platform/linux/linux-process-table.adapter.js +61 -0
- package/dist/src/core/infrastructure/platform/macos/macos-clipboard.adapter.js +29 -0
- package/dist/src/core/infrastructure/platform/macos/macos-opener.adapter.js +13 -0
- package/dist/src/core/infrastructure/platform/macos/macos-port-table.adapter.js +86 -0
- package/dist/src/core/infrastructure/platform/macos/macos-process-table.adapter.js +62 -0
- package/dist/src/core/infrastructure/platform/macos/macos-trash.adapter.js +64 -0
- package/dist/src/core/infrastructure/platform/windows/system32.js +6 -0
- package/dist/src/core/infrastructure/platform/windows/windows-clipboard.adapter.js +50 -0
- package/dist/src/core/infrastructure/platform/windows/windows-opener.adapter.js +11 -0
- package/dist/src/core/infrastructure/platform/windows/windows-port-table.adapter.js +38 -0
- package/dist/src/core/infrastructure/platform/windows/windows-process-table.adapter.js +134 -0
- package/dist/src/core/infrastructure/platform/windows/windows-trash.adapter.js +86 -0
- package/dist/src/core/presentation/cli/argv.js +83 -0
- package/dist/src/core/presentation/cli/cli-application.js +201 -0
- package/dist/src/core/presentation/cli/cli-view-format.js +46 -0
- package/dist/src/core/presentation/cli/help.js +202 -0
- package/dist/src/core/presentation/cli/json-output.js +28 -0
- package/dist/src/core/presentation/cli/style.js +21 -0
- package/dist/src/core/presentation/cli/suggest.js +25 -0
- package/dist/src/core/presentation/cli/terminal-confirmation.js +63 -0
- package/dist/src/core/presentation/ended-processes.js +18 -0
- package/dist/src/core/presentation/i18n/translator.js +23 -0
- package/dist/src/core/presentation/list-preview.js +10 -0
- package/dist/src/core/presentation/mcp/elicitation.js +55 -0
- package/dist/src/core/presentation/mcp/mcp-application.js +60 -0
- package/dist/src/core/presentation/mcp/mcp-confirmation.js +59 -0
- package/dist/src/core/presentation/mcp/mcp-server.js +355 -0
- package/dist/src/core/presentation/mcp/protocol.js +32 -0
- package/dist/src/core/presentation/mcp/request-state.js +66 -0
- package/dist/src/core/presentation/mcp/tool-definitions.js +149 -0
- package/dist/src/core/presentation/mcp/tool-results.js +56 -0
- package/dist/src/i18n/locales/en.js +827 -0
- package/dist/src/main.js +154 -0
- package/dist/src/modules/archive/application/archive-sources.js +55 -0
- package/dist/src/modules/archive/application/create-tar.use-case.js +123 -0
- package/dist/src/modules/archive/application/create-zip.use-case.js +106 -0
- package/dist/src/modules/archive/application/extract-tar.use-case.js +240 -0
- package/dist/src/modules/archive/application/extract-zip.use-case.js +181 -0
- package/dist/src/modules/archive/application/tar-reader.js +45 -0
- package/dist/src/modules/archive/application/zip-reader.js +57 -0
- package/dist/src/modules/archive/archive.module.js +27 -0
- package/dist/src/modules/archive/domain/crc32.js +18 -0
- package/dist/src/modules/archive/domain/tar-format.js +97 -0
- package/dist/src/modules/archive/domain/tar-stream.js +223 -0
- package/dist/src/modules/archive/domain/zip-format.js +158 -0
- package/dist/src/modules/archive/presentation/tar.view.js +17 -0
- package/dist/src/modules/archive/presentation/untar.view.js +30 -0
- package/dist/src/modules/archive/presentation/unzip.view.js +24 -0
- package/dist/src/modules/archive/presentation/zip.view.js +17 -0
- package/dist/src/modules/clip/application/copy-text.use-case.js +43 -0
- package/dist/src/modules/clip/application/paste-text.use-case.js +25 -0
- package/dist/src/modules/clip/clip.module.js +16 -0
- package/dist/src/modules/clip/presentation/clip.views.js +7 -0
- package/dist/src/modules/completion/application/print-script.use-case.js +38 -0
- package/dist/src/modules/completion/application/suggest-words.use-case.js +37 -0
- package/dist/src/modules/completion/completion.module.js +14 -0
- package/dist/src/modules/completion/domain/scripts.js +91 -0
- package/dist/src/modules/completion/domain/suggest.js +134 -0
- package/dist/src/modules/completion/presentation/completion.views.js +6 -0
- package/dist/src/modules/config/application/config-commands.js +14 -0
- package/dist/src/modules/config/application/get-setting.use-case.js +25 -0
- package/dist/src/modules/config/application/list-config-keys.use-case.js +26 -0
- package/dist/src/modules/config/application/list-settings.use-case.js +28 -0
- package/dist/src/modules/config/application/set-setting.use-case.js +53 -0
- package/dist/src/modules/config/application/show-config-path.use-case.js +21 -0
- package/dist/src/modules/config/application/unset-setting.use-case.js +28 -0
- package/dist/src/modules/config/config.module.js +28 -0
- package/dist/src/modules/config/presentation/config.views.js +33 -0
- package/dist/src/modules/convert/application/convert-case.use-case.js +38 -0
- package/dist/src/modules/convert/application/convert-codecs.use-case.js +92 -0
- package/dist/src/modules/convert/application/convert-input.js +16 -0
- package/dist/src/modules/convert/application/convert-json.use-case.js +51 -0
- package/dist/src/modules/convert/application/convert-time.use-case.js +39 -0
- package/dist/src/modules/convert/application/decode-jwt.use-case.js +52 -0
- package/dist/src/modules/convert/convert.module.js +29 -0
- package/dist/src/modules/convert/domain/json-syntax.js +135 -0
- package/dist/src/modules/convert/domain/jwt.js +29 -0
- package/dist/src/modules/convert/domain/moments.js +20 -0
- package/dist/src/modules/convert/presentation/convert.views.js +44 -0
- package/dist/src/modules/docker/application/clean-engine.use-case.js +77 -0
- package/dist/src/modules/docker/application/compose-project.js +45 -0
- package/dist/src/modules/docker/application/docker-engine.js +40 -0
- package/dist/src/modules/docker/application/list-containers.use-case.js +69 -0
- package/dist/src/modules/docker/application/run-compose.use-case.js +83 -0
- package/dist/src/modules/docker/application/show-logs.use-case.js +51 -0
- package/dist/src/modules/docker/application/stop-project.use-case.js +56 -0
- package/dist/src/modules/docker/docker.module.js +23 -0
- package/dist/src/modules/docker/domain/compose-file.js +105 -0
- package/dist/src/modules/docker/domain/engine-output.js +65 -0
- package/dist/src/modules/docker/presentation/docker.views.js +63 -0
- package/dist/src/modules/doctor/application/run-checks.use-case.js +128 -0
- package/dist/src/modules/doctor/doctor.module.js +13 -0
- package/dist/src/modules/doctor/domain/versions.js +22 -0
- package/dist/src/modules/doctor/presentation/doctor.view.js +30 -0
- package/dist/src/modules/env/application/check-dotenv.use-case.js +116 -0
- package/dist/src/modules/env/application/inspect-path.use-case.js +71 -0
- package/dist/src/modules/env/application/path-entries.js +28 -0
- package/dist/src/modules/env/application/show-variables.use-case.js +47 -0
- package/dist/src/modules/env/domain/dotenv.js +62 -0
- package/dist/src/modules/env/env.module.js +17 -0
- package/dist/src/modules/env/presentation/env.views.js +43 -0
- package/dist/src/modules/files/application/clean-builds.use-case.js +149 -0
- package/dist/src/modules/files/application/compare-paths.use-case.js +127 -0
- package/dist/src/modules/files/application/copy-paths.use-case.js +101 -0
- package/dist/src/modules/files/application/create-paths.use-case.js +85 -0
- package/dist/src/modules/files/application/delete-paths.use-case.js +127 -0
- package/dist/src/modules/files/application/find-duplicates.use-case.js +105 -0
- package/dist/src/modules/files/application/find-files.use-case.js +131 -0
- package/dist/src/modules/files/application/hash-files.use-case.js +73 -0
- package/dist/src/modules/files/application/list-entries.use-case.js +87 -0
- package/dist/src/modules/files/application/measure-folders.use-case.js +102 -0
- package/dist/src/modules/files/application/move-entry.js +19 -0
- package/dist/src/modules/files/application/move-paths.use-case.js +82 -0
- package/dist/src/modules/files/application/read-text.use-case.js +92 -0
- package/dist/src/modules/files/application/rename-paths.use-case.js +184 -0
- package/dist/src/modules/files/application/replace-text.use-case.js +146 -0
- package/dist/src/modules/files/application/search-text.use-case.js +115 -0
- package/dist/src/modules/files/application/show-info.use-case.js +80 -0
- package/dist/src/modules/files/application/show-tree.use-case.js +75 -0
- package/dist/src/modules/files/application/sync-folders.use-case.js +162 -0
- package/dist/src/modules/files/application/text-files.js +46 -0
- package/dist/src/modules/files/application/transfers.js +35 -0
- package/dist/src/modules/files/domain/case-style.js +18 -0
- package/dist/src/modules/files/domain/filters.js +9 -0
- package/dist/src/modules/files/domain/sync-plan.js +35 -0
- package/dist/src/modules/files/domain/text-encoding.js +75 -0
- package/dist/src/modules/files/domain/values.js +28 -0
- package/dist/src/modules/files/files.module.js +70 -0
- package/dist/src/modules/files/presentation/clean.view.js +15 -0
- package/dist/src/modules/files/presentation/compare.view.js +50 -0
- package/dist/src/modules/files/presentation/copy.view.js +17 -0
- package/dist/src/modules/files/presentation/delete.view.js +16 -0
- package/dist/src/modules/files/presentation/dupes.view.js +20 -0
- package/dist/src/modules/files/presentation/find.view.js +16 -0
- package/dist/src/modules/files/presentation/grep.view.js +27 -0
- package/dist/src/modules/files/presentation/hash.view.js +15 -0
- package/dist/src/modules/files/presentation/info.view.js +36 -0
- package/dist/src/modules/files/presentation/list.view.js +17 -0
- package/dist/src/modules/files/presentation/move.view.js +14 -0
- package/dist/src/modules/files/presentation/new.view.js +8 -0
- package/dist/src/modules/files/presentation/read.view.js +6 -0
- package/dist/src/modules/files/presentation/rename.view.js +16 -0
- package/dist/src/modules/files/presentation/replace.view.js +26 -0
- package/dist/src/modules/files/presentation/size.view.js +24 -0
- package/dist/src/modules/files/presentation/sync.view.js +37 -0
- package/dist/src/modules/files/presentation/tree.view.js +22 -0
- package/dist/src/modules/gen/application/gen-options.js +18 -0
- package/dist/src/modules/gen/application/generate-passwords.use-case.js +39 -0
- package/dist/src/modules/gen/application/generate-tokens.use-case.js +38 -0
- package/dist/src/modules/gen/application/generate-ulids.use-case.js +40 -0
- package/dist/src/modules/gen/application/generate-uuids.use-case.js +32 -0
- package/dist/src/modules/gen/domain/identifiers.js +65 -0
- package/dist/src/modules/gen/domain/secrets.js +43 -0
- package/dist/src/modules/gen/gen.module.js +20 -0
- package/dist/src/modules/git/application/fetch-repositories.use-case.js +50 -0
- package/dist/src/modules/git/application/git-client.js +62 -0
- package/dist/src/modules/git/application/pull-repositories.use-case.js +67 -0
- package/dist/src/modules/git/application/repositories.js +67 -0
- package/dist/src/modules/git/application/show-status.use-case.js +49 -0
- package/dist/src/modules/git/application/switch-branch.use-case.js +118 -0
- package/dist/src/modules/git/domain/repository-status.js +37 -0
- package/dist/src/modules/git/git.module.js +25 -0
- package/dist/src/modules/git/presentation/git.views.js +100 -0
- package/dist/src/modules/net/application/check-connection.use-case.js +71 -0
- package/dist/src/modules/net/application/list-addresses.use-case.js +29 -0
- package/dist/src/modules/net/application/lookup-name.use-case.js +55 -0
- package/dist/src/modules/net/net.module.js +17 -0
- package/dist/src/modules/net/presentation/net.views.js +39 -0
- package/dist/src/modules/open/application/open-target.use-case.js +53 -0
- package/dist/src/modules/open/domain/targets.js +45 -0
- package/dist/src/modules/open/open.module.js +12 -0
- package/dist/src/modules/open/presentation/open.view.js +6 -0
- package/dist/src/modules/port/application/end-port-owners.use-case.js +68 -0
- package/dist/src/modules/port/application/find-free-port.use-case.js +40 -0
- package/dist/src/modules/port/application/find-listeners.use-case.js +34 -0
- package/dist/src/modules/port/application/port-owners.js +25 -0
- package/dist/src/modules/port/domain/port-number.js +8 -0
- package/dist/src/modules/port/port.module.js +17 -0
- package/dist/src/modules/port/presentation/port.views.js +27 -0
- package/dist/src/modules/proc/application/end-processes.use-case.js +77 -0
- package/dist/src/modules/proc/application/find-processes.use-case.js +50 -0
- package/dist/src/modules/proc/application/list-processes.use-case.js +41 -0
- package/dist/src/modules/proc/application/show-tree.use-case.js +40 -0
- package/dist/src/modules/proc/domain/process-tree.js +55 -0
- package/dist/src/modules/proc/presentation/proc.views.js +28 -0
- package/dist/src/modules/proc/proc.module.js +19 -0
- package/dist/src/modules/sys/application/create-report.use-case.js +30 -0
- package/dist/src/modules/sys/application/find-tools.use-case.js +20 -0
- package/dist/src/modules/sys/application/machine-facts.js +44 -0
- package/dist/src/modules/sys/application/show-info.use-case.js +29 -0
- package/dist/src/modules/sys/domain/tools.js +17 -0
- package/dist/src/modules/sys/presentation/sys.views.js +67 -0
- package/dist/src/modules/sys/sys.module.js +17 -0
- package/dist/src/modules/wait/application/poll.js +26 -0
- package/dist/src/modules/wait/application/wait-file.use-case.js +55 -0
- package/dist/src/modules/wait/application/wait-port.use-case.js +76 -0
- package/dist/src/modules/wait/application/wait-url.use-case.js +71 -0
- package/dist/src/modules/wait/domain/reasons.js +14 -0
- package/dist/src/modules/wait/domain/wait-input.js +50 -0
- package/dist/src/modules/wait/presentation/wait.views.js +31 -0
- package/dist/src/modules/wait/wait.module.js +21 -0
- package/package.json +57 -0
package/dist/src/main.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The composition root: the only file that constructs adapters and wires them in.
|
|
4
|
+
* Choosing an adapter by operating system happens here and nowhere else.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { dirname } from "node:path";
|
|
8
|
+
import { BUILT_IN_MODULES } from "./config/modules.js";
|
|
9
|
+
import { CommandRegistry } from "./core/application/command-registry.js";
|
|
10
|
+
import { pluginEntries } from "./core/application/config-values.js";
|
|
11
|
+
import { PathGuard } from "./core/application/path-guard.js";
|
|
12
|
+
import { PluginLoader } from "./core/application/plugin-loader.js";
|
|
13
|
+
import { ClosedStandardInput } from "./core/infrastructure/node/closed-standard-input.adapter.js";
|
|
14
|
+
import { configFilePath } from "./core/infrastructure/node/config-location.js";
|
|
15
|
+
import { JsonConfigStore } from "./core/infrastructure/node/json-config-store.adapter.js";
|
|
16
|
+
import { NodeCompressionAdapter } from "./core/infrastructure/node/node-compression.adapter.js";
|
|
17
|
+
import { NodeEnvironmentAdapter } from "./core/infrastructure/node/node-environment.adapter.js";
|
|
18
|
+
import { NodeFileContentAdapter } from "./core/infrastructure/node/node-file-content.adapter.js";
|
|
19
|
+
import { NodeFileSystemAdapter } from "./core/infrastructure/node/node-file-system.adapter.js";
|
|
20
|
+
import { NodeHasherAdapter } from "./core/infrastructure/node/node-hasher.adapter.js";
|
|
21
|
+
import { NodeNetworkAdapter } from "./core/infrastructure/node/node-network.adapter.js";
|
|
22
|
+
import { NodePluginSource } from "./core/infrastructure/node/node-plugin-source.adapter.js";
|
|
23
|
+
import { NodeProcessRunnerAdapter } from "./core/infrastructure/node/node-process-runner.adapter.js";
|
|
24
|
+
import { NodeRandomSource } from "./core/infrastructure/node/node-random-source.adapter.js";
|
|
25
|
+
import { NodeStandardInput } from "./core/infrastructure/node/node-standard-input.adapter.js";
|
|
26
|
+
import { NodeSystemInfoAdapter } from "./core/infrastructure/node/node-system-info.adapter.js";
|
|
27
|
+
import { SystemClockAdapter } from "./core/infrastructure/node/system-clock.adapter.js";
|
|
28
|
+
import { FreedesktopTrashAdapter } from "./core/infrastructure/platform/linux/freedesktop-trash.adapter.js";
|
|
29
|
+
import { LinuxClipboardAdapter } from "./core/infrastructure/platform/linux/linux-clipboard.adapter.js";
|
|
30
|
+
import { LinuxOpenerAdapter } from "./core/infrastructure/platform/linux/linux-opener.adapter.js";
|
|
31
|
+
import { LinuxPortTableAdapter } from "./core/infrastructure/platform/linux/linux-port-table.adapter.js";
|
|
32
|
+
import { LinuxProcessTableAdapter } from "./core/infrastructure/platform/linux/linux-process-table.adapter.js";
|
|
33
|
+
import { MacosClipboardAdapter } from "./core/infrastructure/platform/macos/macos-clipboard.adapter.js";
|
|
34
|
+
import { MacosOpenerAdapter } from "./core/infrastructure/platform/macos/macos-opener.adapter.js";
|
|
35
|
+
import { MacosPortTableAdapter } from "./core/infrastructure/platform/macos/macos-port-table.adapter.js";
|
|
36
|
+
import { MacosProcessTableAdapter } from "./core/infrastructure/platform/macos/macos-process-table.adapter.js";
|
|
37
|
+
import { MacosTrashAdapter } from "./core/infrastructure/platform/macos/macos-trash.adapter.js";
|
|
38
|
+
import { WindowsClipboardAdapter } from "./core/infrastructure/platform/windows/windows-clipboard.adapter.js";
|
|
39
|
+
import { WindowsOpenerAdapter } from "./core/infrastructure/platform/windows/windows-opener.adapter.js";
|
|
40
|
+
import { WindowsPortTableAdapter } from "./core/infrastructure/platform/windows/windows-port-table.adapter.js";
|
|
41
|
+
import { WindowsProcessTableAdapter } from "./core/infrastructure/platform/windows/windows-process-table.adapter.js";
|
|
42
|
+
import { WindowsTrashAdapter } from "./core/infrastructure/platform/windows/windows-trash.adapter.js";
|
|
43
|
+
import { splitGlobalFlags } from "./core/presentation/cli/argv.js";
|
|
44
|
+
import { CliApplication } from "./core/presentation/cli/cli-application.js";
|
|
45
|
+
import { serveMcp } from "./core/presentation/mcp/mcp-application.js";
|
|
46
|
+
import { en } from "./i18n/locales/en.js";
|
|
47
|
+
function trashFor(environment) {
|
|
48
|
+
switch (environment.os) {
|
|
49
|
+
case "windows":
|
|
50
|
+
return new WindowsTrashAdapter();
|
|
51
|
+
case "macos":
|
|
52
|
+
return new MacosTrashAdapter(environment);
|
|
53
|
+
case "linux":
|
|
54
|
+
return new FreedesktopTrashAdapter(environment);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function processTableFor(environment, processRunner) {
|
|
58
|
+
switch (environment.os) {
|
|
59
|
+
case "windows":
|
|
60
|
+
return new WindowsProcessTableAdapter(processRunner);
|
|
61
|
+
case "macos":
|
|
62
|
+
return new MacosProcessTableAdapter(processRunner);
|
|
63
|
+
case "linux":
|
|
64
|
+
return new LinuxProcessTableAdapter();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function portTableFor(environment, processRunner) {
|
|
68
|
+
switch (environment.os) {
|
|
69
|
+
case "windows":
|
|
70
|
+
return new WindowsPortTableAdapter(processRunner);
|
|
71
|
+
case "macos":
|
|
72
|
+
return new MacosPortTableAdapter(processRunner);
|
|
73
|
+
case "linux":
|
|
74
|
+
return new LinuxPortTableAdapter();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function clipboardFor(environment, processRunner) {
|
|
78
|
+
switch (environment.os) {
|
|
79
|
+
case "windows":
|
|
80
|
+
return new WindowsClipboardAdapter();
|
|
81
|
+
case "macos":
|
|
82
|
+
return new MacosClipboardAdapter();
|
|
83
|
+
case "linux":
|
|
84
|
+
return new LinuxClipboardAdapter(environment, processRunner);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function openerFor(environment, processRunner) {
|
|
88
|
+
switch (environment.os) {
|
|
89
|
+
case "windows":
|
|
90
|
+
return new WindowsOpenerAdapter();
|
|
91
|
+
case "macos":
|
|
92
|
+
return new MacosOpenerAdapter();
|
|
93
|
+
case "linux":
|
|
94
|
+
return new LinuxOpenerAdapter(environment, processRunner);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const { version } = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
|
|
98
|
+
const argv = process.argv.slice(2);
|
|
99
|
+
// Over MCP, stdin carries the protocol, so no command may read it.
|
|
100
|
+
const servingMcp = splitGlobalFlags(argv).rest[0] === "mcp";
|
|
101
|
+
const environment = new NodeEnvironmentAdapter();
|
|
102
|
+
const config = new JsonConfigStore(configFilePath(environment, process.cwd()));
|
|
103
|
+
const plugins = new PluginLoader();
|
|
104
|
+
const processRunner = new NodeProcessRunnerAdapter();
|
|
105
|
+
const registry = new CommandRegistry();
|
|
106
|
+
const ports = {
|
|
107
|
+
fileSystem: new NodeFileSystemAdapter(),
|
|
108
|
+
fileContent: new NodeFileContentAdapter(),
|
|
109
|
+
hasher: new NodeHasherAdapter(),
|
|
110
|
+
compression: new NodeCompressionAdapter(),
|
|
111
|
+
processRunner,
|
|
112
|
+
trash: trashFor(environment),
|
|
113
|
+
environment,
|
|
114
|
+
clock: new SystemClockAdapter(),
|
|
115
|
+
random: new NodeRandomSource(),
|
|
116
|
+
stdin: servingMcp ? new ClosedStandardInput() : new NodeStandardInput(),
|
|
117
|
+
system: new NodeSystemInfoAdapter(environment, processRunner),
|
|
118
|
+
network: new NodeNetworkAdapter(),
|
|
119
|
+
processTable: processTableFor(environment, processRunner),
|
|
120
|
+
portTable: portTableFor(environment, processRunner),
|
|
121
|
+
clipboard: clipboardFor(environment, processRunner),
|
|
122
|
+
opener: openerFor(environment, processRunner),
|
|
123
|
+
commands: registry,
|
|
124
|
+
protectedPaths: new PathGuard(environment),
|
|
125
|
+
config,
|
|
126
|
+
plugins,
|
|
127
|
+
runtime: { kiriyaVersion: version, nodeVersion: process.version },
|
|
128
|
+
};
|
|
129
|
+
for (const module of BUILT_IN_MODULES)
|
|
130
|
+
registry.register(module, ports);
|
|
131
|
+
// A configuration file kiriya cannot read loads no plugins; `kiriya doctor` and `kiriya config` report why.
|
|
132
|
+
const entries = await config.read().then(pluginEntries, () => []);
|
|
133
|
+
await plugins.load(entries, dirname(config.path), new NodePluginSource(), registry, ports);
|
|
134
|
+
const catalog = { ...en, ...plugins.messages };
|
|
135
|
+
const streams = { stdin: process.stdin, stdout: process.stdout, stderr: process.stderr };
|
|
136
|
+
const cli = new CliApplication({
|
|
137
|
+
registry,
|
|
138
|
+
catalog,
|
|
139
|
+
version,
|
|
140
|
+
environment,
|
|
141
|
+
...streams,
|
|
142
|
+
serveMcp: (input, cwd) => serveMcp({
|
|
143
|
+
registry,
|
|
144
|
+
catalog,
|
|
145
|
+
version,
|
|
146
|
+
fileSystem: ports.fileSystem,
|
|
147
|
+
config,
|
|
148
|
+
random: ports.random,
|
|
149
|
+
clock: ports.clock,
|
|
150
|
+
...streams,
|
|
151
|
+
}, input, cwd),
|
|
152
|
+
});
|
|
153
|
+
process.exitCode = await cli.run(argv, process.cwd());
|
|
154
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { DEPENDENCY_DIRECTORIES } from "../../../config/dependency-directories.js";
|
|
3
|
+
import { expandPaths, outermost } from "../../../core/application/paths.js";
|
|
4
|
+
import { walk } from "../../../core/application/walk.js";
|
|
5
|
+
import { UsageError } from "../../../core/domain/errors.js";
|
|
6
|
+
/** Every entry to store, gathered before writing starts, so a new archive never ends up inside itself. */
|
|
7
|
+
export async function collectSources(fileSystem, paths, cwd, options) {
|
|
8
|
+
const sources = outermost(await expandPaths(fileSystem, paths, cwd, options.all));
|
|
9
|
+
const entries = [];
|
|
10
|
+
const topNames = new Set();
|
|
11
|
+
let skippedLinks = 0;
|
|
12
|
+
for (const source of sources) {
|
|
13
|
+
const name = path.basename(source);
|
|
14
|
+
if (topNames.has(name.toLowerCase()))
|
|
15
|
+
throw new UsageError("archive.same-name", { name });
|
|
16
|
+
topNames.add(name.toLowerCase());
|
|
17
|
+
const stat = await fileSystem.lstat(source);
|
|
18
|
+
if (stat === null)
|
|
19
|
+
continue;
|
|
20
|
+
if (stat.kind === "symlink") {
|
|
21
|
+
skippedLinks += 1;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (stat.kind !== "directory") {
|
|
25
|
+
entries.push({ name, path: source, size: stat.size, modifiedMs: stat.modifiedMs, mode: stat.mode });
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
// A folder keeps its own name and everything inside it, hidden files included.
|
|
29
|
+
entries.push({ name: `${name}/`, path: null, size: 0, modifiedMs: stat.modifiedMs, mode: stat.mode });
|
|
30
|
+
const walkOptions = options.lean
|
|
31
|
+
? { all: true, skipDirectories: DEPENDENCY_DIRECTORIES }
|
|
32
|
+
: { all: true };
|
|
33
|
+
for await (const entry of walk(fileSystem, source, walkOptions)) {
|
|
34
|
+
if (entry.kind === "symlink") {
|
|
35
|
+
skippedLinks += 1;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (entry.kind !== "directory" && entry.kind !== "file")
|
|
39
|
+
continue;
|
|
40
|
+
const entryStat = await fileSystem.lstat(entry.path);
|
|
41
|
+
if (entryStat === null)
|
|
42
|
+
continue;
|
|
43
|
+
const isFolder = entry.kind === "directory";
|
|
44
|
+
entries.push({
|
|
45
|
+
name: `${name}/${entry.rel}${isFolder ? "/" : ""}`,
|
|
46
|
+
path: isFolder ? null : entry.path,
|
|
47
|
+
size: isFolder ? 0 : entryStat.size,
|
|
48
|
+
modifiedMs: entryStat.modifiedMs,
|
|
49
|
+
mode: entryStat.mode,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return { entries, skippedLinks };
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=archive-sources.js.map
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { done } from "../../../core/domain/command.js";
|
|
3
|
+
import { ConflictError, InterruptedError, UsageError } from "../../../core/domain/errors.js";
|
|
4
|
+
import { RawReader } from "../../../core/domain/input-schema.js";
|
|
5
|
+
import { TAR_END, tarCompression, tarHeader, tarPadding } from "../domain/tar-format.js";
|
|
6
|
+
import { collectSources } from "./archive-sources.js";
|
|
7
|
+
/** Files are read in pieces this size, so no file has to fit in memory. */
|
|
8
|
+
const CHUNK_BYTES = 1024 * 1024;
|
|
9
|
+
export const tarSpec = {
|
|
10
|
+
id: "archive.tar",
|
|
11
|
+
summary: "archive.tar.summary",
|
|
12
|
+
examples: [
|
|
13
|
+
"kiriya archive tar my-project --to my-project.tar.gz --lean",
|
|
14
|
+
'kiriya archive tar "logs/*.log" --to logs.tgz',
|
|
15
|
+
"kiriya archive tar dist --to dist.tar",
|
|
16
|
+
],
|
|
17
|
+
safety: "write",
|
|
18
|
+
idempotent: false,
|
|
19
|
+
usesNetwork: false,
|
|
20
|
+
runsUserCommands: false,
|
|
21
|
+
input: {
|
|
22
|
+
positionals: [{ name: "paths", description: "archive.tar.arg.paths", required: true, variadic: true, path: true }],
|
|
23
|
+
options: {
|
|
24
|
+
to: { type: "string", description: "archive.tar.option.to", valueName: "<file.tar.gz>", path: true },
|
|
25
|
+
lean: { type: "boolean", description: "archive.tar.option.lean" },
|
|
26
|
+
all: { type: "boolean", description: "archive.tar.option.all" },
|
|
27
|
+
},
|
|
28
|
+
parse(raw) {
|
|
29
|
+
const reader = new RawReader(raw);
|
|
30
|
+
const to = reader.string("to");
|
|
31
|
+
if (to === undefined)
|
|
32
|
+
throw new UsageError("archive.tar.to-required");
|
|
33
|
+
return { paths: reader.positionalsFrom(0), to, lean: reader.flag("lean"), all: reader.flag("all") };
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
/** The archive file, written through gzip when there is one. */
|
|
38
|
+
class ArchiveSink {
|
|
39
|
+
writer;
|
|
40
|
+
gzip;
|
|
41
|
+
bytesOut = 0;
|
|
42
|
+
constructor(writer, gzip) {
|
|
43
|
+
this.writer = writer;
|
|
44
|
+
this.gzip = gzip;
|
|
45
|
+
}
|
|
46
|
+
async write(bytes) {
|
|
47
|
+
if (bytes.length === 0)
|
|
48
|
+
return;
|
|
49
|
+
await this.emit(this.gzip === null ? bytes : await this.gzip.push(bytes));
|
|
50
|
+
}
|
|
51
|
+
async finish() {
|
|
52
|
+
if (this.gzip !== null)
|
|
53
|
+
await this.emit(await this.gzip.end());
|
|
54
|
+
await this.writer.close();
|
|
55
|
+
}
|
|
56
|
+
async emit(bytes) {
|
|
57
|
+
if (bytes.length === 0)
|
|
58
|
+
return;
|
|
59
|
+
await this.writer.write(bytes);
|
|
60
|
+
this.bytesOut += bytes.length;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export class CreateTar {
|
|
64
|
+
fileSystem;
|
|
65
|
+
content;
|
|
66
|
+
compression;
|
|
67
|
+
spec = tarSpec;
|
|
68
|
+
constructor(fileSystem, content, compression) {
|
|
69
|
+
this.fileSystem = fileSystem;
|
|
70
|
+
this.content = content;
|
|
71
|
+
this.compression = compression;
|
|
72
|
+
}
|
|
73
|
+
async execute(input, context) {
|
|
74
|
+
const archive = path.resolve(context.cwd, input.to);
|
|
75
|
+
const compressed = tarCompression(archive);
|
|
76
|
+
if (compressed === null)
|
|
77
|
+
throw new UsageError("archive.tar.extension", { path: input.to });
|
|
78
|
+
if ((await this.fileSystem.lstat(archive)) !== null)
|
|
79
|
+
throw new ConflictError("core.fs.exists", { path: archive });
|
|
80
|
+
const { entries, skippedLinks } = await collectSources(this.fileSystem, input.paths, context.cwd, input);
|
|
81
|
+
const writer = await this.content.createExclusive(archive);
|
|
82
|
+
const sink = new ArchiveSink(writer, compressed ? this.compression.gzip() : null);
|
|
83
|
+
let bytesIn = 0;
|
|
84
|
+
try {
|
|
85
|
+
for (const entry of entries) {
|
|
86
|
+
if (context.signal.aborted)
|
|
87
|
+
throw new InterruptedError("core.error.interrupted");
|
|
88
|
+
bytesIn += await this.add(sink, entry);
|
|
89
|
+
}
|
|
90
|
+
await sink.write(TAR_END);
|
|
91
|
+
await sink.finish();
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
// A failed archive leaves nothing behind.
|
|
95
|
+
await writer.discard();
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
return done({ archive, entries: entries.length, bytesIn, bytesOut: sink.bytesOut, skippedLinks, compressed });
|
|
99
|
+
}
|
|
100
|
+
/** One entry's header and content. The size is taken when the file opens, in case it changed since it was listed. */
|
|
101
|
+
async add(sink, entry) {
|
|
102
|
+
if (entry.path === null) {
|
|
103
|
+
await sink.write(tarHeader({ name: entry.name, isDirectory: true, size: 0, mode: 0o755, modifiedMs: entry.modifiedMs }));
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
const reader = await this.content.openForReading(entry.path);
|
|
107
|
+
try {
|
|
108
|
+
// Executable files stay executable; everything else is readable by all and writable by its owner.
|
|
109
|
+
const mode = (entry.mode & 0o111) !== 0 ? 0o755 : 0o644;
|
|
110
|
+
const size = reader.size;
|
|
111
|
+
await sink.write(tarHeader({ name: entry.name, isDirectory: false, size, mode, modifiedMs: entry.modifiedMs }));
|
|
112
|
+
for (let position = 0; position < size; position += CHUNK_BYTES) {
|
|
113
|
+
await sink.write(await reader.readAt(position, Math.min(CHUNK_BYTES, size - position)));
|
|
114
|
+
}
|
|
115
|
+
await sink.write(new Uint8Array(tarPadding(size)));
|
|
116
|
+
return size;
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
await reader.close();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=create-tar.use-case.js.map
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { done } from "../../../core/domain/command.js";
|
|
3
|
+
import { ConflictError, InterruptedError, OperationFailedError, UsageError } from "../../../core/domain/errors.js";
|
|
4
|
+
import { RawReader } from "../../../core/domain/input-schema.js";
|
|
5
|
+
import { crc32 } from "../domain/crc32.js";
|
|
6
|
+
import { centralHeader, DEFLATED, endRecord, localHeader, MAX_ENTRIES, STORED, ZIP32_LIMIT, } from "../domain/zip-format.js";
|
|
7
|
+
import { collectSources } from "./archive-sources.js";
|
|
8
|
+
export const zipSpec = {
|
|
9
|
+
id: "archive.zip",
|
|
10
|
+
summary: "archive.zip.summary",
|
|
11
|
+
examples: [
|
|
12
|
+
"kiriya archive zip my-project --to my-project.zip --lean",
|
|
13
|
+
'kiriya archive zip "reports/*.pdf" --to reports.zip',
|
|
14
|
+
"kiriya archive zip src docs README.md --to handover.zip",
|
|
15
|
+
],
|
|
16
|
+
safety: "write",
|
|
17
|
+
idempotent: false,
|
|
18
|
+
usesNetwork: false,
|
|
19
|
+
runsUserCommands: false,
|
|
20
|
+
input: {
|
|
21
|
+
positionals: [{ name: "paths", description: "archive.zip.arg.paths", required: true, variadic: true, path: true }],
|
|
22
|
+
options: {
|
|
23
|
+
to: { type: "string", description: "archive.zip.option.to", valueName: "<file.zip>", path: true },
|
|
24
|
+
lean: { type: "boolean", description: "archive.zip.option.lean" },
|
|
25
|
+
all: { type: "boolean", description: "archive.zip.option.all" },
|
|
26
|
+
},
|
|
27
|
+
parse(raw) {
|
|
28
|
+
const reader = new RawReader(raw);
|
|
29
|
+
const to = reader.string("to");
|
|
30
|
+
if (to === undefined)
|
|
31
|
+
throw new UsageError("archive.zip.to-required");
|
|
32
|
+
return { paths: reader.positionalsFrom(0), to, lean: reader.flag("lean"), all: reader.flag("all") };
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
export class CreateZip {
|
|
37
|
+
fileSystem;
|
|
38
|
+
content;
|
|
39
|
+
compression;
|
|
40
|
+
spec = zipSpec;
|
|
41
|
+
constructor(fileSystem, content, compression) {
|
|
42
|
+
this.fileSystem = fileSystem;
|
|
43
|
+
this.content = content;
|
|
44
|
+
this.compression = compression;
|
|
45
|
+
}
|
|
46
|
+
async execute(input, context) {
|
|
47
|
+
const archive = path.resolve(context.cwd, input.to);
|
|
48
|
+
if ((await this.fileSystem.lstat(archive)) !== null)
|
|
49
|
+
throw new ConflictError("core.fs.exists", { path: archive });
|
|
50
|
+
const { entries, skippedLinks } = await collectSources(this.fileSystem, input.paths, context.cwd, input);
|
|
51
|
+
if (entries.length >= MAX_ENTRIES) {
|
|
52
|
+
throw new OperationFailedError("archive.zip.too-many-entries", { count: entries.length });
|
|
53
|
+
}
|
|
54
|
+
const writer = await this.content.createExclusive(archive);
|
|
55
|
+
let offset = 0;
|
|
56
|
+
let bytesIn = 0;
|
|
57
|
+
const write = async (bytes) => {
|
|
58
|
+
await writer.write(bytes);
|
|
59
|
+
offset += bytes.length;
|
|
60
|
+
};
|
|
61
|
+
try {
|
|
62
|
+
const central = [];
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
if (context.signal.aborted)
|
|
65
|
+
throw new InterruptedError("core.error.interrupted");
|
|
66
|
+
if (entry.size >= ZIP32_LIMIT)
|
|
67
|
+
throw new OperationFailedError("archive.zip.entry-too-large", { name: entry.name });
|
|
68
|
+
if (offset >= ZIP32_LIMIT)
|
|
69
|
+
throw new OperationFailedError("archive.zip.archive-too-large", { path: archive });
|
|
70
|
+
const data = entry.path === null ? new Uint8Array(0) : await this.content.read(entry.path);
|
|
71
|
+
if (data.length >= ZIP32_LIMIT)
|
|
72
|
+
throw new OperationFailedError("archive.zip.entry-too-large", { name: entry.name });
|
|
73
|
+
const deflated = entry.path === null ? data : await this.compression.deflateRaw(data);
|
|
74
|
+
const method = entry.path !== null && deflated.length < data.length ? DEFLATED : STORED;
|
|
75
|
+
const stored = method === DEFLATED ? deflated : data;
|
|
76
|
+
const header = {
|
|
77
|
+
name: entry.name,
|
|
78
|
+
isDirectory: entry.path === null,
|
|
79
|
+
method,
|
|
80
|
+
crc: crc32(data),
|
|
81
|
+
compressedSize: stored.length,
|
|
82
|
+
size: data.length,
|
|
83
|
+
modifiedMs: entry.modifiedMs,
|
|
84
|
+
};
|
|
85
|
+
central.push(centralHeader(header, offset));
|
|
86
|
+
await write(localHeader(header));
|
|
87
|
+
await write(stored);
|
|
88
|
+
bytesIn += data.length;
|
|
89
|
+
}
|
|
90
|
+
const directoryOffset = offset;
|
|
91
|
+
for (const bytes of central)
|
|
92
|
+
await write(bytes);
|
|
93
|
+
if (offset >= ZIP32_LIMIT)
|
|
94
|
+
throw new OperationFailedError("archive.zip.archive-too-large", { path: archive });
|
|
95
|
+
await write(endRecord({ count: entries.length, size: offset - directoryOffset, offset: directoryOffset }));
|
|
96
|
+
await writer.close();
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
// A failed archive leaves nothing behind.
|
|
100
|
+
await writer.discard();
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
return done({ archive, entries: entries.length, bytesIn, bytesOut: offset, skippedLinks });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=create-zip.use-case.js.map
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { isInside } from "../../../core/application/paths.js";
|
|
3
|
+
import { requireTypedConfirmation } from "../../../core/application/safety.js";
|
|
4
|
+
import { done } from "../../../core/domain/command.js";
|
|
5
|
+
import { ConflictError, KiriyaError, NotFoundError, OperationFailedError } from "../../../core/domain/errors.js";
|
|
6
|
+
import { RawReader } from "../../../core/domain/input-schema.js";
|
|
7
|
+
import { message } from "../../../core/domain/message.js";
|
|
8
|
+
import { invalidNameReason } from "../../../core/domain/names.js";
|
|
9
|
+
import { safeEntrySegments } from "../domain/zip-format.js";
|
|
10
|
+
import { readTar } from "./tar-reader.js";
|
|
11
|
+
/** Names listed per kind of problem before the rest are counted. */
|
|
12
|
+
const REPORT_LIMIT = 20;
|
|
13
|
+
export const untarSpec = {
|
|
14
|
+
id: "archive.untar",
|
|
15
|
+
summary: "archive.untar.summary",
|
|
16
|
+
examples: [
|
|
17
|
+
"kiriya archive untar release.tar.gz",
|
|
18
|
+
"kiriya archive untar release.tgz --list",
|
|
19
|
+
"kiriya archive untar node.tar.gz --to vendor/node --overwrite",
|
|
20
|
+
],
|
|
21
|
+
safety: "destroy",
|
|
22
|
+
idempotent: false,
|
|
23
|
+
usesNetwork: false,
|
|
24
|
+
runsUserCommands: false,
|
|
25
|
+
input: {
|
|
26
|
+
positionals: [
|
|
27
|
+
{ name: "archive", description: "archive.untar.arg.archive", required: true, variadic: false, path: true },
|
|
28
|
+
],
|
|
29
|
+
options: {
|
|
30
|
+
to: { type: "string", description: "archive.untar.option.to", valueName: "<folder>", path: true },
|
|
31
|
+
list: { type: "boolean", description: "archive.untar.option.list" },
|
|
32
|
+
overwrite: { type: "boolean", description: "archive.untar.option.overwrite" },
|
|
33
|
+
confirm: {
|
|
34
|
+
type: "string",
|
|
35
|
+
description: "archive.untar.option.confirm",
|
|
36
|
+
valueName: "<count>",
|
|
37
|
+
terminalOnly: true,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
parse(raw) {
|
|
41
|
+
const reader = new RawReader(raw);
|
|
42
|
+
return {
|
|
43
|
+
archive: reader.positional(0) ?? "",
|
|
44
|
+
to: reader.string("to"),
|
|
45
|
+
list: reader.flag("list"),
|
|
46
|
+
overwrite: reader.flag("overwrite"),
|
|
47
|
+
confirm: reader.string("confirm"),
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
/** The default folder: the archive's name without .tar.gz, .tgz or .tar, next to it. */
|
|
53
|
+
export function defaultTarget(archive) {
|
|
54
|
+
const name = path.basename(archive);
|
|
55
|
+
let base = name.replace(/(\.tar\.gz|\.tgz|\.tar)$/i, "");
|
|
56
|
+
if (base === name)
|
|
57
|
+
base = path.basename(name, path.extname(name));
|
|
58
|
+
if (base === name || base === "")
|
|
59
|
+
base = `${name}-extracted`;
|
|
60
|
+
return path.join(path.dirname(archive), base);
|
|
61
|
+
}
|
|
62
|
+
export class ExtractTar {
|
|
63
|
+
fileSystem;
|
|
64
|
+
content;
|
|
65
|
+
compression;
|
|
66
|
+
spec = untarSpec;
|
|
67
|
+
constructor(fileSystem, content, compression) {
|
|
68
|
+
this.fileSystem = fileSystem;
|
|
69
|
+
this.content = content;
|
|
70
|
+
this.compression = compression;
|
|
71
|
+
}
|
|
72
|
+
async execute(input, context) {
|
|
73
|
+
const archive = path.resolve(context.cwd, input.archive);
|
|
74
|
+
if ((await this.fileSystem.lstat(archive)) === null) {
|
|
75
|
+
throw new NotFoundError("core.fs.not-found", { path: archive });
|
|
76
|
+
}
|
|
77
|
+
// A first pass reads only the headers, so nothing is written before every name has been checked.
|
|
78
|
+
const entries = [];
|
|
79
|
+
await readTar(this.content, this.compression, archive, context.signal, (event) => {
|
|
80
|
+
if (event.kind === "entry")
|
|
81
|
+
entries.push(event.entry);
|
|
82
|
+
return Promise.resolve();
|
|
83
|
+
});
|
|
84
|
+
if (input.list) {
|
|
85
|
+
const listed = entries.map(({ name, type, size, modifiedMs }) => ({ name, type, size, modifiedMs }));
|
|
86
|
+
const bytes = entries.reduce((sum, entry) => sum + (entry.type === "file" ? entry.size : 0), 0);
|
|
87
|
+
return done({ mode: "list", archive, entries: listed, bytes });
|
|
88
|
+
}
|
|
89
|
+
return this.extract(entries, archive, input, context);
|
|
90
|
+
}
|
|
91
|
+
async extract(entries, archive, input, context) {
|
|
92
|
+
const target = path.resolve(context.cwd, input.to ?? defaultTarget(archive));
|
|
93
|
+
const plan = new Map();
|
|
94
|
+
const unsafe = [];
|
|
95
|
+
const skipped = [];
|
|
96
|
+
const failures = [];
|
|
97
|
+
let skippedLinks = 0;
|
|
98
|
+
entries.forEach((entry, index) => {
|
|
99
|
+
// A link can point anywhere, so extracting one could let later entries escape the target.
|
|
100
|
+
if (entry.type === "link" || entry.type === "other") {
|
|
101
|
+
skippedLinks += 1;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const segments = safeEntrySegments(entry.name);
|
|
105
|
+
if (segments === null) {
|
|
106
|
+
unsafe.push(entry.name);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (segments.length === 0)
|
|
110
|
+
return;
|
|
111
|
+
const invalid = segments
|
|
112
|
+
.map((segment) => ({ segment, reason: invalidNameReason(segment) }))
|
|
113
|
+
.find((item) => item.reason !== null);
|
|
114
|
+
if (invalid !== undefined && invalid.reason !== null) {
|
|
115
|
+
skipped.push(entry.name);
|
|
116
|
+
const reason = message(invalid.reason, { name: invalid.segment });
|
|
117
|
+
failures.push(message("archive.unzip.invalid-name", { name: entry.name, reason }));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const destination = path.join(target, ...segments);
|
|
121
|
+
if (isInside(destination, target))
|
|
122
|
+
plan.set(index, destination);
|
|
123
|
+
else
|
|
124
|
+
unsafe.push(entry.name);
|
|
125
|
+
});
|
|
126
|
+
const result = {
|
|
127
|
+
mode: "extract",
|
|
128
|
+
archive,
|
|
129
|
+
target,
|
|
130
|
+
extracted: false,
|
|
131
|
+
files: 0,
|
|
132
|
+
bytes: 0,
|
|
133
|
+
unsafe,
|
|
134
|
+
skipped,
|
|
135
|
+
conflicts: [],
|
|
136
|
+
skippedLinks,
|
|
137
|
+
};
|
|
138
|
+
if (unsafe.length > 0) {
|
|
139
|
+
const names = unsafe.slice(0, REPORT_LIMIT).map((name) => message("archive.unzip.unsafe-name", { name }));
|
|
140
|
+
return done(result, { failures: [...names, message("archive.unzip.unsafe-archive")] });
|
|
141
|
+
}
|
|
142
|
+
const conflicts = [];
|
|
143
|
+
for (const [index, destination] of plan) {
|
|
144
|
+
if (entries[index]?.type === "file" && (await this.fileSystem.lstat(destination)) !== null) {
|
|
145
|
+
conflicts.push(destination);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (conflicts.length > 0) {
|
|
149
|
+
if (!input.overwrite) {
|
|
150
|
+
const existing = conflicts
|
|
151
|
+
.slice(0, REPORT_LIMIT)
|
|
152
|
+
.map((conflict) => message("core.fs.exists", { path: conflict }));
|
|
153
|
+
const more = conflicts.length > REPORT_LIMIT
|
|
154
|
+
? [message("core.list.more", { count: conflicts.length - REPORT_LIMIT })]
|
|
155
|
+
: [];
|
|
156
|
+
return done({ ...result, conflicts }, { failures: [...failures, ...existing], warnings: [...more, message("archive.unzip.nothing-extracted")] });
|
|
157
|
+
}
|
|
158
|
+
const warning = message("archive.unzip.warn-overwrite", { count: conflicts.length, target });
|
|
159
|
+
await requireTypedConfirmation(context, warning, String(conflicts.length), input.confirm);
|
|
160
|
+
}
|
|
161
|
+
const written = await this.write(entries, plan, archive, failures, context.signal);
|
|
162
|
+
return done({ ...result, conflicts, extracted: true, ...written }, { failures });
|
|
163
|
+
}
|
|
164
|
+
/** The second pass: the same entries again, now with their data written out. */
|
|
165
|
+
async write(entries, plan, archive, failures, signal) {
|
|
166
|
+
const progress = {
|
|
167
|
+
index: -1,
|
|
168
|
+
open: null,
|
|
169
|
+
files: 0,
|
|
170
|
+
bytes: 0,
|
|
171
|
+
};
|
|
172
|
+
const fail = async (current, error) => {
|
|
173
|
+
if (!(error instanceof KiriyaError))
|
|
174
|
+
throw error;
|
|
175
|
+
failures.push(message("archive.unzip.entry-failed", { name: current.entry.name, reason: error.detail }));
|
|
176
|
+
await current.writer?.discard();
|
|
177
|
+
current.writer = null;
|
|
178
|
+
};
|
|
179
|
+
try {
|
|
180
|
+
await readTar(this.content, this.compression, archive, signal, async (event) => {
|
|
181
|
+
if (event.kind === "entry") {
|
|
182
|
+
progress.index += 1;
|
|
183
|
+
const expected = entries[progress.index];
|
|
184
|
+
if (expected?.name !== event.entry.name || expected.size !== event.entry.size) {
|
|
185
|
+
throw new OperationFailedError("archive.untar.changed", { path: archive });
|
|
186
|
+
}
|
|
187
|
+
const destination = plan.get(progress.index);
|
|
188
|
+
const next = destination === undefined ? null : { entry: event.entry, destination, writer: null };
|
|
189
|
+
progress.open = next;
|
|
190
|
+
if (next !== null)
|
|
191
|
+
await this.begin(next).catch((error) => fail(next, error));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const current = progress.open;
|
|
195
|
+
// A folder, an entry that is not extracted, or a file whose writing already failed.
|
|
196
|
+
if (current?.writer === null || current?.writer === undefined)
|
|
197
|
+
return;
|
|
198
|
+
const writer = current.writer;
|
|
199
|
+
if (event.kind === "data") {
|
|
200
|
+
await writer.write(event.bytes).catch((error) => fail(current, error));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
progress.open = null;
|
|
204
|
+
try {
|
|
205
|
+
await writer.close();
|
|
206
|
+
current.writer = null;
|
|
207
|
+
await this.fileSystem.setTimes(current.destination, current.entry.modifiedMs, current.entry.modifiedMs);
|
|
208
|
+
// The owner keeps read and write access, whatever the archive says.
|
|
209
|
+
await this.fileSystem.setMode(current.destination, (current.entry.mode & 0o777) | 0o600);
|
|
210
|
+
progress.files += 1;
|
|
211
|
+
progress.bytes += current.entry.size;
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
await fail(current, error);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
await progress.open?.writer?.discard();
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
return { files: progress.files, bytes: progress.bytes };
|
|
223
|
+
}
|
|
224
|
+
/** Makes the folder, or opens the file for writing, replacing a file already there. */
|
|
225
|
+
async begin(open) {
|
|
226
|
+
if (open.entry.type === "directory") {
|
|
227
|
+
await this.fileSystem.createDirectory(open.destination);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const existing = await this.fileSystem.lstat(open.destination);
|
|
231
|
+
if (existing?.kind === "directory") {
|
|
232
|
+
throw new ConflictError("archive.unzip.folder-in-the-way", { name: open.entry.name });
|
|
233
|
+
}
|
|
234
|
+
await this.fileSystem.createDirectory(path.dirname(open.destination));
|
|
235
|
+
if (existing !== null)
|
|
236
|
+
await this.fileSystem.remove(open.destination);
|
|
237
|
+
open.writer = await this.content.createExclusive(open.destination);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=extract-tar.use-case.js.map
|