devvami 1.3.0 → 1.4.1
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/oclif.manifest.json +397 -1
- package/package.json +1 -1
- package/src/commands/dotfiles/add.js +249 -0
- package/src/commands/dotfiles/setup.js +190 -0
- package/src/commands/dotfiles/status.js +103 -0
- package/src/commands/dotfiles/sync.js +375 -0
- package/src/commands/init.js +35 -2
- package/src/commands/vuln/detail.js +65 -0
- package/src/commands/vuln/scan.js +155 -0
- package/src/commands/vuln/search.js +128 -0
- package/src/formatters/dotfiles.js +259 -0
- package/src/formatters/vuln.js +317 -0
- package/src/help.js +62 -2
- package/src/services/audit-detector.js +120 -0
- package/src/services/audit-runner.js +365 -0
- package/src/services/dotfiles.js +573 -0
- package/src/services/nvd.js +245 -0
- package/src/types.js +73 -5
- package/src/utils/errors.js +2 -0
- package/src/utils/tui/modal.js +224 -0
- package/src/utils/tui/navigable-table.js +496 -0
package/oclif.manifest.json
CHANGED
|
@@ -813,6 +813,216 @@
|
|
|
813
813
|
"search.js"
|
|
814
814
|
]
|
|
815
815
|
},
|
|
816
|
+
"dotfiles:add": {
|
|
817
|
+
"aliases": [],
|
|
818
|
+
"args": {
|
|
819
|
+
"files": {
|
|
820
|
+
"description": "File paths to add",
|
|
821
|
+
"name": "files",
|
|
822
|
+
"required": false
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
"description": "Add dotfiles to chezmoi management with automatic encryption for sensitive files",
|
|
826
|
+
"examples": [
|
|
827
|
+
"<%= config.bin %> dotfiles add",
|
|
828
|
+
"<%= config.bin %> dotfiles add ~/.zshrc",
|
|
829
|
+
"<%= config.bin %> dotfiles add ~/.zshrc ~/.gitconfig",
|
|
830
|
+
"<%= config.bin %> dotfiles add ~/.ssh/id_ed25519 --encrypt",
|
|
831
|
+
"<%= config.bin %> dotfiles add --json ~/.zshrc"
|
|
832
|
+
],
|
|
833
|
+
"flags": {
|
|
834
|
+
"json": {
|
|
835
|
+
"description": "Format output as json.",
|
|
836
|
+
"helpGroup": "GLOBAL",
|
|
837
|
+
"name": "json",
|
|
838
|
+
"allowNo": false,
|
|
839
|
+
"type": "boolean"
|
|
840
|
+
},
|
|
841
|
+
"help": {
|
|
842
|
+
"char": "h",
|
|
843
|
+
"description": "Show CLI help.",
|
|
844
|
+
"name": "help",
|
|
845
|
+
"allowNo": false,
|
|
846
|
+
"type": "boolean"
|
|
847
|
+
},
|
|
848
|
+
"encrypt": {
|
|
849
|
+
"char": "e",
|
|
850
|
+
"description": "Force encryption for all files being added",
|
|
851
|
+
"name": "encrypt",
|
|
852
|
+
"allowNo": false,
|
|
853
|
+
"type": "boolean"
|
|
854
|
+
},
|
|
855
|
+
"no-encrypt": {
|
|
856
|
+
"description": "Disable auto-encryption (add all as plaintext)",
|
|
857
|
+
"name": "no-encrypt",
|
|
858
|
+
"allowNo": false,
|
|
859
|
+
"type": "boolean"
|
|
860
|
+
}
|
|
861
|
+
},
|
|
862
|
+
"hasDynamicHelp": false,
|
|
863
|
+
"hiddenAliases": [],
|
|
864
|
+
"id": "dotfiles:add",
|
|
865
|
+
"pluginAlias": "devvami",
|
|
866
|
+
"pluginName": "devvami",
|
|
867
|
+
"pluginType": "core",
|
|
868
|
+
"strict": false,
|
|
869
|
+
"enableJsonFlag": true,
|
|
870
|
+
"isESM": true,
|
|
871
|
+
"relativePath": [
|
|
872
|
+
"src",
|
|
873
|
+
"commands",
|
|
874
|
+
"dotfiles",
|
|
875
|
+
"add.js"
|
|
876
|
+
]
|
|
877
|
+
},
|
|
878
|
+
"dotfiles:setup": {
|
|
879
|
+
"aliases": [],
|
|
880
|
+
"args": {},
|
|
881
|
+
"description": "Interactive wizard to configure chezmoi with age encryption for dotfile management",
|
|
882
|
+
"examples": [
|
|
883
|
+
"<%= config.bin %> dotfiles setup",
|
|
884
|
+
"<%= config.bin %> dotfiles setup --json"
|
|
885
|
+
],
|
|
886
|
+
"flags": {
|
|
887
|
+
"json": {
|
|
888
|
+
"description": "Format output as json.",
|
|
889
|
+
"helpGroup": "GLOBAL",
|
|
890
|
+
"name": "json",
|
|
891
|
+
"allowNo": false,
|
|
892
|
+
"type": "boolean"
|
|
893
|
+
},
|
|
894
|
+
"help": {
|
|
895
|
+
"char": "h",
|
|
896
|
+
"description": "Show CLI help.",
|
|
897
|
+
"name": "help",
|
|
898
|
+
"allowNo": false,
|
|
899
|
+
"type": "boolean"
|
|
900
|
+
}
|
|
901
|
+
},
|
|
902
|
+
"hasDynamicHelp": false,
|
|
903
|
+
"hiddenAliases": [],
|
|
904
|
+
"id": "dotfiles:setup",
|
|
905
|
+
"pluginAlias": "devvami",
|
|
906
|
+
"pluginName": "devvami",
|
|
907
|
+
"pluginType": "core",
|
|
908
|
+
"strict": true,
|
|
909
|
+
"enableJsonFlag": true,
|
|
910
|
+
"isESM": true,
|
|
911
|
+
"relativePath": [
|
|
912
|
+
"src",
|
|
913
|
+
"commands",
|
|
914
|
+
"dotfiles",
|
|
915
|
+
"setup.js"
|
|
916
|
+
]
|
|
917
|
+
},
|
|
918
|
+
"dotfiles:status": {
|
|
919
|
+
"aliases": [],
|
|
920
|
+
"args": {},
|
|
921
|
+
"description": "Show chezmoi dotfiles status: managed files, encryption state, and sync health",
|
|
922
|
+
"examples": [
|
|
923
|
+
"<%= config.bin %> dotfiles status",
|
|
924
|
+
"<%= config.bin %> dotfiles status --json"
|
|
925
|
+
],
|
|
926
|
+
"flags": {
|
|
927
|
+
"json": {
|
|
928
|
+
"description": "Format output as json.",
|
|
929
|
+
"helpGroup": "GLOBAL",
|
|
930
|
+
"name": "json",
|
|
931
|
+
"allowNo": false,
|
|
932
|
+
"type": "boolean"
|
|
933
|
+
},
|
|
934
|
+
"help": {
|
|
935
|
+
"char": "h",
|
|
936
|
+
"description": "Show CLI help.",
|
|
937
|
+
"name": "help",
|
|
938
|
+
"allowNo": false,
|
|
939
|
+
"type": "boolean"
|
|
940
|
+
}
|
|
941
|
+
},
|
|
942
|
+
"hasDynamicHelp": false,
|
|
943
|
+
"hiddenAliases": [],
|
|
944
|
+
"id": "dotfiles:status",
|
|
945
|
+
"pluginAlias": "devvami",
|
|
946
|
+
"pluginName": "devvami",
|
|
947
|
+
"pluginType": "core",
|
|
948
|
+
"strict": true,
|
|
949
|
+
"enableJsonFlag": true,
|
|
950
|
+
"isESM": true,
|
|
951
|
+
"relativePath": [
|
|
952
|
+
"src",
|
|
953
|
+
"commands",
|
|
954
|
+
"dotfiles",
|
|
955
|
+
"status.js"
|
|
956
|
+
]
|
|
957
|
+
},
|
|
958
|
+
"dotfiles:sync": {
|
|
959
|
+
"aliases": [],
|
|
960
|
+
"args": {
|
|
961
|
+
"repo": {
|
|
962
|
+
"description": "Remote repository URL (for initial remote setup)",
|
|
963
|
+
"name": "repo",
|
|
964
|
+
"required": false
|
|
965
|
+
}
|
|
966
|
+
},
|
|
967
|
+
"description": "Sync dotfiles with remote repository: push local changes or pull from remote",
|
|
968
|
+
"examples": [
|
|
969
|
+
"<%= config.bin %> dotfiles sync",
|
|
970
|
+
"<%= config.bin %> dotfiles sync --push",
|
|
971
|
+
"<%= config.bin %> dotfiles sync --pull",
|
|
972
|
+
"<%= config.bin %> dotfiles sync --pull git@github.com:user/dotfiles.git",
|
|
973
|
+
"<%= config.bin %> dotfiles sync --dry-run --push",
|
|
974
|
+
"<%= config.bin %> dotfiles sync --json"
|
|
975
|
+
],
|
|
976
|
+
"flags": {
|
|
977
|
+
"json": {
|
|
978
|
+
"description": "Format output as json.",
|
|
979
|
+
"helpGroup": "GLOBAL",
|
|
980
|
+
"name": "json",
|
|
981
|
+
"allowNo": false,
|
|
982
|
+
"type": "boolean"
|
|
983
|
+
},
|
|
984
|
+
"help": {
|
|
985
|
+
"char": "h",
|
|
986
|
+
"description": "Show CLI help.",
|
|
987
|
+
"name": "help",
|
|
988
|
+
"allowNo": false,
|
|
989
|
+
"type": "boolean"
|
|
990
|
+
},
|
|
991
|
+
"push": {
|
|
992
|
+
"description": "Push local changes to remote",
|
|
993
|
+
"name": "push",
|
|
994
|
+
"allowNo": false,
|
|
995
|
+
"type": "boolean"
|
|
996
|
+
},
|
|
997
|
+
"pull": {
|
|
998
|
+
"description": "Pull remote changes and apply",
|
|
999
|
+
"name": "pull",
|
|
1000
|
+
"allowNo": false,
|
|
1001
|
+
"type": "boolean"
|
|
1002
|
+
},
|
|
1003
|
+
"dry-run": {
|
|
1004
|
+
"description": "Show what would change without applying",
|
|
1005
|
+
"name": "dry-run",
|
|
1006
|
+
"allowNo": false,
|
|
1007
|
+
"type": "boolean"
|
|
1008
|
+
}
|
|
1009
|
+
},
|
|
1010
|
+
"hasDynamicHelp": false,
|
|
1011
|
+
"hiddenAliases": [],
|
|
1012
|
+
"id": "dotfiles:sync",
|
|
1013
|
+
"pluginAlias": "devvami",
|
|
1014
|
+
"pluginName": "devvami",
|
|
1015
|
+
"pluginType": "core",
|
|
1016
|
+
"strict": true,
|
|
1017
|
+
"enableJsonFlag": true,
|
|
1018
|
+
"isESM": true,
|
|
1019
|
+
"relativePath": [
|
|
1020
|
+
"src",
|
|
1021
|
+
"commands",
|
|
1022
|
+
"dotfiles",
|
|
1023
|
+
"sync.js"
|
|
1024
|
+
]
|
|
1025
|
+
},
|
|
816
1026
|
"logs": {
|
|
817
1027
|
"aliases": [],
|
|
818
1028
|
"args": {},
|
|
@@ -1722,7 +1932,193 @@
|
|
|
1722
1932
|
"tasks",
|
|
1723
1933
|
"today.js"
|
|
1724
1934
|
]
|
|
1935
|
+
},
|
|
1936
|
+
"vuln:detail": {
|
|
1937
|
+
"aliases": [],
|
|
1938
|
+
"args": {
|
|
1939
|
+
"cveId": {
|
|
1940
|
+
"description": "CVE identifier (e.g. CVE-2021-44228)",
|
|
1941
|
+
"name": "cveId",
|
|
1942
|
+
"required": true
|
|
1943
|
+
}
|
|
1944
|
+
},
|
|
1945
|
+
"description": "View full details for a specific CVE",
|
|
1946
|
+
"examples": [
|
|
1947
|
+
"<%= config.bin %> vuln detail CVE-2021-44228",
|
|
1948
|
+
"<%= config.bin %> vuln detail CVE-2021-44228 --open",
|
|
1949
|
+
"<%= config.bin %> vuln detail CVE-2021-44228 --json"
|
|
1950
|
+
],
|
|
1951
|
+
"flags": {
|
|
1952
|
+
"json": {
|
|
1953
|
+
"description": "Format output as json.",
|
|
1954
|
+
"helpGroup": "GLOBAL",
|
|
1955
|
+
"name": "json",
|
|
1956
|
+
"allowNo": false,
|
|
1957
|
+
"type": "boolean"
|
|
1958
|
+
},
|
|
1959
|
+
"open": {
|
|
1960
|
+
"char": "o",
|
|
1961
|
+
"description": "Open the first reference URL in the default browser",
|
|
1962
|
+
"name": "open",
|
|
1963
|
+
"allowNo": false,
|
|
1964
|
+
"type": "boolean"
|
|
1965
|
+
}
|
|
1966
|
+
},
|
|
1967
|
+
"hasDynamicHelp": false,
|
|
1968
|
+
"hiddenAliases": [],
|
|
1969
|
+
"id": "vuln:detail",
|
|
1970
|
+
"pluginAlias": "devvami",
|
|
1971
|
+
"pluginName": "devvami",
|
|
1972
|
+
"pluginType": "core",
|
|
1973
|
+
"strict": true,
|
|
1974
|
+
"enableJsonFlag": true,
|
|
1975
|
+
"isESM": true,
|
|
1976
|
+
"relativePath": [
|
|
1977
|
+
"src",
|
|
1978
|
+
"commands",
|
|
1979
|
+
"vuln",
|
|
1980
|
+
"detail.js"
|
|
1981
|
+
]
|
|
1982
|
+
},
|
|
1983
|
+
"vuln:scan": {
|
|
1984
|
+
"aliases": [],
|
|
1985
|
+
"args": {},
|
|
1986
|
+
"description": "Scan the current directory for known vulnerabilities in dependencies",
|
|
1987
|
+
"examples": [
|
|
1988
|
+
"<%= config.bin %> vuln scan",
|
|
1989
|
+
"<%= config.bin %> vuln scan --severity high",
|
|
1990
|
+
"<%= config.bin %> vuln scan --no-fail",
|
|
1991
|
+
"<%= config.bin %> vuln scan --report vuln-report.md",
|
|
1992
|
+
"<%= config.bin %> vuln scan --json"
|
|
1993
|
+
],
|
|
1994
|
+
"flags": {
|
|
1995
|
+
"json": {
|
|
1996
|
+
"description": "Format output as json.",
|
|
1997
|
+
"helpGroup": "GLOBAL",
|
|
1998
|
+
"name": "json",
|
|
1999
|
+
"allowNo": false,
|
|
2000
|
+
"type": "boolean"
|
|
2001
|
+
},
|
|
2002
|
+
"severity": {
|
|
2003
|
+
"char": "s",
|
|
2004
|
+
"description": "Minimum severity filter",
|
|
2005
|
+
"name": "severity",
|
|
2006
|
+
"hasDynamicHelp": false,
|
|
2007
|
+
"multiple": false,
|
|
2008
|
+
"options": [
|
|
2009
|
+
"low",
|
|
2010
|
+
"medium",
|
|
2011
|
+
"high",
|
|
2012
|
+
"critical"
|
|
2013
|
+
],
|
|
2014
|
+
"type": "option"
|
|
2015
|
+
},
|
|
2016
|
+
"no-fail": {
|
|
2017
|
+
"description": "Exit with code 0 even when vulnerabilities are found",
|
|
2018
|
+
"name": "no-fail",
|
|
2019
|
+
"allowNo": false,
|
|
2020
|
+
"type": "boolean"
|
|
2021
|
+
},
|
|
2022
|
+
"report": {
|
|
2023
|
+
"char": "r",
|
|
2024
|
+
"description": "Export vulnerability report to file path (Markdown format)",
|
|
2025
|
+
"name": "report",
|
|
2026
|
+
"hasDynamicHelp": false,
|
|
2027
|
+
"multiple": false,
|
|
2028
|
+
"type": "option"
|
|
2029
|
+
}
|
|
2030
|
+
},
|
|
2031
|
+
"hasDynamicHelp": false,
|
|
2032
|
+
"hiddenAliases": [],
|
|
2033
|
+
"id": "vuln:scan",
|
|
2034
|
+
"pluginAlias": "devvami",
|
|
2035
|
+
"pluginName": "devvami",
|
|
2036
|
+
"pluginType": "core",
|
|
2037
|
+
"strict": true,
|
|
2038
|
+
"enableJsonFlag": true,
|
|
2039
|
+
"isESM": true,
|
|
2040
|
+
"relativePath": [
|
|
2041
|
+
"src",
|
|
2042
|
+
"commands",
|
|
2043
|
+
"vuln",
|
|
2044
|
+
"scan.js"
|
|
2045
|
+
]
|
|
2046
|
+
},
|
|
2047
|
+
"vuln:search": {
|
|
2048
|
+
"aliases": [],
|
|
2049
|
+
"args": {
|
|
2050
|
+
"keyword": {
|
|
2051
|
+
"description": "Product, library, or keyword to search for (optional — omit to see all recent CVEs)",
|
|
2052
|
+
"name": "keyword",
|
|
2053
|
+
"required": false
|
|
2054
|
+
}
|
|
2055
|
+
},
|
|
2056
|
+
"description": "Search for recent CVEs by keyword (omit keyword to see all recent CVEs)",
|
|
2057
|
+
"examples": [
|
|
2058
|
+
"<%= config.bin %> vuln search openssl",
|
|
2059
|
+
"<%= config.bin %> vuln search openssl --days 30",
|
|
2060
|
+
"<%= config.bin %> vuln search log4j --severity critical",
|
|
2061
|
+
"<%= config.bin %> vuln search nginx --limit 10 --json",
|
|
2062
|
+
"<%= config.bin %> vuln search",
|
|
2063
|
+
"<%= config.bin %> vuln search --days 7 --severity high"
|
|
2064
|
+
],
|
|
2065
|
+
"flags": {
|
|
2066
|
+
"json": {
|
|
2067
|
+
"description": "Format output as json.",
|
|
2068
|
+
"helpGroup": "GLOBAL",
|
|
2069
|
+
"name": "json",
|
|
2070
|
+
"allowNo": false,
|
|
2071
|
+
"type": "boolean"
|
|
2072
|
+
},
|
|
2073
|
+
"days": {
|
|
2074
|
+
"char": "d",
|
|
2075
|
+
"description": "Time window in days (search CVEs published within last N days)",
|
|
2076
|
+
"name": "days",
|
|
2077
|
+
"default": 14,
|
|
2078
|
+
"hasDynamicHelp": false,
|
|
2079
|
+
"multiple": false,
|
|
2080
|
+
"type": "option"
|
|
2081
|
+
},
|
|
2082
|
+
"severity": {
|
|
2083
|
+
"char": "s",
|
|
2084
|
+
"description": "Minimum severity filter",
|
|
2085
|
+
"name": "severity",
|
|
2086
|
+
"hasDynamicHelp": false,
|
|
2087
|
+
"multiple": false,
|
|
2088
|
+
"options": [
|
|
2089
|
+
"low",
|
|
2090
|
+
"medium",
|
|
2091
|
+
"high",
|
|
2092
|
+
"critical"
|
|
2093
|
+
],
|
|
2094
|
+
"type": "option"
|
|
2095
|
+
},
|
|
2096
|
+
"limit": {
|
|
2097
|
+
"char": "l",
|
|
2098
|
+
"description": "Maximum number of results to display",
|
|
2099
|
+
"name": "limit",
|
|
2100
|
+
"default": 20,
|
|
2101
|
+
"hasDynamicHelp": false,
|
|
2102
|
+
"multiple": false,
|
|
2103
|
+
"type": "option"
|
|
2104
|
+
}
|
|
2105
|
+
},
|
|
2106
|
+
"hasDynamicHelp": false,
|
|
2107
|
+
"hiddenAliases": [],
|
|
2108
|
+
"id": "vuln:search",
|
|
2109
|
+
"pluginAlias": "devvami",
|
|
2110
|
+
"pluginName": "devvami",
|
|
2111
|
+
"pluginType": "core",
|
|
2112
|
+
"strict": true,
|
|
2113
|
+
"enableJsonFlag": true,
|
|
2114
|
+
"isESM": true,
|
|
2115
|
+
"relativePath": [
|
|
2116
|
+
"src",
|
|
2117
|
+
"commands",
|
|
2118
|
+
"vuln",
|
|
2119
|
+
"search.js"
|
|
2120
|
+
]
|
|
1725
2121
|
}
|
|
1726
2122
|
},
|
|
1727
|
-
"version": "1.
|
|
2123
|
+
"version": "1.4.1"
|
|
1728
2124
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { Command, Flags, Args } from '@oclif/core'
|
|
2
|
+
import ora from 'ora'
|
|
3
|
+
import chalk from 'chalk'
|
|
4
|
+
import { checkbox, confirm, input } from '@inquirer/prompts'
|
|
5
|
+
import { detectPlatform } from '../../services/platform.js'
|
|
6
|
+
import {
|
|
7
|
+
isChezmoiInstalled,
|
|
8
|
+
getManagedFiles,
|
|
9
|
+
getDefaultFileList,
|
|
10
|
+
getSensitivePatterns,
|
|
11
|
+
isPathSensitive,
|
|
12
|
+
isWSLWindowsPath,
|
|
13
|
+
} from '../../services/dotfiles.js'
|
|
14
|
+
import { loadConfig } from '../../services/config.js'
|
|
15
|
+
import { execOrThrow } from '../../services/shell.js'
|
|
16
|
+
import { formatDotfilesAdd } from '../../formatters/dotfiles.js'
|
|
17
|
+
import { DvmiError } from '../../utils/errors.js'
|
|
18
|
+
import { homedir } from 'node:os'
|
|
19
|
+
import { join } from 'node:path'
|
|
20
|
+
import { existsSync } from 'node:fs'
|
|
21
|
+
|
|
22
|
+
/** @import { DotfilesAddResult } from '../../types.js' */
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Expand tilde to home directory.
|
|
26
|
+
* @param {string} p
|
|
27
|
+
* @returns {string}
|
|
28
|
+
*/
|
|
29
|
+
function expandTilde(p) {
|
|
30
|
+
if (p.startsWith('~/') || p === '~') {
|
|
31
|
+
return join(homedir(), p.slice(2))
|
|
32
|
+
}
|
|
33
|
+
return p
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default class DotfilesAdd extends Command {
|
|
37
|
+
static description = 'Add dotfiles to chezmoi management with automatic encryption for sensitive files'
|
|
38
|
+
|
|
39
|
+
static examples = [
|
|
40
|
+
'<%= config.bin %> dotfiles add',
|
|
41
|
+
'<%= config.bin %> dotfiles add ~/.zshrc',
|
|
42
|
+
'<%= config.bin %> dotfiles add ~/.zshrc ~/.gitconfig',
|
|
43
|
+
'<%= config.bin %> dotfiles add ~/.ssh/id_ed25519 --encrypt',
|
|
44
|
+
'<%= config.bin %> dotfiles add --json ~/.zshrc',
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
static enableJsonFlag = true
|
|
48
|
+
|
|
49
|
+
static flags = {
|
|
50
|
+
help: Flags.help({ char: 'h' }),
|
|
51
|
+
encrypt: Flags.boolean({ char: 'e', description: 'Force encryption for all files being added', default: false }),
|
|
52
|
+
'no-encrypt': Flags.boolean({ description: 'Disable auto-encryption (add all as plaintext)', default: false }),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static args = {
|
|
56
|
+
files: Args.string({ description: 'File paths to add', required: false }),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// oclif does not support variadic args natively via Args.string for multiple values;
|
|
60
|
+
// we'll parse extra args from this.argv
|
|
61
|
+
static strict = false
|
|
62
|
+
|
|
63
|
+
async run() {
|
|
64
|
+
const { flags } = await this.parse(DotfilesAdd)
|
|
65
|
+
const isJson = flags.json
|
|
66
|
+
const forceEncrypt = flags.encrypt
|
|
67
|
+
const forceNoEncrypt = flags['no-encrypt']
|
|
68
|
+
|
|
69
|
+
// Collect file args from argv (strict=false allows extra positional args)
|
|
70
|
+
const rawArgs = this.argv.filter((a) => !a.startsWith('-'))
|
|
71
|
+
const fileArgs = rawArgs
|
|
72
|
+
|
|
73
|
+
// Pre-checks
|
|
74
|
+
const config = await loadConfig()
|
|
75
|
+
if (!config.dotfiles?.enabled) {
|
|
76
|
+
throw new DvmiError(
|
|
77
|
+
'Chezmoi dotfiles management is not configured',
|
|
78
|
+
'Run `dvmi dotfiles setup` first',
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const chezmoiInstalled = await isChezmoiInstalled()
|
|
83
|
+
if (!chezmoiInstalled) {
|
|
84
|
+
const platformInfo = await detectPlatform()
|
|
85
|
+
const hint = platformInfo.platform === 'macos'
|
|
86
|
+
? 'Run `brew install chezmoi` or visit https://chezmoi.io/install'
|
|
87
|
+
: 'Run `sh -c "$(curl -fsLS get.chezmoi.io)"` or visit https://chezmoi.io/install'
|
|
88
|
+
throw new DvmiError('chezmoi is not installed', hint)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const platformInfo = await detectPlatform()
|
|
92
|
+
const { platform } = platformInfo
|
|
93
|
+
const sensitivePatterns = getSensitivePatterns(config)
|
|
94
|
+
|
|
95
|
+
// Get already-managed files for V-007 check
|
|
96
|
+
const managedFiles = await getManagedFiles()
|
|
97
|
+
const managedPaths = new Set(managedFiles.map((f) => f.path))
|
|
98
|
+
|
|
99
|
+
/** @type {DotfilesAddResult} */
|
|
100
|
+
const result = { added: [], skipped: [], rejected: [] }
|
|
101
|
+
|
|
102
|
+
if (fileArgs.length > 0) {
|
|
103
|
+
// Direct mode — files provided as arguments
|
|
104
|
+
for (const rawPath of fileArgs) {
|
|
105
|
+
const absPath = expandTilde(rawPath)
|
|
106
|
+
const displayPath = rawPath
|
|
107
|
+
|
|
108
|
+
// V-002: WSL2 Windows path rejection
|
|
109
|
+
if (platform === 'wsl2' && isWSLWindowsPath(absPath)) {
|
|
110
|
+
result.rejected.push({ path: displayPath, reason: 'Windows filesystem paths not supported on WSL2. Use Linux-native paths (~/) instead.' })
|
|
111
|
+
continue
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// V-001: file must exist
|
|
115
|
+
if (!existsSync(absPath)) {
|
|
116
|
+
result.skipped.push({ path: displayPath, reason: 'File not found' })
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// V-007: not already managed
|
|
121
|
+
if (managedPaths.has(absPath)) {
|
|
122
|
+
result.skipped.push({ path: displayPath, reason: 'Already managed by chezmoi' })
|
|
123
|
+
continue
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Determine encryption
|
|
127
|
+
let encrypt = false
|
|
128
|
+
if (forceEncrypt) {
|
|
129
|
+
encrypt = true
|
|
130
|
+
} else if (forceNoEncrypt) {
|
|
131
|
+
encrypt = false
|
|
132
|
+
} else {
|
|
133
|
+
encrypt = isPathSensitive(rawPath, sensitivePatterns)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const args = ['add']
|
|
138
|
+
if (encrypt) args.push('--encrypt')
|
|
139
|
+
args.push(absPath)
|
|
140
|
+
await execOrThrow('chezmoi', args)
|
|
141
|
+
result.added.push({ path: displayPath, encrypted: encrypt })
|
|
142
|
+
} catch {
|
|
143
|
+
result.skipped.push({ path: displayPath, reason: `Failed to add to chezmoi. Run \`chezmoi doctor\` to verify your setup.` })
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (isJson) return result
|
|
148
|
+
this.log(formatDotfilesAdd(result))
|
|
149
|
+
return result
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Interactive mode — no file args
|
|
153
|
+
if (isJson) {
|
|
154
|
+
// In --json with no files: return empty result
|
|
155
|
+
return result
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Non-interactive guard for interactive mode
|
|
159
|
+
const isCI = process.env.CI === 'true'
|
|
160
|
+
const isNonInteractive = !process.stdout.isTTY
|
|
161
|
+
if (isCI || isNonInteractive) {
|
|
162
|
+
this.error(
|
|
163
|
+
'This command requires an interactive terminal (TTY) when no files are specified. Provide file paths as arguments or run with --json.',
|
|
164
|
+
{ exit: 1 },
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const spinner = ora({ spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Loading recommended files...') }).start()
|
|
169
|
+
const recommended = getDefaultFileList(platform)
|
|
170
|
+
spinner.stop()
|
|
171
|
+
|
|
172
|
+
// Filter and build choices
|
|
173
|
+
const choices = recommended.map((rec) => {
|
|
174
|
+
const absPath = expandTilde(rec.path)
|
|
175
|
+
const exists = existsSync(absPath)
|
|
176
|
+
const alreadyManaged = managedPaths.has(absPath)
|
|
177
|
+
const sensitive = rec.autoEncrypt || isPathSensitive(rec.path, sensitivePatterns)
|
|
178
|
+
const encTag = sensitive ? chalk.dim(' (auto-encrypted)') : ''
|
|
179
|
+
const statusTag = !exists ? chalk.dim(' (not found)') : alreadyManaged ? chalk.dim(' (already tracked)') : ''
|
|
180
|
+
return {
|
|
181
|
+
name: `${rec.path}${encTag}${statusTag} — ${rec.description}`,
|
|
182
|
+
value: rec.path,
|
|
183
|
+
checked: exists && !alreadyManaged,
|
|
184
|
+
disabled: alreadyManaged ? 'already tracked' : false,
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
const selected = await checkbox({
|
|
189
|
+
message: 'Select files to add to chezmoi:',
|
|
190
|
+
choices,
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
// Offer custom file
|
|
194
|
+
const addCustom = await confirm({ message: 'Add a custom file path?', default: false })
|
|
195
|
+
if (addCustom) {
|
|
196
|
+
const customPath = await input({ message: 'Enter file path:' })
|
|
197
|
+
if (customPath.trim()) selected.push(customPath.trim())
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (selected.length === 0) {
|
|
201
|
+
this.log(chalk.dim(' No files selected.'))
|
|
202
|
+
return result
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const addSpinner = ora({ spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Adding files to chezmoi...') }).start()
|
|
206
|
+
addSpinner.stop()
|
|
207
|
+
|
|
208
|
+
for (const rawPath of selected) {
|
|
209
|
+
const absPath = expandTilde(rawPath)
|
|
210
|
+
|
|
211
|
+
if (platform === 'wsl2' && isWSLWindowsPath(absPath)) {
|
|
212
|
+
result.rejected.push({ path: rawPath, reason: 'Windows filesystem paths not supported on WSL2' })
|
|
213
|
+
continue
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!existsSync(absPath)) {
|
|
217
|
+
result.skipped.push({ path: rawPath, reason: 'File not found' })
|
|
218
|
+
continue
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (managedPaths.has(absPath)) {
|
|
222
|
+
result.skipped.push({ path: rawPath, reason: 'Already managed by chezmoi' })
|
|
223
|
+
continue
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let encrypt = false
|
|
227
|
+
if (forceEncrypt) {
|
|
228
|
+
encrypt = true
|
|
229
|
+
} else if (forceNoEncrypt) {
|
|
230
|
+
encrypt = false
|
|
231
|
+
} else {
|
|
232
|
+
encrypt = isPathSensitive(rawPath, sensitivePatterns)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
const args = ['add']
|
|
237
|
+
if (encrypt) args.push('--encrypt')
|
|
238
|
+
args.push(absPath)
|
|
239
|
+
await execOrThrow('chezmoi', args)
|
|
240
|
+
result.added.push({ path: rawPath, encrypted: encrypt })
|
|
241
|
+
} catch {
|
|
242
|
+
result.skipped.push({ path: rawPath, reason: `Failed to add. Run \`chezmoi doctor\` to verify your setup.` })
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
this.log(formatDotfilesAdd(result))
|
|
247
|
+
return result
|
|
248
|
+
}
|
|
249
|
+
}
|