@alipay/alipay-aipay 1.7.2 → 1.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -18
- package/dist/commands/install.js +24 -17
- package/package.json +3 -3
- package/skill/SKILL.md +24 -2
- package/skill/VERSION +1 -1
- package/skill/references/normal/json/customer-messages.json +1 -1
- package/skill/references/normal/scripts/channel_offer.mjs +83 -0
- package/skill/references/normal/scripts/runtime.mjs +3 -1
- package/skill-pre/SKILL.md +27 -5
- package/skill-pre/VERSION +1 -1
- package/skill-pre/references/integration/flow.md +43 -25
- package/skill-pre/references/integration/modules/checklist.md +4 -4
- package/skill-pre/references/integration/modules/sandbox/sandbox-setup-guide.md +33 -33
- package/skill-pre/references/integration/modules/scripts/local_402_sandbox_pay.py +3 -3
- package/skill-pre/references/integration/modules/scripts/sandbox_config.sh +12 -2
- package/skill-pre/references/normal/alipay-cli-env.md +3 -1
- package/skill-pre/references/normal/conversation-telemetry.md +4 -2
- package/skill-pre/references/normal/full-process-routing.md +2 -2
- package/skill-pre/references/normal/json/customer-messages.json +1 -1
- package/skill-pre/references/normal/json/policy-rules.json +1 -1
- package/skill-pre/references/normal/scripts/alipay_cli_env.mjs +9 -0
- package/skill-pre/references/normal/scripts/alipay_cli_refresh.mjs +10 -1
- package/skill-pre/references/normal/scripts/auth_runtime_runner.mjs +4 -1
- package/skill-pre/references/normal/scripts/common.sh +11 -1
- package/skill-pre/references/normal/scripts/onboarding_discovery_runner.mjs +4 -1
- package/skill-pre/references/normal/scripts/onboarding_message_runner.mjs +10 -1
- package/skill-pre/references/normal/scripts/onboarding_recovery_runner.mjs +4 -1
- package/skill-pre/references/normal/scripts/open_official_url.sh +10 -2
- package/skill-pre/references/normal/scripts/preview_runtime.mjs +32 -2
- package/skill-pre/references/normal/scripts/python_script_runner.mjs +9 -4
- package/skill-pre/references/normal/scripts/render_customer_message.mjs +4 -2
- package/skill-pre/references/normal/scripts/runtime.mjs +50 -6
- package/skill-pre/references/normal/scripts/telemetry.mjs +9 -0
- package/skill-pre/references/normal/scripts/windows/acl-native.mjs +3 -0
- package/skill-pre/references/normal/scripts/windows/acl.mjs +41 -0
- package/skill-pre/references/normal/scripts/windows/bin/alipay-cli +4 -0
- package/skill-pre/references/normal/scripts/windows/bootstrap-node.ps1 +140 -0
- package/skill-pre/references/normal/scripts/windows/cli-install.mjs +51 -0
- package/skill-pre/references/normal/scripts/windows/cli-process.mjs +11 -0
- package/skill-pre/references/normal/scripts/windows/execution.mjs +51 -0
- package/skill-pre/references/normal/scripts/windows/git-discovery.mjs +42 -0
- package/skill-pre/references/normal/scripts/windows/policy.json +13 -0
- package/skill-pre/references/normal/scripts/windows/preflight.mjs +182 -0
- package/skill-pre/references/normal/scripts/windows/prepare.mjs +83 -0
- package/skill-pre/references/normal/scripts/windows/probe.sh +14 -0
- package/skill-pre/references/normal/scripts/windows/python-execution.mjs +38 -0
- package/skill-pre/references/normal/scripts/windows/python-runtime.mjs +85 -0
- package/skill-pre/references/normal/scripts/windows/routing.mjs +83 -0
- package/skill-pre/references/normal/scripts/windows/shell_helpers.sh +83 -0
- package/skill-pre/references/normal/windows-environment.md +82 -0
- package/skill-pre/references/onboarding/flow.md +4 -1
- package/skill-pre/references/onboarding/modules/scripts/app.sh +1 -0
- package/skill-pre/references/onboarding/modules/scripts/ar_sign_apply.sh +10 -6
- package/skill-pre/references/onboarding/modules/scripts/auth.sh +1 -0
- package/skill-pre/references/runtime-entry.md +13 -4
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { spawnSync as nativeSpawn } from 'node:child_process';
|
|
4
|
+
const { spawnSync } = process.platform === 'win32'
|
|
5
|
+
? await import('./windows/execution.mjs')
|
|
6
|
+
: { spawnSync: nativeSpawn };
|
|
4
7
|
import process from 'node:process';
|
|
5
8
|
|
|
6
9
|
const internalPrefix = 'ALIPAY_AIPAY_INTERNAL:';
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
# 调用前置: 无
|
|
6
6
|
#=============================================================================
|
|
7
7
|
|
|
8
|
+
case "$(uname -s)" in
|
|
9
|
+
MINGW*|MSYS*) source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/windows/shell_helpers.sh" || exit 78 ;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
8
12
|
case "${HOME:-}" in
|
|
9
13
|
/*)
|
|
10
14
|
user_bin="$HOME/.local/bin"
|
|
@@ -39,6 +43,9 @@ ALIPAY_AIPAY_LOCAL_CONFIG_PERMISSION_EXIT_CODE=77
|
|
|
39
43
|
ALIPAY_AIPAY_LOCAL_CONFIG_ACCESS_CONFIRMED=false
|
|
40
44
|
|
|
41
45
|
alipay_cli_credentials_dir() {
|
|
46
|
+
case "$(uname -s)" in
|
|
47
|
+
MINGW*|MSYS*) cygpath -u "$ALIPAY_WINDOWS_CREDENTIALS_DIR"; return $? ;;
|
|
48
|
+
esac
|
|
42
49
|
case "${HOME:-}" in
|
|
43
50
|
/*) [ "$HOME" != "/" ] || return "$ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE" ;;
|
|
44
51
|
*) return "$ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE" ;;
|
|
@@ -47,6 +54,9 @@ alipay_cli_credentials_dir() {
|
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
alipay_cli_logs_dir() {
|
|
57
|
+
case "$(uname -s)" in
|
|
58
|
+
MINGW*|MSYS*) cygpath -u "$ALIPAY_WINDOWS_LOGS_DIR"; return $? ;;
|
|
59
|
+
esac
|
|
50
60
|
local data_home="${XDG_DATA_HOME:-}"
|
|
51
61
|
if [ -z "$data_home" ]; then
|
|
52
62
|
case "${HOME:-}" in
|
|
@@ -67,7 +77,7 @@ alipay_cli_has_local_state_permission_error() {
|
|
|
67
77
|
[ -n "$raw" ] || return 1
|
|
68
78
|
|
|
69
79
|
permission_pattern='permission denied|operation not permitted|(^|[^[:alnum:]_])(EACCES|EPERM)([^[:alnum:]_]|$)'
|
|
70
|
-
private_state_pattern='poll_state\.enc\.tmp|session\.enc|\.alipay-cli[/\\]credentials|alipay-cli[/\\]logs'
|
|
80
|
+
private_state_pattern='poll_state\.enc\.tmp|session\.enc|\.alipay-cli[/\\]credentials|alipay-cli[/\\](data[/\\])?logs'
|
|
71
81
|
local_state_failure_pattern='写入临时文件失败|删除凭据文件失败|can.?t rename log file'
|
|
72
82
|
|
|
73
83
|
printf '%s' "$raw" | grep -qiE "$local_state_failure_pattern" && return 0
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { spawn } from 'node:child_process';
|
|
3
|
+
import { spawn as nativeSpawn } from 'node:child_process';
|
|
4
|
+
const { spawn } = process.platform === 'win32'
|
|
5
|
+
? await import('./windows/execution.mjs')
|
|
6
|
+
: { spawn: nativeSpawn };
|
|
4
7
|
import path from 'node:path';
|
|
5
8
|
import process from 'node:process';
|
|
6
9
|
import { fileURLToPath } from 'node:url';
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
const windowsRouting = process.platform === 'win32' ? await import('./windows/routing.mjs') : null;
|
|
5
|
+
let windowsMccCatalog;
|
|
6
|
+
|
|
3
7
|
import { createHash } from 'node:crypto';
|
|
4
8
|
import path from 'node:path';
|
|
5
9
|
import process from 'node:process';
|
|
@@ -156,6 +160,7 @@ async function readStdin() {
|
|
|
156
160
|
}
|
|
157
161
|
|
|
158
162
|
function runPlatformEntry(args = {}) {
|
|
163
|
+
if (windowsRouting && windowsRouting.onboardingRoute() === 'READY') return '';
|
|
159
164
|
if (!isWindowsPlatform()) return '';
|
|
160
165
|
const runContext = currentRunContext();
|
|
161
166
|
if (runContext?.terminal) {
|
|
@@ -305,6 +310,10 @@ function mccCandidateFromRecord(record, productType = '', mccRunContext = captur
|
|
|
305
310
|
}
|
|
306
311
|
|
|
307
312
|
function dynamicMccRecordsFromCatalog(rawCatalog) {
|
|
313
|
+
if (windowsRouting && rawCatalog === '@windows-stdin') {
|
|
314
|
+
windowsMccCatalog ??= fs.readFileSync(0, 'utf8');
|
|
315
|
+
rawCatalog = windowsMccCatalog;
|
|
316
|
+
}
|
|
308
317
|
let catalog;
|
|
309
318
|
try {
|
|
310
319
|
catalog = typeof rawCatalog === 'string' ? JSON.parse(rawCatalog) : rawCatalog;
|
|
@@ -1332,7 +1341,7 @@ export async function runOnboardingMessageCli(argv = process.argv) {
|
|
|
1332
1341
|
if (command === 'platform-entry') {
|
|
1333
1342
|
const message = runPlatformEntry(args);
|
|
1334
1343
|
if (message) console.log(message);
|
|
1335
|
-
return;
|
|
1344
|
+
return windowsRouting ? (message ? 'LEGACY' : 'READY') : undefined;
|
|
1336
1345
|
}
|
|
1337
1346
|
if (command === 'product-clarify') {
|
|
1338
1347
|
console.log(runProductClarify());
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { spawnSync as nativeSpawn } from 'node:child_process';
|
|
4
|
+
const { spawnSync } = process.platform === 'win32'
|
|
5
|
+
? await import('./windows/execution.mjs')
|
|
6
|
+
: { spawnSync: nativeSpawn };
|
|
4
7
|
import path from 'node:path';
|
|
5
8
|
import process from 'node:process';
|
|
6
9
|
import { fileURLToPath } from 'node:url';
|
|
@@ -6,6 +6,12 @@ KIND="${1:-}"
|
|
|
6
6
|
IFS= read -r URL || URL=""
|
|
7
7
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
8
|
POLICY_FILE="${SCRIPT_DIR}/../json/policy-rules.json"
|
|
9
|
+
case "$(uname -s)" in
|
|
10
|
+
MINGW*|MSYS*)
|
|
11
|
+
source "$SCRIPT_DIR/windows/shell_helpers.sh" || exit 78
|
|
12
|
+
POLICY_FILE=$(cygpath -m "$POLICY_FILE") || exit 78
|
|
13
|
+
;;
|
|
14
|
+
esac
|
|
9
15
|
|
|
10
16
|
case "$KIND" in authorization|public-key-confirmation) ;; *) echo "OPEN_FAILED"; exit 1 ;; esac
|
|
11
17
|
command -v node >/dev/null 2>&1 || { echo "OPEN_FAILED"; exit 1; }
|
|
@@ -18,8 +24,10 @@ const policy = JSON.parse(fs.readFileSync(policyFile, "utf8"));
|
|
|
18
24
|
if (/[\u0000-\u0020\u007f`()\[\]<>|]/.test(raw)) process.exit(1);
|
|
19
25
|
let url;
|
|
20
26
|
try { url = new URL(raw); } catch { process.exit(1); }
|
|
21
|
-
const
|
|
22
|
-
|
|
27
|
+
const entries = policy.temporaryUrlAllowlist[kind === "authorization" ? "authorization" : "publicKeyConfirmation"];
|
|
28
|
+
const allowed = (Array.isArray(entries) ? entries : [entries]).find((entry) =>
|
|
29
|
+
entry && url.hostname === entry.host && url.pathname === entry.path);
|
|
30
|
+
if (!allowed || url.protocol !== "https:" || url.username || url.password || url.port || url.hash) process.exit(1);
|
|
23
31
|
if (kind === "authorization") {
|
|
24
32
|
const required = allowed.requiredQueryParams;
|
|
25
33
|
const optional = allowed.optionalQueryParams;
|
|
@@ -108,6 +108,36 @@ export function openLocalKeygen({ platform = runtimePlatform(), env = process.en
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
// Match complete declarations, never an affirmative substring inside a denial,
|
|
112
|
+
// question, condition, quotation, or a description of somebody else's choice.
|
|
113
|
+
export function parsePreviewConsent(value) {
|
|
114
|
+
if (typeof value !== 'string') return null;
|
|
115
|
+
let text = value.normalize('NFKC').trim();
|
|
116
|
+
text = text.replace(/^【([^【】]+)】([。.!!;;]*)$/u, '$1$2');
|
|
117
|
+
if (!text || /[??]|吗|么|嘛|是否|能否|可否|如果|假如|除非|前提|只要|也许|可能|不确定|考虑|再说|行吧|随便|或者|还是|先.*再/u.test(text)) return null;
|
|
118
|
+
const clauses = text.split(/[,,。.!!;;\n]|但是|不过|但/u).map(part => part.replace(/\s+/gu, '')).filter(Boolean);
|
|
119
|
+
const subject = '(?:我(?:们)?(?:明确)?|本人)?';
|
|
120
|
+
const actor = '(?:你们|你|支付宝|本Skill|Skill)?';
|
|
121
|
+
const action = '(?:采集|收集|记录)(?:(?:并|和|及)(?:上报|上传|使用))?';
|
|
122
|
+
const object = '(?:(?:我的|我们的|本次|当前)?(?:会话|对话|聊天)(?:信息|内容|记录|数据)?)?';
|
|
123
|
+
const allow = new RegExp(`^${subject}(?:同意|允许|授权|接受|可以)${actor}${action}${object}$`, 'u');
|
|
124
|
+
const deny = new RegExp(`^${subject}(?:不同意|不允许|不授权|不接受|不希望|不想|不愿意|不需要|不要|不用|无需|拒绝|禁止|停止|别|不)${actor}${action}${object}$`, 'u');
|
|
125
|
+
let grant = false;
|
|
126
|
+
let refusal = false;
|
|
127
|
+
let confirm = false;
|
|
128
|
+
for (const clause of clauses) {
|
|
129
|
+
const part = clause.replace(/^请/u, '');
|
|
130
|
+
if (deny.test(part) || /^(?:我)?(?:仅|只)(?:使用|用)(?:内测版Skill|内测版|服务)$/u.test(part)) refusal = true;
|
|
131
|
+
else if (allow.test(part)) grant = true;
|
|
132
|
+
else if (/^(?:我)?确认$/u.test(part)) confirm = true;
|
|
133
|
+
else if (/^(?:我)?(?:希望)?继续使用(?:内测版Skill|内测版|服务)$/u.test(part)) continue;
|
|
134
|
+
else return null;
|
|
135
|
+
}
|
|
136
|
+
if (grant && refusal) return null;
|
|
137
|
+
if (refusal) return 'DENIED';
|
|
138
|
+
return grant || confirm ? 'GRANTED' : null;
|
|
139
|
+
}
|
|
140
|
+
|
|
111
141
|
export function runPreviewCli(argv = process.argv) {
|
|
112
142
|
const action = argv[2];
|
|
113
143
|
if (action === 'consent') {
|
|
@@ -120,10 +150,10 @@ export function runPreviewCli(argv = process.argv) {
|
|
|
120
150
|
}
|
|
121
151
|
if (!args.get('--input-id')) throw new Error('preview consent 缺少 --input-id');
|
|
122
152
|
// A generic business confirmation (1/OK/继续) is not consent to collection.
|
|
123
|
-
const
|
|
124
|
-
const decision = reply === '确认' ? 'GRANTED' : reply === '仅使用服务' ? 'DENIED' : null;
|
|
153
|
+
const decision = parsePreviewConsent(args.get('--reply'));
|
|
125
154
|
if (!decision) {
|
|
126
155
|
process.stderr.write(`${internalPrefix}PREVIEW_DECISION=UNCONFIRMED\n`);
|
|
156
|
+
deliver('preview.consent.clarify');
|
|
127
157
|
return;
|
|
128
158
|
}
|
|
129
159
|
const saved = setConversationConsent({ decision, inputId: args.get('--input-id') });
|
|
@@ -7,6 +7,8 @@ import process from 'node:process';
|
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
|
|
9
9
|
import { isWindowsPlatform } from './platform_compat.mjs';
|
|
10
|
+
import { runWindowsPython } from './windows/python-execution.mjs';
|
|
11
|
+
import { findWindowsPython } from './windows/python-runtime.mjs';
|
|
10
12
|
|
|
11
13
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
12
14
|
const allowedScript = path.resolve(scriptDir, '../../integration/modules/scripts/local_402_sandbox_pay.py');
|
|
@@ -17,6 +19,7 @@ function commandRuns(command, args) {
|
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
function resolvePython() {
|
|
22
|
+
if (isWindowsPlatform()) return findWindowsPython();
|
|
20
23
|
const candidates = isWindowsPlatform()
|
|
21
24
|
? [
|
|
22
25
|
{ command: 'py', prefix: ['-3'] },
|
|
@@ -40,10 +43,12 @@ export function runPythonScriptCli(argv = process.argv) {
|
|
|
40
43
|
|
|
41
44
|
const python = resolvePython();
|
|
42
45
|
if (!python) throw new Error('未找到可用的 Python 3 运行时');
|
|
43
|
-
const result =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
const result = isWindowsPlatform()
|
|
47
|
+
? runWindowsPython(python, resolvedScript, argv.slice(3))
|
|
48
|
+
: spawnSync(python.command, [...python.prefix, resolvedScript, ...argv.slice(3)], {
|
|
49
|
+
stdio: 'inherit',
|
|
50
|
+
shell: false
|
|
51
|
+
});
|
|
47
52
|
if (result.error) throw result.error;
|
|
48
53
|
process.exitCode = Number.isInteger(result.status) ? result.status : 1;
|
|
49
54
|
}
|
|
@@ -343,8 +343,10 @@ function validateUrl(value, policy, urlKind, expected = {}) {
|
|
|
343
343
|
} catch {
|
|
344
344
|
throw new Error('officialTemporaryUrl 不是合法 URL');
|
|
345
345
|
}
|
|
346
|
-
const
|
|
347
|
-
|
|
346
|
+
const entries = policy.temporaryUrlAllowlist[urlKind];
|
|
347
|
+
const allowed = (Array.isArray(entries) ? entries : [entries]).find((entry) =>
|
|
348
|
+
entry && parsed.hostname === entry.host && parsed.pathname === entry.path);
|
|
349
|
+
if (!allowed || parsed.protocol !== 'https:' || parsed.username || parsed.password || parsed.port || parsed.hash) {
|
|
348
350
|
throw new Error(`officialTemporaryUrl 不符合 ${urlKind} allowlist`);
|
|
349
351
|
}
|
|
350
352
|
if (urlKind === 'authorization') {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { spawnSync as nativeSpawn } from 'node:child_process';
|
|
4
|
+
const { spawnSync } = process.platform === 'win32'
|
|
5
|
+
? await import('./windows/execution.mjs')
|
|
6
|
+
: { spawnSync: nativeSpawn };
|
|
4
7
|
import path from 'node:path';
|
|
5
8
|
import process from 'node:process';
|
|
6
9
|
import { fileURLToPath } from 'node:url';
|
|
@@ -8,6 +11,16 @@ import { fileURLToPath } from 'node:url';
|
|
|
8
11
|
import { applyTelemetrySession, currentRunContext, startTelemetryRun } from './telemetry.mjs';
|
|
9
12
|
import { applyAgentPlatform } from './agent_platform.mjs';
|
|
10
13
|
|
|
14
|
+
const windowsRouting = process.platform === 'win32' ? await import('./windows/routing.mjs') : null;
|
|
15
|
+
|
|
16
|
+
// A user-local Node bootstrap may not be on the host's PATH. Its bundled npm/npx
|
|
17
|
+
// and child Node commands must use the same verified runtime for this process.
|
|
18
|
+
if (process.platform === 'win32') {
|
|
19
|
+
const entry = Object.entries(process.env).find(([key]) => key.toLowerCase() === 'path');
|
|
20
|
+
for (const key of Object.keys(process.env)) if (key.toLowerCase() === 'path') delete process.env[key];
|
|
21
|
+
process.env.PATH = [path.dirname(process.execPath), entry?.[1] || ''].join(path.delimiter);
|
|
22
|
+
}
|
|
23
|
+
|
|
11
24
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
12
25
|
const referencesDir = path.resolve(scriptDir, '../..');
|
|
13
26
|
const flowDirs = Object.freeze({
|
|
@@ -41,6 +54,7 @@ const nodeEntrypoints = Object.freeze({
|
|
|
41
54
|
});
|
|
42
55
|
|
|
43
56
|
export const runtimeDomains = Object.freeze([
|
|
57
|
+
...(process.platform === 'win32' ? ['windows-entry'] : []),
|
|
44
58
|
'self-update',
|
|
45
59
|
'integration-bootstrap',
|
|
46
60
|
'onboarding-bootstrap',
|
|
@@ -56,12 +70,18 @@ function cliArgv(args) {
|
|
|
56
70
|
}
|
|
57
71
|
|
|
58
72
|
function runShell(script, cwd, args) {
|
|
73
|
+
const captureWindowsSandbox = process.platform === 'win32' && path.basename(script) === 'sandbox_config.sh';
|
|
59
74
|
const result = spawnSync('bash', [script, ...args], {
|
|
60
75
|
cwd,
|
|
61
76
|
env: process.env,
|
|
62
|
-
stdio: 'inherit',
|
|
77
|
+
stdio: captureWindowsSandbox ? 'pipe' : 'inherit',
|
|
78
|
+
...(captureWindowsSandbox ? {encoding: 'utf8'} : {}),
|
|
63
79
|
shell: false
|
|
64
80
|
});
|
|
81
|
+
if (captureWindowsSandbox) {
|
|
82
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
83
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
84
|
+
}
|
|
65
85
|
if (result.error) throw result.error;
|
|
66
86
|
process.exitCode = Number.isInteger(result.status) ? result.status : 1;
|
|
67
87
|
}
|
|
@@ -186,9 +206,9 @@ async function runOnboardingBootstrap(args) {
|
|
|
186
206
|
await startTelemetryRun(rootIntent, productType);
|
|
187
207
|
const { runOnboardingMessageCli } = await import('./onboarding_message_runner.mjs');
|
|
188
208
|
const platformEntryArgs = ['platform-entry'];
|
|
189
|
-
await runOnboardingMessageCli(cliArgv(platformEntryArgs));
|
|
209
|
+
const route = await runOnboardingMessageCli(cliArgv(platformEntryArgs));
|
|
190
210
|
const { isWindowsPlatform } = await import('./platform_compat.mjs');
|
|
191
|
-
if (isWindowsPlatform()) return;
|
|
211
|
+
if (isWindowsPlatform() && route !== 'READY') return;
|
|
192
212
|
const { runFlowCli } = await import('./flow_loader.mjs');
|
|
193
213
|
runFlowCli(cliArgv(['onboarding', 'entry']));
|
|
194
214
|
}
|
|
@@ -203,6 +223,18 @@ async function runOnboardingExecuteReady(args) {
|
|
|
203
223
|
}
|
|
204
224
|
|
|
205
225
|
async function dispatch(domain, args) {
|
|
226
|
+
if (windowsRouting) {
|
|
227
|
+
if (domain === 'windows-entry') return windowsRouting.runWindowsEntry(args);
|
|
228
|
+
if (['sandbox', 'mcc', 'auth', 'screenshots', 'special-license', 'sign', 'service', 'app',
|
|
229
|
+
'onboarding-discovery', 'onboarding-recovery', 'onboarding-execute-ready'].includes(domain)) {
|
|
230
|
+
windowsRouting.requireBusinessEnvironment();
|
|
231
|
+
}
|
|
232
|
+
if (domain === 'env' && ['prepare', 'prepare-jq', 'retry'].includes(args[0])) {
|
|
233
|
+
const { runEnvironment } = await import('./windows/prepare.mjs');
|
|
234
|
+
process.exitCode = runEnvironment(args);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
206
238
|
if (domain === 'integration-bootstrap') return runIntegrationBootstrap(args);
|
|
207
239
|
if (domain === 'onboarding-bootstrap') return runOnboardingBootstrap(args);
|
|
208
240
|
if (domain === 'onboarding-execute-ready') return runOnboardingExecuteReady(args);
|
|
@@ -223,6 +255,11 @@ async function dispatch(domain, args) {
|
|
|
223
255
|
return;
|
|
224
256
|
}
|
|
225
257
|
if (domain === 'env') {
|
|
258
|
+
if (process.platform === 'win32' && ['check-python', 'prepare-python'].includes(args[0])) {
|
|
259
|
+
const { runPythonEnvironment } = await import('./windows/python-runtime.mjs');
|
|
260
|
+
process.exitCode = await runPythonEnvironment(args);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
226
263
|
if (args[0] === 'refresh-alipay-cli') {
|
|
227
264
|
if (args.length !== 1) throw new Error('用法: runtime.mjs env refresh-alipay-cli');
|
|
228
265
|
const { runAlipayCliRefreshCli } = await import('./alipay_cli_refresh.mjs');
|
|
@@ -259,9 +296,16 @@ async function dispatch(domain, args) {
|
|
|
259
296
|
export async function runRuntimeCli(argv = process.argv) {
|
|
260
297
|
const domain = argv[2] || '';
|
|
261
298
|
try {
|
|
262
|
-
|
|
299
|
+
const received = applyTelemetrySession(applyAgentPlatform(argv.slice(3)));
|
|
300
|
+
const routed = windowsRouting ? windowsRouting.consumeRoute(received) : {args: received};
|
|
301
|
+
if (routed.route) process.env.ALIPAY_AIPAY_WINDOWS_ROUTE = routed.route;
|
|
302
|
+
await dispatch(domain, applyTelemetryContext(domain, routed.args));
|
|
263
303
|
} catch (error) {
|
|
264
|
-
|
|
304
|
+
if (windowsRouting && error.reason) {
|
|
305
|
+
const { report } = await import('./windows/preflight.mjs');
|
|
306
|
+
report(error);
|
|
307
|
+
process.stderr.write('ALIPAY_AIPAY_INTERNAL:WINDOWS_BUSINESS_BLOCKED=ENVIRONMENT_UNAVAILABLE\n');
|
|
308
|
+
} else runtimeError(domain, error);
|
|
265
309
|
process.exitCode = domain === 'self-update' ? 0 : 1;
|
|
266
310
|
}
|
|
267
311
|
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
|
|
2
|
+
const windowsProtect = process.platform === 'win32'
|
|
3
|
+
? (await import('./windows/acl.mjs')).protect
|
|
4
|
+
: null;
|
|
1
5
|
import crypto from 'node:crypto';
|
|
2
6
|
import fs from 'node:fs';
|
|
3
7
|
import https from 'node:https';
|
|
@@ -364,6 +368,11 @@ export function telemetryStoragePaths(env = process.env) {
|
|
|
364
368
|
}
|
|
365
369
|
|
|
366
370
|
function ensurePrivateDir(dir) {
|
|
371
|
+
if (windowsProtect) {
|
|
372
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
373
|
+
windowsProtect(dir);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
367
376
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
368
377
|
try {
|
|
369
378
|
fs.chmodSync(dir, 0o700);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {spawnSync} from 'node:child_process';
|
|
4
|
+
|
|
5
|
+
export function fail(reason, dependency = '') { throw Object.assign(new Error(reason), {reason, dependency}); }
|
|
6
|
+
export function run(command,args,options={}) {
|
|
7
|
+
return spawnSync(command,args,{encoding:'utf8',windowsHide:true,shell:false,timeout:15000,maxBuffer:4*1024*1024,...options});
|
|
8
|
+
}
|
|
9
|
+
export function powershell() {
|
|
10
|
+
const root=process.env.SystemRoot || 'C:\\Windows';
|
|
11
|
+
const command=path.join(root,'System32/WindowsPowerShell/v1.0/powershell.exe');
|
|
12
|
+
if(!fs.existsSync(command)) fail('DEPENDENCY_MISSING','PowerShell');
|
|
13
|
+
return command;
|
|
14
|
+
}
|
|
15
|
+
export function psEnv(env=process.env) {
|
|
16
|
+
return Object.fromEntries(Object.entries(env).filter(([k])=>k.toLowerCase()!=='psmodulepath'));
|
|
17
|
+
}
|
|
18
|
+
export function protect(target) {
|
|
19
|
+
if(fs.lstatSync(target).isSymbolicLink()||path.resolve(target)===path.parse(target).root)fail('ACL_FAILED','unsafe target');
|
|
20
|
+
const system=path.join(process.env.SystemRoot||'C:/Windows','System32');
|
|
21
|
+
const who=run(path.join(system,'whoami.exe'),['/user','/fo','csv','/nh']);
|
|
22
|
+
const sid=who.stdout?.match(/S-1-5-[0-9-]+/)?.[0];if(who.status!==0||!sid)fail('ACL_FAILED');
|
|
23
|
+
const env={...psEnv(),ALIPAY_WINDOWS_ACL_TARGET:path.resolve(target)};
|
|
24
|
+
// Native icacls changes permissions; PowerShell only reads ACL metadata.
|
|
25
|
+
// No .ps1 execution, ExecutionPolicy override, elevation, or policy mutation.
|
|
26
|
+
const inspect=()=>{
|
|
27
|
+
const r=run(powershell(),['-NoProfile','-NonInteractive','-Command',
|
|
28
|
+
'$ErrorActionPreference="Stop"; $p=$env:ALIPAY_WINDOWS_ACL_TARGET; $d=New-Object IO.DriveInfo([IO.Path]::GetPathRoot($p)); if($d.DriveFormat -ne "NTFS"){exit 2}; $a=Get-Acl -LiteralPath $p; @{protected=$a.AreAccessRulesProtected;rules=@($a.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier]) | ForEach-Object { @{sid=$_.IdentityReference.Value;type=[string]$_.AccessControlType;rights=[int]$_.FileSystemRights} })} | ConvertTo-Json -Depth 4 -Compress'],{env});
|
|
29
|
+
if(r.status!==0)fail('ACL_FAILED','NTFS');try{return JSON.parse(r.stdout);}catch{fail('ACL_FAILED');}
|
|
30
|
+
};
|
|
31
|
+
const allowed=[sid,'S-1-5-18','S-1-5-32-544'];inspect();
|
|
32
|
+
const inherited=fs.statSync(target).isDirectory()?'(OI)(CI)F':'F';
|
|
33
|
+
const args=[target,'/inheritance:r','/grant:r',...allowed.map(s=>'*'+s+':'+inherited)];
|
|
34
|
+
if(run(path.join(system,'icacls.exe'),args).status!==0)fail('ACL_FAILED','grant');
|
|
35
|
+
const remove=[...new Set(inspect().rules.filter(r=>!allowed.includes(r.sid)||r.type!=='Allow').map(r=>r.sid))];
|
|
36
|
+
for(const s of remove)if(run(path.join(system,'icacls.exe'),[target,'/remove','*'+s]).status!==0)fail('ACL_FAILED','remove');
|
|
37
|
+
// Reapply after removing any explicit deny ACE for an allowed identity.
|
|
38
|
+
if(remove.length&&run(path.join(system,'icacls.exe'),args).status!==0)fail('ACL_FAILED','regrant');
|
|
39
|
+
const after=inspect();
|
|
40
|
+
if(!after.protected||after.rules.some(r=>!allowed.includes(r.sid)||r.type!=='Allow')||!after.rules.some(r=>r.sid===sid&&(r.rights&2032127)===2032127))fail('ACL_FAILED','readback');
|
|
41
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
param([ValidateSet('check', 'prepare', 'repair')][string]$Mode = 'check')
|
|
2
|
+
|
|
3
|
+
# The only pre-Node entry. No runtime/business dispatch, telemetry, elevation,
|
|
4
|
+
# ExecutionPolicy override, or persistent PATH change belongs in this script.
|
|
5
|
+
function Invoke-NodeProbe([string]$Executable) {
|
|
6
|
+
if (-not (Test-Path -LiteralPath $Executable -PathType Leaf)) { return $null }
|
|
7
|
+
$process = New-Object System.Diagnostics.Process
|
|
8
|
+
try {
|
|
9
|
+
$process.StartInfo.FileName = $Executable
|
|
10
|
+
$process.StartInfo.Arguments = '-p "JSON.stringify({version:process.versions.node,platform:process.platform,arch:process.arch,executable:process.execPath})"'
|
|
11
|
+
$process.StartInfo.UseShellExecute = $false
|
|
12
|
+
$process.StartInfo.CreateNoWindow = $true
|
|
13
|
+
$process.StartInfo.RedirectStandardOutput = $true
|
|
14
|
+
$process.StartInfo.RedirectStandardError = $true
|
|
15
|
+
if (-not $process.Start()) { return $null }
|
|
16
|
+
$stdout = $process.StandardOutput.ReadToEndAsync()
|
|
17
|
+
$stderr = $process.StandardError.ReadToEndAsync()
|
|
18
|
+
if (-not $process.WaitForExit(5000)) { $process.Kill(); return $null }
|
|
19
|
+
if ($process.ExitCode -ne 0) { return $null }
|
|
20
|
+
$value = $stdout.Result | ConvertFrom-Json -ErrorAction Stop
|
|
21
|
+
if ($value.platform -ne 'win32' -or $value.arch -ne 'x64' -or
|
|
22
|
+
$value.version -notmatch '^([0-9]+)\.[0-9]+\.[0-9]+$' -or [int]$Matches[1] -lt 18 -or
|
|
23
|
+
-not [IO.Path]::IsPathRooted($value.executable)) { return $null }
|
|
24
|
+
return $value
|
|
25
|
+
} catch { return $null } finally { $process.Dispose() }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function Find-LocalNode([string]$Root) {
|
|
29
|
+
$candidates = @()
|
|
30
|
+
$candidates += @(Get-Command node.exe -CommandType Application -All -ErrorAction SilentlyContinue | ForEach-Object { $_.Source })
|
|
31
|
+
foreach ($directory in @($env:ProgramFiles, $env:LOCALAPPDATA)) {
|
|
32
|
+
if ($directory) {
|
|
33
|
+
$candidates += (Join-Path $directory 'nodejs/node.exe')
|
|
34
|
+
$candidates += (Join-Path $directory 'Programs/nodejs/node.exe')
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if ($env:NVM_SYMLINK) { $candidates += (Join-Path $env:NVM_SYMLINK 'node.exe') }
|
|
38
|
+
if (Test-Path -LiteralPath $Root -PathType Container) {
|
|
39
|
+
$candidates += @(Get-ChildItem -LiteralPath $Root -Directory -Filter 'node-*' -ErrorAction SilentlyContinue |
|
|
40
|
+
ForEach-Object { Get-ChildItem -LiteralPath $_.FullName -Directory -Filter 'node-v*-win-x64' -ErrorAction SilentlyContinue } |
|
|
41
|
+
ForEach-Object { Join-Path $_.FullName 'node.exe' })
|
|
42
|
+
}
|
|
43
|
+
foreach ($candidate in @($candidates | Select-Object -Unique)) {
|
|
44
|
+
$found = Invoke-NodeProbe $candidate
|
|
45
|
+
if ($null -ne $found) { return $found }
|
|
46
|
+
}
|
|
47
|
+
return $null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function Receive-NodeFile([string]$Uri, [string]$Destination) {
|
|
51
|
+
# URLs are constructed only from the official index and a validated version.
|
|
52
|
+
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
53
|
+
$ProgressPreference = 'SilentlyContinue'
|
|
54
|
+
try { Invoke-WebRequest -UseBasicParsing -Uri $Uri -OutFile $Destination -TimeoutSec 60 -ErrorAction Stop | Out-Null }
|
|
55
|
+
catch { throw 'DOWNLOAD_FAILED' }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function Install-UserNode([string]$Root) {
|
|
59
|
+
$staging = Join-Path $Root ('node-' + [Guid]::NewGuid().ToString('N'))
|
|
60
|
+
New-Item -ItemType Directory -Path $staging -ErrorAction Stop | Out-Null
|
|
61
|
+
$complete = $false
|
|
62
|
+
try {
|
|
63
|
+
$indexFile = Join-Path $staging 'index.json'
|
|
64
|
+
Receive-NodeFile 'https://nodejs.org/dist/index.json' $indexFile
|
|
65
|
+
$releases = @(Get-Content -LiteralPath $indexFile -Raw | ConvertFrom-Json)
|
|
66
|
+
$release = $releases | Where-Object {
|
|
67
|
+
$_.lts -is [string] -and $_.lts.Length -gt 0 -and $_.version -match '^v[0-9]+\.[0-9]+\.[0-9]+$' -and
|
|
68
|
+
[version]$_.version.Substring(1) -ge [version]'18.0.0' -and $_.files -contains 'win-x64-zip'
|
|
69
|
+
} | Sort-Object { [version]$_.version.Substring(1) } -Descending | Select-Object -First 1
|
|
70
|
+
if ($null -eq $release) { throw 'NO_LTS_ASSET' }
|
|
71
|
+
$name = 'node-' + $release.version + '-win-x64'
|
|
72
|
+
$zipName = $name + '.zip'
|
|
73
|
+
$base = 'https://nodejs.org/dist/' + $release.version + '/'
|
|
74
|
+
$checksums = Join-Path $staging 'SHASUMS256.txt'
|
|
75
|
+
Receive-NodeFile ($base + 'SHASUMS256.txt') $checksums
|
|
76
|
+
$pattern = '^([0-9a-fA-F]{64})\s+' + [regex]::Escape($zipName) + '$'
|
|
77
|
+
$lines = @(Get-Content -LiteralPath $checksums | Where-Object { $_ -match $pattern })
|
|
78
|
+
if ($lines.Count -ne 1 -or $lines[0] -notmatch $pattern) { throw 'INTEGRITY_FAILED' }
|
|
79
|
+
$expected = $Matches[1]
|
|
80
|
+
$archive = Join-Path $staging $zipName
|
|
81
|
+
Receive-NodeFile ($base + $zipName) $archive
|
|
82
|
+
if ((Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash -ine $expected) { throw 'INTEGRITY_FAILED' }
|
|
83
|
+
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
84
|
+
[IO.Compression.ZipFile]::ExtractToDirectory($archive, $staging)
|
|
85
|
+
$node = Invoke-NodeProbe (Join-Path $staging ($name + '/node.exe'))
|
|
86
|
+
if ($null -eq $node -or $node.version -ne $release.version.Substring(1)) { throw 'VERIFY_FAILED' }
|
|
87
|
+
Remove-Item -LiteralPath $indexFile, $checksums, $archive -Force
|
|
88
|
+
$complete = $true
|
|
89
|
+
return $node
|
|
90
|
+
} finally {
|
|
91
|
+
if (-not $complete) { Remove-Item -LiteralPath $staging -Recurse -Force -ErrorAction SilentlyContinue }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function Invoke-NodeBootstrap([string]$Action, [string]$Root) {
|
|
96
|
+
$found = Find-LocalNode $Root
|
|
97
|
+
if ($null -ne $found) { return @{status='READY'; nodePath=$found.executable; version=$found.version} }
|
|
98
|
+
if ($Action -eq 'check') { return @{status='NEEDS_SETUP'; reason='NODE_MISSING_OR_UNSUPPORTED'} }
|
|
99
|
+
New-Item -ItemType Directory -Path $Root -Force -ErrorAction Stop | Out-Null
|
|
100
|
+
if ((Get-Item -LiteralPath $Root).Attributes -band [IO.FileAttributes]::ReparsePoint) { throw 'UNSAFE_DIRECTORY' }
|
|
101
|
+
$lock = $null
|
|
102
|
+
try {
|
|
103
|
+
try { $lock = [IO.File]::Open((Join-Path $Root 'node.lock'), 'OpenOrCreate', 'ReadWrite', 'None') }
|
|
104
|
+
catch { throw 'ENVIRONMENT_BUSY' }
|
|
105
|
+
$stateFile = Join-Path $Root 'node-environment.json'
|
|
106
|
+
$state = @{prepareAttempts=0; repairAttempts=0}
|
|
107
|
+
if (Test-Path -LiteralPath $stateFile) {
|
|
108
|
+
try {
|
|
109
|
+
$loaded = Get-Content -LiteralPath $stateFile -Raw | ConvertFrom-Json
|
|
110
|
+
foreach ($key in @('prepareAttempts', 'repairAttempts')) {
|
|
111
|
+
if ($loaded.$key -isnot [int] -and $loaded.$key -isnot [long]) { throw 'invalid' }
|
|
112
|
+
if ($loaded.$key -lt 0) { throw 'invalid' }
|
|
113
|
+
$state[$key] = $loaded.$key
|
|
114
|
+
}
|
|
115
|
+
} catch { throw 'ENVIRONMENT_STATE_INVALID' }
|
|
116
|
+
}
|
|
117
|
+
if (($Action -eq 'repair' -and $state.repairAttempts -ge 1) -or
|
|
118
|
+
($Action -eq 'prepare' -and ($state.prepareAttempts -ge 1 -or $state.repairAttempts -ge 1))) { throw 'RETRY_BUDGET_EXHAUSTED' }
|
|
119
|
+
$state.prepareAttempts++
|
|
120
|
+
if ($Action -eq 'repair') { $state.repairAttempts++ }
|
|
121
|
+
[IO.File]::WriteAllText($stateFile, ($state | ConvertTo-Json -Compress))
|
|
122
|
+
$found = Install-UserNode $Root
|
|
123
|
+
return @{status='READY'; nodePath=$found.executable; version=$found.version}
|
|
124
|
+
} finally { if ($null -ne $lock) { $lock.Dispose() } }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if ($MyInvocation.InvocationName -ne '.') {
|
|
128
|
+
$ErrorActionPreference = 'Stop'
|
|
129
|
+
try {
|
|
130
|
+
$architecture = if ($env:PROCESSOR_ARCHITEW6432) { $env:PROCESSOR_ARCHITEW6432 } else { $env:PROCESSOR_ARCHITECTURE }
|
|
131
|
+
if ($env:OS -ne 'Windows_NT' -or $architecture -ne 'AMD64' -or -not $env:LOCALAPPDATA) { throw 'UNSUPPORTED_PLATFORM' }
|
|
132
|
+
$result = Invoke-NodeBootstrap $Mode (Join-Path $env:LOCALAPPDATA 'alipay-aipay-pre/bootstrap')
|
|
133
|
+
} catch {
|
|
134
|
+
$reason = [string]$_.Exception.Message
|
|
135
|
+
if ($reason -notmatch '^(DOWNLOAD_FAILED|NO_LTS_ASSET|INTEGRITY_FAILED|VERIFY_FAILED|UNSAFE_DIRECTORY|ENVIRONMENT_BUSY|ENVIRONMENT_STATE_INVALID|RETRY_BUDGET_EXHAUSTED|UNSUPPORTED_PLATFORM)$') { $reason = 'SETUP_FAILED' }
|
|
136
|
+
$result = @{status='BLOCKED'; reason=$reason}
|
|
137
|
+
}
|
|
138
|
+
[Console]::Error.WriteLine('ALIPAY_AIPAY_INTERNAL:NODE_BOOTSTRAP=' + ($result | ConvertTo-Json -Compress))
|
|
139
|
+
if ($result.status -ne 'READY') { exit 78 }
|
|
140
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import {spawnSync} from 'node:child_process';
|
|
4
|
+
|
|
5
|
+
export const installCommand = String.raw`
|
|
6
|
+
$ErrorActionPreference = 'Stop'
|
|
7
|
+
$env:ALIPAY_CLI_BIN = "$HOME\.local\bin"
|
|
8
|
+
$env:ALIPAY_CLI_SKIP_VERIFY = 'true'
|
|
9
|
+
try {
|
|
10
|
+
Invoke-RestMethod -UseBasicParsing -Uri 'https://opengw.alipay.com/alipaycli/install_ps1' -TimeoutSec 20 -ErrorAction Stop | Invoke-Expression
|
|
11
|
+
if ($global:LASTEXITCODE -ne 0) { exit 1 }
|
|
12
|
+
} catch {
|
|
13
|
+
$code = $_.Exception.Data['InstallerExitCode']
|
|
14
|
+
if ($code -in @(3, 4, 5)) { exit ([int]$code) }
|
|
15
|
+
if ($_.Exception -is [System.Net.WebException]) { exit 3 }
|
|
16
|
+
if ($_.Exception -is [System.UnauthorizedAccessException]) { exit 4 }
|
|
17
|
+
exit 1
|
|
18
|
+
}
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
export function refreshWindowsCli({env = process.env, home = os.homedir(), run = spawnSync} = {}) {
|
|
22
|
+
const childEnv = {...env};
|
|
23
|
+
// Keep business context and installer overrides out of the official install.
|
|
24
|
+
for (const key of Object.keys(childEnv)) {
|
|
25
|
+
if (['PLATFORM', 'PLATFORM_ID', 'PRODUCT', 'ALIPAYCLI_BIZ_INFO', 'PSMODULEPATH',
|
|
26
|
+
'ALIPAY_CLI_BIN', 'ALIPAY_CLI_SKIP_VERIFY', 'ALIPAY_CLI_VERSION',
|
|
27
|
+
'ALIPAY_CLI_CHANNEL', 'ALIPAY_CLI_MANIFEST_URL',
|
|
28
|
+
'ALIPAY_CLI_AFTS_MANIFEST_BASE'].includes(key.toUpperCase())) delete childEnv[key];
|
|
29
|
+
}
|
|
30
|
+
const command = path.win32.join(env.SystemRoot || 'C:\\Windows', 'System32/WindowsPowerShell/v1.0/powershell.exe');
|
|
31
|
+
const options = {env: childEnv, encoding: 'utf8', shell: false, windowsHide: true,
|
|
32
|
+
maxBuffer: 30 * 1024 * 1024, timeout: 60000};
|
|
33
|
+
try {
|
|
34
|
+
const result = run(command, ['-NoProfile', '-NonInteractive', '-Command', installCommand], options);
|
|
35
|
+
const output = `${result.stdout || ''}\n${result.stderr || ''}\n${result.error?.message || ''}`;
|
|
36
|
+
if (result.error || result.status !== 0) {
|
|
37
|
+
if (result.status === 3) return 'RETRY_WITH_NETWORK';
|
|
38
|
+
if (result.status === 4) return 'RETRY_WITH_LOCAL_FS_PERMISSION';
|
|
39
|
+
if (result.status === 5) return 'VERIFY_FAILED';
|
|
40
|
+
if (result.error?.code === 'ETIMEDOUT' ||
|
|
41
|
+
/could not resolve|failed to connect|connection (?:refused|reset)|timed? out|timeout|network|socket|tls|ssl|ENOTFOUND|ECONNREFUSED|EAI_AGAIN/iu.test(output)) return 'RETRY_WITH_NETWORK';
|
|
42
|
+
if (/permission denied|operation not permitted|EACCES|EPERM|access denied/iu.test(output)) return 'RETRY_WITH_LOCAL_FS_PERMISSION';
|
|
43
|
+
return 'FAILED';
|
|
44
|
+
}
|
|
45
|
+
const verify = run(path.join(home, '.local', 'bin', 'alipay-cli.exe'), ['version'],
|
|
46
|
+
{...options, stdio: 'ignore', timeout: 5000});
|
|
47
|
+
return !verify.error && verify.status === 0 ? 'READY' : 'VERIFY_FAILED';
|
|
48
|
+
} catch {
|
|
49
|
+
return 'FAILED';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {spawnSync} from 'node:child_process';
|
|
2
|
+
import {ensureReady} from './preflight.mjs';
|
|
3
|
+
try {
|
|
4
|
+
const {cli,env}=ensureReady();
|
|
5
|
+
const args=process.argv.slice(2);
|
|
6
|
+
if(args[0]==='file'&&args[1]==='upload'&&/^\/[a-zA-Z]\//.test(args[2]||''))args[2]=args[2][1]+':'+args[2].slice(2);
|
|
7
|
+
if(args.reduce((n,a)=>n+a.length+3,0)>28000)throw new Error('COMMAND_LINE_TOO_LONG');
|
|
8
|
+
const r=spawnSync(cli,args,{env,stdio:'inherit',windowsHide:true,shell:false,timeout:60000});
|
|
9
|
+
if(r.error)process.stderr.write('ALIPAY_AIPAY_INTERNAL:WINDOWS_CLI_ERROR='+r.error.code+'; result may be unknown; do not replay writes\n');
|
|
10
|
+
process.exitCode=r.status??124;
|
|
11
|
+
}catch(e){process.stderr.write('ALIPAY_AIPAY_INTERNAL:WINDOWS_CLI_ERROR='+e.message+'\n');process.exitCode=78;}
|