dsh-autorestart 0.8.0 → 0.8.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/CHANGELOG.md +97 -0
- package/README.md +55 -6
- package/client.js +102 -9
- package/docs/README.zh.md +4 -2
- package/index.js +514 -39
- package/lib/backup.js +279 -9
- package/lib/checkpoint.js +134 -0
- package/lib/diagnostics.js +18 -0
- package/lib/restart-phase.js +263 -0
- package/lib/watchdog-disarm.js +506 -0
- package/lib/watchdog.js +86 -0
- package/locale/en.json +1 -0
- package/locale/zh.json +1 -0
- package/package.json +6 -1
- package/scripts/drill-rescue.sh +427 -0
- package/scripts/drill-watchdog.mjs +474 -0
- package/scripts/emergency-disable.sh +314 -0
- package/scripts/rescue-strings.zh.json +168 -0
- package/scripts/restore-profile.sh +593 -0
- package/scripts/watchdog.mjs +310 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# dsh-autorestart - the emergency switch (POSIX: Linux, WSL, macOS).
|
|
3
|
+
#
|
|
4
|
+
# Three traps this file avoids (the whole story: scripts/README.md):
|
|
5
|
+
# 1. the wording lives in rescue-strings.zh.json and is NESTED - it is read
|
|
6
|
+
# by path with node, never by a flat grep;
|
|
7
|
+
# 2. the English fallbacks below must NOT contain parentheses: dash counts
|
|
8
|
+
# them even inside single quotes, and one stray ")" breaks the script;
|
|
9
|
+
# 3. the sed expressions stay single-quoted: the shell eats the backslashes
|
|
10
|
+
# in a double-quoted sed program and truncates it.
|
|
11
|
+
#
|
|
12
|
+
# "I enabled the plugin and DSH does not start any more" is the hardest moment
|
|
13
|
+
# for a plugin: fixing it needs the plugin itself. This script touches ONE file
|
|
14
|
+
# - the package.json of the profile - and ONE array in it:
|
|
15
|
+
# `dsh.profile.bundles`.
|
|
16
|
+
#
|
|
17
|
+
# - disable: take "dsh-autorestart" out of the bundles (it was not there
|
|
18
|
+
# anyway -> report "already disabled", that is not a failure);
|
|
19
|
+
# - enable : put "dsh-autorestart" back into the bundles;
|
|
20
|
+
# - status : read only, says whether it is in the bundles right now.
|
|
21
|
+
#
|
|
22
|
+
# Two hard rules:
|
|
23
|
+
#
|
|
24
|
+
# 1. the file is backed up as `package.json.bak-emergency-<stamp>` before
|
|
25
|
+
# anything is written;
|
|
26
|
+
# 2. after writing, the JSON is parsed again and checked; if the check
|
|
27
|
+
# fails, the backup is put back and the failure is reported honestly.
|
|
28
|
+
#
|
|
29
|
+
# The indentation and the line endings of the file are kept as they are (the
|
|
30
|
+
# TEXT is edited, the JSON is not re-serialized).
|
|
31
|
+
#
|
|
32
|
+
# The file is pure ASCII on purpose: the Chinese wording lives in
|
|
33
|
+
# rescue-strings.zh.json (UTF-8 without a BOM).
|
|
34
|
+
set -eu
|
|
35
|
+
|
|
36
|
+
PACKAGE_NAME='dsh-autorestart'
|
|
37
|
+
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
38
|
+
STRINGS_FILE="$SCRIPT_DIR/rescue-strings.zh.json"
|
|
39
|
+
|
|
40
|
+
ACTION='disable'
|
|
41
|
+
DRY_RUN=0
|
|
42
|
+
PACKAGE_JSON=''
|
|
43
|
+
|
|
44
|
+
usage() {
|
|
45
|
+
printf 'Usage: %s [--action disable|enable|status] [--dry-run] [--package FILE]\n' "$0"
|
|
46
|
+
printf ' --action ... what to do (default: disable)\n'
|
|
47
|
+
printf ' --dry-run say what would be written, change nothing\n'
|
|
48
|
+
printf ' --package FILE the package.json to work on (default: the profile of this machine)\n'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
while [ $# -gt 0 ]; do
|
|
52
|
+
case "$1" in
|
|
53
|
+
--action) ACTION="${2:-}"; shift ;;
|
|
54
|
+
--dry-run|-n) DRY_RUN=1 ;;
|
|
55
|
+
--package) PACKAGE_JSON="${2:-}"; shift ;;
|
|
56
|
+
--help|-h) usage; exit 0 ;;
|
|
57
|
+
*) printf 'unknown argument: %s\n' "$1" >&2; usage >&2; exit 2 ;;
|
|
58
|
+
esac
|
|
59
|
+
shift
|
|
60
|
+
done
|
|
61
|
+
|
|
62
|
+
case "$ACTION" in
|
|
63
|
+
disable|enable|status) ;;
|
|
64
|
+
*) printf 'unknown action: %s\n' "$ACTION" >&2; usage >&2; exit 2 ;;
|
|
65
|
+
esac
|
|
66
|
+
|
|
67
|
+
NODE_BIN=''
|
|
68
|
+
if command -v node >/dev/null 2>&1; then
|
|
69
|
+
NODE_BIN=$(command -v node)
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# t <key> <english fallback>
|
|
73
|
+
#
|
|
74
|
+
# The key is a PATH into the strings file ("emergency.done"), because that file
|
|
75
|
+
# is nested. Node reads it (it is on every machine that runs DSH); without
|
|
76
|
+
# node every message falls back to English.
|
|
77
|
+
t() {
|
|
78
|
+
key="$1"
|
|
79
|
+
fallback="$2"
|
|
80
|
+
if [ -n "$NODE_BIN" ] && [ -f "$STRINGS_FILE" ]; then
|
|
81
|
+
value=$("$NODE_BIN" -e '
|
|
82
|
+
const fs = require("fs");
|
|
83
|
+
try {
|
|
84
|
+
const doc = JSON.parse(fs.readFileSync(process.argv[1], "utf8").replace(/^\uFEFF/, ""));
|
|
85
|
+
let value = doc;
|
|
86
|
+
for (const part of process.argv[2].split(".")) value = value == null ? undefined : value[part];
|
|
87
|
+
if (typeof value !== "string" || value === "") process.exit(1);
|
|
88
|
+
process.stdout.write(value);
|
|
89
|
+
} catch (error) { process.exit(1); }
|
|
90
|
+
' "$STRINGS_FILE" "$key" 2>/dev/null) || value=''
|
|
91
|
+
if [ -n "$value" ]; then
|
|
92
|
+
printf '%s' "$value"
|
|
93
|
+
return 0
|
|
94
|
+
fi
|
|
95
|
+
fi
|
|
96
|
+
printf '%s' "$fallback"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# fmt <text> <value> ...
|
|
100
|
+
#
|
|
101
|
+
# Node does the replacement when it is there: the Chinese wording is UTF-8 and
|
|
102
|
+
# a byte-wise sed would mangle it. Without node only the English fallbacks are
|
|
103
|
+
# used, and those are plain ASCII.
|
|
104
|
+
fmt() {
|
|
105
|
+
text="$1"
|
|
106
|
+
shift
|
|
107
|
+
if [ -n "$NODE_BIN" ]; then
|
|
108
|
+
if "$NODE_BIN" -e '
|
|
109
|
+
let text = process.argv[1];
|
|
110
|
+
process.argv.slice(2).forEach((value, index) => { text = text.split("{" + index + "}").join(value); });
|
|
111
|
+
process.stdout.write(text);
|
|
112
|
+
' "$text" "$@" 2>/dev/null; then
|
|
113
|
+
return 0
|
|
114
|
+
fi
|
|
115
|
+
fi
|
|
116
|
+
index=0
|
|
117
|
+
for value in "$@"; do
|
|
118
|
+
safe=$(printf '%s' "$value" | sed 's/[&|\\]/\\&/g')
|
|
119
|
+
text=$(printf '%s' "$text" | sed "s|{$index}|$safe|g")
|
|
120
|
+
index=$((index + 1))
|
|
121
|
+
done
|
|
122
|
+
printf '%s' "$text"
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
say() {
|
|
126
|
+
printf '%s\n' "$1"
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fail() {
|
|
130
|
+
printf '%s%s\n' "$(t 'emergency.failed' 'the emergency switch failed: {0}')" "$1" >&2
|
|
131
|
+
exit 1
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
full_path() {
|
|
135
|
+
target="${1:-}"
|
|
136
|
+
[ -n "$target" ] || return 0
|
|
137
|
+
case "$target" in
|
|
138
|
+
/*) resolved="$target" ;;
|
|
139
|
+
*) resolved="$PWD/$target" ;;
|
|
140
|
+
esac
|
|
141
|
+
while [ "${resolved%/}" != "$resolved" ] && [ "${resolved%/}" != '' ]; do
|
|
142
|
+
resolved="${resolved%/}"
|
|
143
|
+
done
|
|
144
|
+
printf '%s' "$resolved"
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
dsh_home() {
|
|
148
|
+
if [ -n "${DSH_HOME:-}" ]; then
|
|
149
|
+
full_path "$DSH_HOME"
|
|
150
|
+
return 0
|
|
151
|
+
fi
|
|
152
|
+
if [ -n "${HOME:-}" ]; then
|
|
153
|
+
full_path "$HOME/.dsh"
|
|
154
|
+
return 0
|
|
155
|
+
fi
|
|
156
|
+
printf ''
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# The package.json of the profile of this machine.
|
|
160
|
+
default_package_json() {
|
|
161
|
+
if [ -n "${DSH_PROFILE_DIR:-}" ]; then
|
|
162
|
+
explicit=$(full_path "$DSH_PROFILE_DIR")
|
|
163
|
+
if [ -n "$explicit" ] && [ -f "$explicit/package.json" ]; then
|
|
164
|
+
printf '%s' "$explicit/package.json"
|
|
165
|
+
return 0
|
|
166
|
+
fi
|
|
167
|
+
fi
|
|
168
|
+
root=$(dsh_home)
|
|
169
|
+
if [ -n "$root" ]; then
|
|
170
|
+
profiles="$root/profiles"
|
|
171
|
+
if [ -d "$profiles" ]; then
|
|
172
|
+
match=''
|
|
173
|
+
count=0
|
|
174
|
+
for entry in "$profiles"/*; do
|
|
175
|
+
[ -f "$entry/package.json" ] || continue
|
|
176
|
+
if grep -q "\"$PACKAGE_NAME\"" "$entry/package.json" 2>/dev/null; then
|
|
177
|
+
match="$entry/package.json"
|
|
178
|
+
count=$((count + 1))
|
|
179
|
+
fi
|
|
180
|
+
done
|
|
181
|
+
if [ "$count" -eq 1 ]; then
|
|
182
|
+
printf '%s' "$match"
|
|
183
|
+
return 0
|
|
184
|
+
fi
|
|
185
|
+
fi
|
|
186
|
+
if [ -n "${DSH_PROFILE:-}" ]; then
|
|
187
|
+
printf '%s' "$profiles/$DSH_PROFILE/package.json"
|
|
188
|
+
return 0
|
|
189
|
+
fi
|
|
190
|
+
printf '%s' "$profiles/desktop/package.json"
|
|
191
|
+
return 0
|
|
192
|
+
fi
|
|
193
|
+
printf ''
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# The bundles of a package.json, one per line.
|
|
197
|
+
bundles_list() {
|
|
198
|
+
file="$1"
|
|
199
|
+
[ -f "$file" ] || return 1
|
|
200
|
+
if [ -n "$NODE_BIN" ]; then
|
|
201
|
+
"$NODE_BIN" -e 'try { const d = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8").replace(/^\uFEFF/, "")); const b = d && d.dsh && d.dsh.profile && d.dsh.profile.bundles; if (!Array.isArray(b)) process.exit(1); process.stdout.write(b.join("\n")) } catch (error) { process.exit(1) }' "$file" 2>/dev/null
|
|
202
|
+
return $?
|
|
203
|
+
fi
|
|
204
|
+
sed -n 's/.*"bundles"[[:space:]]*:[[:space:]]*\[\([^]]*\)\].*/\1/p' "$file" | tr ',' '\n' | sed 's/["[:space:]]//g'
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
json_parses() {
|
|
208
|
+
file="$1"
|
|
209
|
+
[ -f "$file" ] || return 1
|
|
210
|
+
if [ -n "$NODE_BIN" ]; then
|
|
211
|
+
"$NODE_BIN" -e 'try { JSON.parse(require("fs").readFileSync(process.argv[1], "utf8").replace(/^\uFEFF/, "")); process.exit(0) } catch (error) { process.exit(1) }' "$file" >/dev/null 2>&1
|
|
212
|
+
return $?
|
|
213
|
+
fi
|
|
214
|
+
head -c 1 "$file" | grep -q '{' || return 1
|
|
215
|
+
return 0
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
# --- Start ---
|
|
219
|
+
|
|
220
|
+
if [ -n "$PACKAGE_JSON" ]; then
|
|
221
|
+
pkg=$(full_path "$PACKAGE_JSON")
|
|
222
|
+
else
|
|
223
|
+
pkg=$(default_package_json)
|
|
224
|
+
fi
|
|
225
|
+
if [ -z "$pkg" ]; then
|
|
226
|
+
fail "$(t 'emergency.notFound' 'the package.json of the profile cannot be found: {0}')" ''
|
|
227
|
+
fi
|
|
228
|
+
if [ ! -f "$pkg" ]; then
|
|
229
|
+
fail "$(fmt "$(t 'emergency.notFound' 'the package.json of the profile cannot be found: {0}')" "$pkg")"
|
|
230
|
+
fi
|
|
231
|
+
if ! json_parses "$pkg"; then
|
|
232
|
+
fail "$(fmt "$(t 'emergency.badJson' 'this package.json is not valid JSON: {0}')" "$pkg")"
|
|
233
|
+
fi
|
|
234
|
+
say "$(fmt "$(t 'emergency.file' 'file: {0}')" "$pkg")"
|
|
235
|
+
|
|
236
|
+
list=$(bundles_list "$pkg" || printf '')
|
|
237
|
+
if [ -z "$list" ]; then
|
|
238
|
+
fail "$(fmt "$(t 'emergency.noBundles' 'this profile has no dsh.profile.bundles list')" '')"
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
in_bundles=0
|
|
242
|
+
if printf '%s\n' "$list" | grep -qx "$PACKAGE_NAME"; then
|
|
243
|
+
in_bundles=1
|
|
244
|
+
fi
|
|
245
|
+
|
|
246
|
+
if [ "$ACTION" = 'status' ]; then
|
|
247
|
+
if [ "$in_bundles" -eq 1 ]; then
|
|
248
|
+
say "$(fmt "$(t 'emergency.enabled' 'enabled: {0} is in the bundles.')" "$PACKAGE_NAME")"
|
|
249
|
+
else
|
|
250
|
+
say "$(fmt "$(t 'emergency.disabled' 'disabled: {0} is not in the bundles.')" "$PACKAGE_NAME")"
|
|
251
|
+
fi
|
|
252
|
+
exit 0
|
|
253
|
+
fi
|
|
254
|
+
|
|
255
|
+
if [ "$ACTION" = 'disable' ] && [ "$in_bundles" -eq 0 ]; then
|
|
256
|
+
say "$(fmt "$(t 'emergency.alreadyDisabled' 'already disabled: {0} was not in the bundles (no file was changed).')" "$PACKAGE_NAME")"
|
|
257
|
+
exit 0
|
|
258
|
+
fi
|
|
259
|
+
if [ "$ACTION" = 'enable' ] && [ "$in_bundles" -eq 1 ]; then
|
|
260
|
+
say "$(fmt "$(t 'emergency.alreadyEnabled' 'already enabled: {0} was in the bundles (no file was changed).')" "$PACKAGE_NAME")"
|
|
261
|
+
exit 0
|
|
262
|
+
fi
|
|
263
|
+
|
|
264
|
+
if [ "$DRY_RUN" -eq 1 ]; then
|
|
265
|
+
if [ "$ACTION" = 'disable' ]; then
|
|
266
|
+
say "$(fmt "$(t 'emergency.dryRunWill' '[dry run] {0} would be taken out of the bundles.')" "$PACKAGE_NAME")"
|
|
267
|
+
else
|
|
268
|
+
say "$(fmt "$(t 'emergency.dryRunWill' '[dry run] {0} would be taken out of the bundles.')" "$PACKAGE_NAME")"
|
|
269
|
+
fi
|
|
270
|
+
say "$(fmt "$(t 'emergency.dryRunBackup' '[dry run] it would be backed up as: {0}.bak-emergency-<stamp>')" "$pkg")"
|
|
271
|
+
say "$(t 'emergency.dryRunNoChange' '[dry run] no file was changed.')"
|
|
272
|
+
exit 0
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
# --- Back up, then write ---
|
|
276
|
+
stamp=$(date -u +%Y%m%d-%H%M%S)
|
|
277
|
+
backup="$pkg.bak-emergency-$stamp"
|
|
278
|
+
if ! cp -- "$pkg" "$backup"; then
|
|
279
|
+
fail "$(fmt "$(t 'emergency.backupFailed' 'the backup failed - the original file was not touched: {0}')" "$backup")"
|
|
280
|
+
fi
|
|
281
|
+
|
|
282
|
+
tmp="$pkg.tmp-emergency-$$"
|
|
283
|
+
if [ "$ACTION" = 'disable' ]; then
|
|
284
|
+
sed "/^[[:space:]]*\"$PACKAGE_NAME\"[[:space:]]*,\{0,1\}[[:space:]]*$/d" "$pkg" > "$tmp"
|
|
285
|
+
else
|
|
286
|
+
sed "s/^\\([[:space:]]*\"bundles\"[[:space:]]*:[[:space:]]*\\[\\)$/\\1\\n \"$PACKAGE_NAME\",/" "$pkg" > "$tmp"
|
|
287
|
+
fi
|
|
288
|
+
if ! mv -- "$tmp" "$pkg"; then
|
|
289
|
+
rm -f -- "$tmp" 2>/dev/null || true
|
|
290
|
+
fail "$(fmt "$(t 'emergency.writeFailed' 'the write failed - the backup is at {0}: {1}')" "$backup" "$pkg")"
|
|
291
|
+
fi
|
|
292
|
+
|
|
293
|
+
# --- Read it back ---
|
|
294
|
+
if ! json_parses "$pkg"; then
|
|
295
|
+
cp -- "$backup" "$pkg" || true
|
|
296
|
+
fail "$(fmt "$(t 'emergency.verifyFailedRolledBack' 'the check failed - it was rolled back: {0}')" "$backup")"
|
|
297
|
+
fi
|
|
298
|
+
after=$(bundles_list "$pkg" || printf '')
|
|
299
|
+
if [ "$ACTION" = 'disable' ]; then
|
|
300
|
+
if printf '%s\n' "$after" | grep -qx "$PACKAGE_NAME"; then
|
|
301
|
+
cp -- "$backup" "$pkg" || true
|
|
302
|
+
fail "$(fmt "$(t 'emergency.verifyFailedRolledBack' 'the check failed - it was rolled back: {0}')" "$backup")"
|
|
303
|
+
fi
|
|
304
|
+
say "$(fmt "$(t 'emergency.done' 'done: {0}.')" "$(fmt "$(t 'emergency.summaryRemove' 'dsh-autorestart was taken out of the bundles')" '')")"
|
|
305
|
+
else
|
|
306
|
+
if ! printf '%s\n' "$after" | grep -qx "$PACKAGE_NAME"; then
|
|
307
|
+
cp -- "$backup" "$pkg" || true
|
|
308
|
+
fail "$(fmt "$(t 'emergency.verifyFailedRolledBack' 'the check failed - it was rolled back: {0}')" "$backup")"
|
|
309
|
+
fi
|
|
310
|
+
say "$(fmt "$(t 'emergency.done' 'done: {0}.')" "$(fmt "$(t 'emergency.summaryAdd' 'dsh-autorestart was put back into the bundles')" '')")"
|
|
311
|
+
fi
|
|
312
|
+
say "$(fmt "$(t 'emergency.backup' 'backup: {0}')" "$backup")"
|
|
313
|
+
say "$(t 'emergency.restartHint' 'Restart DSH for it to take effect.')"
|
|
314
|
+
exit 0
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"purpose": "User-facing Chinese for the dsh-autorestart rescue kit.",
|
|
4
|
+
"encoding": "UTF-8 WITHOUT a byte order mark. The .ps1 files next to this one are pure ASCII, so this is the only place a non-ASCII byte may live. A BOM here would break JSON.parse.",
|
|
5
|
+
"placeholders": "Values may contain {0}, {1} ... placeholders that the scripts fill in with -f formatting.",
|
|
6
|
+
"fallback": "Every key has an English fallback inside the .ps1. If this file is missing or broken, the scripts say so in English and keep working in English."
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
"common": {
|
|
10
|
+
"stringsMissing": "中文文案文件不在:{0}(改用英文兜底)",
|
|
11
|
+
"stringsBroken": "中文文案文件读不出来:{0}(改用英文兜底)",
|
|
12
|
+
"stringsMissingEn": "the Chinese strings file is missing: {0} (falling back to English messages)",
|
|
13
|
+
"stringsBrokenEn": "the Chinese strings file could not be read: {0} (falling back to English messages)"
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
"restore": {
|
|
17
|
+
"title": "dsh-autorestart —— profile 快照还原器",
|
|
18
|
+
"failPrefix": "还原失败:",
|
|
19
|
+
"brokenKept": "你原来的 profile 完整地留在这里:{0}",
|
|
20
|
+
"brokenUntouched": "(这个目录没有被动过,里面还是你出事之前的样子。)",
|
|
21
|
+
"noProfileDir": "找不到要还原的 profile 目录(用 -ProfileDir 说清楚要还原哪一个)。",
|
|
22
|
+
"target": "目标 profile:{0}",
|
|
23
|
+
"targetSuffix": "(目标:{0})",
|
|
24
|
+
"onlyProfiles": "只允许还原 {0} 里的 profile(目标:{1})",
|
|
25
|
+
"missingDir": "目标 profile 目录不存在:{0}(还原需要那个目录已经在那儿;脚本不会为了还原去新建一个 profile 目录)",
|
|
26
|
+
"notProfile": "{0}(目标:{1})。拒绝动手 —— 免得把不是 profile 的目录弄坏。",
|
|
27
|
+
"notProfileSuffix": "。拒绝动手 —— 免得把不是 profile 的目录弄坏。",
|
|
28
|
+
"brokenNotice": "这个 profile 已经坏了:{0}",
|
|
29
|
+
"backupsDir": "快照目录:{0}",
|
|
30
|
+
"usingSnapshot": "要用快照:{0}({1})",
|
|
31
|
+
"snapshotMissing": "找不到这份快照:{0}",
|
|
32
|
+
"snapshotNoManifest": "这份快照没有 manifest.json,不是一份快照:{0}",
|
|
33
|
+
"snapshotSuffix": "{0}(快照:{1})",
|
|
34
|
+
"dryRunMove": "[试运行] 会先把现在的 profile 挪到:{0}",
|
|
35
|
+
"dryRunCopy": "[试运行] 会把 {0} 里的配置和插件复制回:{1}",
|
|
36
|
+
"dryRunVerify": "[试运行] 会回读校验 package.json / dsh.profile.bundles / {0} 的版本号。",
|
|
37
|
+
"dryRunNoChange": "[试运行] 没有改动任何文件。",
|
|
38
|
+
"moveFailed": "挪走现在的 profile 失败(没有动任何东西):{0}",
|
|
39
|
+
"moved": "原来那份已经挪到:{0}",
|
|
40
|
+
"noProfileBefore": "原来那个 profile 目录本来就不在,直接新建。",
|
|
41
|
+
"copyFailed": "把快照复制回去失败(原来那份还在:{0}):{1}",
|
|
42
|
+
"checkOk": " [好] {0} —— {1}",
|
|
43
|
+
"checkSkipped": " [跳过] {0} —— {1}",
|
|
44
|
+
"checkBad": " [坏] {0} —— {1}",
|
|
45
|
+
"verifyFailed": "还原后的 profile 没通过校验({0})。",
|
|
46
|
+
"noteWithPlugins": "(含插件)",
|
|
47
|
+
"noteNoPlugins": "(只有配置文件,没有插件)",
|
|
48
|
+
"done": "已还原到 {0} 的快照{1};现在可以重新打开 DSH。",
|
|
49
|
+
"brokenLeft": "原来那份留在了:{0}(确认没问题之后可以自己删掉它)",
|
|
50
|
+
|
|
51
|
+
"gateEmptyPath": "目标路径是空的",
|
|
52
|
+
"gateDriveRoot": "拒绝动盘符根目录",
|
|
53
|
+
"gateFsRoot": "拒绝动文件系统根目录",
|
|
54
|
+
"gateHome": "拒绝动用户主目录本身",
|
|
55
|
+
"gateSystemDir": "拒绝动系统目录:{0}",
|
|
56
|
+
"gateOtherUser": "拒绝动别人的用户目录",
|
|
57
|
+
|
|
58
|
+
"fingerprintNotProfile": "这里没有 package.json —— 不像 DSH profile",
|
|
59
|
+
"fingerprintProfile": "profile 的 package.json 里有 dsh.profile",
|
|
60
|
+
"fingerprintBrokenNoDsh": "profile 坏了(package.json 不再是 profile),但 profile 的文件还在",
|
|
61
|
+
"fingerprintBrokenNoPkg": "profile 坏了(package.json 没了),但 profile 的文件还在",
|
|
62
|
+
"fingerprintNotDshProfile": "package.json 里没有 dsh.profile,也没有别的东西能证明这是 profile",
|
|
63
|
+
|
|
64
|
+
"snapshotDirMissing": "找不到快照目录:{0}",
|
|
65
|
+
"snapshotNone": "这个目录里没有一份带 manifest 的快照:{0}",
|
|
66
|
+
"snapshotNoConfigPkg": "这份快照里没有 config\\package.json",
|
|
67
|
+
"snapshotBadConfigPkg": "这份快照的 package.json 不是合法的 JSON",
|
|
68
|
+
"snapshotNoDshProfile": "这份快照的 package.json 里没有 dsh.profile",
|
|
69
|
+
"snapshotIsProfile": "这份快照里装的是一个 DSH profile",
|
|
70
|
+
"snapshotUnknownTime": "时间不明",
|
|
71
|
+
|
|
72
|
+
"verifyPkgLabel": "package.json 能解析",
|
|
73
|
+
"verifyPkgMissing": "package.json 不存在",
|
|
74
|
+
"verifyPkgBad": "不是合法的 JSON",
|
|
75
|
+
"verifyBundlesLabel": "dsh.profile.bundles 有内容",
|
|
76
|
+
"verifyBundlesCount": "{0} 个",
|
|
77
|
+
"verifyBundlesEmpty": "没有 dsh.profile.bundles,或者它是空的",
|
|
78
|
+
"verifyPluginLabel": "{0} 的版本号读得到",
|
|
79
|
+
"verifyPluginNotDeclared": "这个 profile 没有声明要装 {0}(没什么可读)",
|
|
80
|
+
"verifyPluginMissing": "profile 里写着要装 {0},但 node_modules\\{0} 不在 —— 配置说一套、磁盘是另一套",
|
|
81
|
+
"verifyPluginNoVersion": "读不到版本号"
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
"drill": {
|
|
85
|
+
"dir": "演练目录:{0}",
|
|
86
|
+
"broke": "已经故意弄坏:package.json 写成非法 JSON,插件目录删掉了。",
|
|
87
|
+
"outputHeader": "--- 还原脚本的输出({0})---",
|
|
88
|
+
"outputEnd": "--- 输出结束 ---",
|
|
89
|
+
"assertions": "断言:",
|
|
90
|
+
"assertPass": " [通过] {0}",
|
|
91
|
+
"assertFail": " [失败] {0} —— {1}",
|
|
92
|
+
"assertFailPlain": " [失败] {0}",
|
|
93
|
+
"refusedNoResult": " [失败] {0} —— 没有拿到运行结果",
|
|
94
|
+
"refusedNotRefused": " [失败] {0} —— 退出码是 0,脚本根本没有拒绝",
|
|
95
|
+
"refusedNoReason": " [失败] {0} —— 退出码是 {1},但输出里没有拒绝原因(期望其中之一:{2})",
|
|
96
|
+
"refusedOk": " [通过] {0} —— 退出码 {1},输出里有拒绝原因「{2}」",
|
|
97
|
+
"cleanup": "清理临时目录:{0}",
|
|
98
|
+
"cleaned": "临时目录已清理干净。",
|
|
99
|
+
"notCleaned": "临时目录没有清理干净,请自己删掉:{0}",
|
|
100
|
+
"failed": "演练失败:{0} 条断言没过。",
|
|
101
|
+
"passed": "演练全部通过:故意弄坏 → 还原 → 校验 → 清理,一整圈跑通了。",
|
|
102
|
+
"error": "演练出错:{0}",
|
|
103
|
+
"noScript": "演练失败:找不到 {0}",
|
|
104
|
+
"dirOccupied": "演练失败:这个演练目录已经被占用了:{0}",
|
|
105
|
+
|
|
106
|
+
"assertDryRunExit": "试运行退出码是 0",
|
|
107
|
+
"assertDryRunUntouched": "试运行没有改动 package.json",
|
|
108
|
+
"assertDryRunNoBroken": "试运行没有留下 broken 目录",
|
|
109
|
+
"assertRestoreExit": "还原退出码是 0",
|
|
110
|
+
"assertChineseOutput": "还原脚本的输出里有中文(文案是从 JSON 读的)",
|
|
111
|
+
"assertParses": "还原后 package.json 能解析",
|
|
112
|
+
"assertUsedSnapshot": "用的是带 manifest 的那份快照(跳过了更新的诱饵)",
|
|
113
|
+
"assertBundles": "还原后 dsh.profile.bundles 有内容",
|
|
114
|
+
"assertVersionReadable": "还原后插件版本读得到",
|
|
115
|
+
"assertVersionSame": "插件版本是快照里的那一个",
|
|
116
|
+
"assertBrokenExists": "broken 证据目录存在",
|
|
117
|
+
"assertBrokenKeeps": "broken 目录里保留的还是坏掉的那份",
|
|
118
|
+
"assertNotProfileRefused": "不像 profile 的目录被拒绝(退出码 + 拒绝原因)",
|
|
119
|
+
"assertNotProfileUntouched": "被拒绝的时候没有动那个目录",
|
|
120
|
+
"assertDriveRootRefused": "盘符根目录被拒绝(退出码 + 拒绝原因)",
|
|
121
|
+
"assertSystemDirRefused": "系统目录 {0} 被拒绝(退出码 + 拒绝原因)",
|
|
122
|
+
"assertSystemDirIntact": "系统目录 {0} 原封不动(没被挪走)",
|
|
123
|
+
"assertMissingDirRefused": "目标 profile 目录不存在时被拒绝(退出码 + 拒绝原因)",
|
|
124
|
+
"assertMissingDirNotCreated": "目标目录不存在时没有把它创建出来",
|
|
125
|
+
"assertLyingRefused": "声明了插件却没装 → 还原必须失败(退出码 + 拒绝原因)",
|
|
126
|
+
"assertLyingBrokenHint": "声明了插件却没装 → 明确说 broken 目录还在",
|
|
127
|
+
"assertPs1Ascii": "restore-profile.ps1 是纯 ASCII(maxByte <= 127)",
|
|
128
|
+
"assertCmdAscii": "restore-profile.cmd 是纯 ASCII",
|
|
129
|
+
"assertOnlyTemp": "演练只在临时目录里动手(broken 目录在临时目录下面)",
|
|
130
|
+
"assertLockFile": "演练目录里有锁文件",
|
|
131
|
+
|
|
132
|
+
"keywords": {
|
|
133
|
+
"notProfile": ["不像", "不是 profile"],
|
|
134
|
+
"driveRoot": ["盘符", "根目录"],
|
|
135
|
+
"systemDir": ["系统目录", "不允许"],
|
|
136
|
+
"missingDir": ["不存在"],
|
|
137
|
+
"notValidated": ["没通过校验", "校验"],
|
|
138
|
+
"dryRun": ["试运行"]
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
"emergency": {
|
|
143
|
+
"failed": "急救开关失败:{0}",
|
|
144
|
+
"notFound": "找不到 profile 的 package.json:{0}",
|
|
145
|
+
"badJson": "这个 package.json 不是合法的 JSON:{0}",
|
|
146
|
+
"file": "文件:{0}",
|
|
147
|
+
"enabled": "已启用:{0} 在 bundles 里。",
|
|
148
|
+
"disabled": "已禁用:{0} 不在 bundles 里。",
|
|
149
|
+
"notInBundles": "在 bundles 里找不到 {0}",
|
|
150
|
+
"alreadyDisabled": "已经禁用:bundles 里本来就没有 {0}(没有改动任何文件)。",
|
|
151
|
+
"alreadyEnabled": "已经启用:{0} 本来就在 bundles 里(没有改动任何文件)。",
|
|
152
|
+
"noBundles": "这个 profile 里没有 dsh.profile.bundles 列表,装不回去(请手动加一行 \"dsh-autorestart\")。",
|
|
153
|
+
"dryRunWill": "[试运行] 将会{0}。",
|
|
154
|
+
"dryRunWrite": "[试运行] 将会写入:{0}",
|
|
155
|
+
"dryRunBackup": "[试运行] 将会先备份成:{0}.bak-emergency-<时间戳>",
|
|
156
|
+
"dryRunNoChange": "[试运行] 没有改动任何文件。",
|
|
157
|
+
"backupFailed": "备份失败(没有动原文件):{0}",
|
|
158
|
+
"writeFailed": "写入失败(备份在 {0}):{1}",
|
|
159
|
+
"rolledBack": "已经用备份回滚:{0}",
|
|
160
|
+
"verifyFailedNoRollback": "校验失败,而且回滚也失败了(备份在 {0}):{1}",
|
|
161
|
+
"verifyFailedRolledBack": "校验失败(已回滚):{0}",
|
|
162
|
+
"done": "已经{0}。",
|
|
163
|
+
"backup": "备份:{0}",
|
|
164
|
+
"restartHint": "请重启 DSH 才会生效。",
|
|
165
|
+
"summaryRemove": "把 dsh-autorestart 从 bundles 里拿掉",
|
|
166
|
+
"summaryAdd": "把 dsh-autorestart 加回 bundles"
|
|
167
|
+
}
|
|
168
|
+
}
|