@alipay/alipay-aipay 1.7.3 → 1.7.5
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/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 +227 -0
- package/skill/references/normal/scripts/runtime.mjs +3 -1
- package/skill-pre/SKILL.md +23 -3
- 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 +2 -2
- package/skill-pre/references/normal/full-process-routing.md +2 -2
- 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 +17 -2
- 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/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 +89 -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 +86 -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/scripts/windows/winget.mjs +13 -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
|
@@ -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;
|
|
@@ -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;}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import {spawn as nativeSpawn,spawnSync as nativeSpawnSync} from 'node:child_process';
|
|
3
|
+
import {ensureReady,report,fail} from './preflight.mjs';
|
|
4
|
+
|
|
5
|
+
export function posix(p) {
|
|
6
|
+
const value=p.replaceAll('\\','/');
|
|
7
|
+
if(/^[a-zA-Z]:\//.test(value)) return '/'+value[0].toLowerCase()+value.slice(2);
|
|
8
|
+
if(value.startsWith('//')) fail('UNC_PATH_NOT_IMPLEMENTED');
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
export function shellArguments(script,args) {
|
|
12
|
+
const a=[...args],base=path.win32.basename(script);
|
|
13
|
+
const positions=base==='sandbox_config.sh'?[a[0]==='summary'?2:1]:
|
|
14
|
+
base==='upload_screenshots.sh'?[2,3,4]:
|
|
15
|
+
base==='project_inspector.sh'?(a[0]==='prepare-new'?[1]:a[0]==='scan'?[2]:[]):[];
|
|
16
|
+
for(const i of positions) if(a[i])a[i]=posix(a[i]);
|
|
17
|
+
const flags=new Set(['--project-path','--config-path','--file','--home-img','--shop-img','--pay-img','--key-file']);
|
|
18
|
+
for(let i=0;i<a.length-1;i++)if(flags.has(a[i]))a[i+1]=posix(a[++i]);
|
|
19
|
+
return [posix(script),...a];
|
|
20
|
+
}
|
|
21
|
+
function invocation(command,args,options) {
|
|
22
|
+
if(command!=='bash') return {command,args,options};
|
|
23
|
+
const c=ensureReady();
|
|
24
|
+
// The caller's business environment wins except adapter-owned execution values.
|
|
25
|
+
const env={...c.env,...(options.env||{})};
|
|
26
|
+
for(const k of ['PATH','TEMP','TMP','TMPDIR','NODE_BINARY','MSYS_NO_PATHCONV','MSYS2_ARG_CONV_EXCL'])env[k]=c.env[k];
|
|
27
|
+
for(const k of Object.keys(c.env))if(k.startsWith('ALIPAY_WINDOWS_'))env[k]=c.env[k];
|
|
28
|
+
for(const k of Object.keys(env))if(k.toLowerCase()==='path'&&k!=='PATH')delete env[k];
|
|
29
|
+
delete env.BASH_ENV;delete env.ENV;
|
|
30
|
+
const mapped=shellArguments(args[0],args.slice(1));
|
|
31
|
+
if(mapped.reduce((n,s)=>n+String(s).length+3,0)>28000)fail('COMMAND_LINE_TOO_LONG');
|
|
32
|
+
return {command:c.bash,args:['--noprofile','--norc',...mapped],options:{...options,env,shell:false,windowsHide:true,timeout:options.timeout||180000,maxBuffer:options.maxBuffer||16*1024*1024}};
|
|
33
|
+
}
|
|
34
|
+
export function nativeSandboxMarkers(stderr) {
|
|
35
|
+
return String(stderr || '').replace(/^(ALIPAY_AIPAY_INTERNAL:SANDBOX_(?:CONFIG|PENDING)_PATH=)\/([a-zA-Z])\/(.*)$/gm,
|
|
36
|
+
(_, prefix, drive, rest) => `${prefix}${drive.toUpperCase()}:/${rest}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function spawnSync(command,args,options={}) {
|
|
40
|
+
const i=invocation(command,args,options);
|
|
41
|
+
const r=nativeSpawnSync(i.command,i.args,i.options);
|
|
42
|
+
if(command === 'bash' && path.win32.basename(args[0]) === 'sandbox_config.sh' && typeof r.stderr === 'string') {
|
|
43
|
+
r.stderr = nativeSandboxMarkers(r.stderr);
|
|
44
|
+
}
|
|
45
|
+
if(r.error) { report({reason:r.error.code==='ETIMEDOUT'?'EXECUTION_TIMEOUT':'EXECUTION_FAILED'}); }
|
|
46
|
+
return r;
|
|
47
|
+
}
|
|
48
|
+
export function spawn(command,args,options={}) {
|
|
49
|
+
const i=invocation(command,args,options);
|
|
50
|
+
return nativeSpawn(i.command,i.args,i.options);
|
|
51
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {run, fail} from './acl.mjs';
|
|
4
|
+
|
|
5
|
+
export function gitCandidates(env = process.env) {
|
|
6
|
+
if (env.ALIPAY_GIT_BASH) return [env.ALIPAY_GIT_BASH];
|
|
7
|
+
const candidates = [
|
|
8
|
+
path.win32.join(env.ProgramFiles || 'C:/Program Files', 'Git/bin/bash.exe'),
|
|
9
|
+
...(env.LOCALAPPDATA ? [path.win32.join(env.LOCALAPPDATA, 'Programs/Git/bin/bash.exe')] : [])
|
|
10
|
+
];
|
|
11
|
+
const search = Object.entries(env).find(([key]) => key.toLowerCase() === 'path')?.[1] || '';
|
|
12
|
+
for (const directory of search.split(';').filter(p => path.win32.isAbsolute(p))) {
|
|
13
|
+
candidates.push(path.win32.join(directory, 'bash.exe'));
|
|
14
|
+
if (['cmd', 'bin'].includes(path.win32.basename(directory).toLowerCase())) {
|
|
15
|
+
candidates.push(path.win32.join(directory, '../bin/bash.exe'));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return [...new Set(candidates.map(p => path.win32.normalize(p)))];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function findGitBash({env = process.env, exists = fs.existsSync, execute = run} = {}) {
|
|
22
|
+
const cleanEnv = {...env};
|
|
23
|
+
delete cleanEnv.BASH_ENV; delete cleanEnv.ENV; delete cleanEnv.SHELLOPTS;
|
|
24
|
+
for (const candidate of gitCandidates(env)) {
|
|
25
|
+
if (!path.win32.isAbsolute(candidate) || !exists(candidate)) continue;
|
|
26
|
+
// Require a Git for Windows layout before running bash. System32/WSL bash
|
|
27
|
+
// and unrelated MSYS installations are not candidates for this adapter.
|
|
28
|
+
const directory = path.win32.dirname(candidate);
|
|
29
|
+
const git = path.win32.basename(path.win32.dirname(directory)).toLowerCase() === 'usr'
|
|
30
|
+
? path.win32.resolve(directory, '../..') : path.win32.resolve(directory, '..');
|
|
31
|
+
const bash = path.win32.join(git, 'bin/bash.exe');
|
|
32
|
+
const gitExe = path.win32.join(git, 'cmd/git.exe');
|
|
33
|
+
if (![bash, gitExe, path.win32.join(git, 'usr/bin/cygpath.exe')].every(exists)) continue;
|
|
34
|
+
const version = execute(gitExe, ['--version'], {env: cleanEnv, timeout: 5000});
|
|
35
|
+
if (version.status !== 0 || !/^git version \d[^\r\n]*\.windows\.\d+/m.test(version.stdout || '')) continue;
|
|
36
|
+
const probe = execute(bash, ['--noprofile', '--norc', '-c',
|
|
37
|
+
'case "$(uname -s)" in MINGW*|MSYS*) exit 0;; *) exit 1;; esac'], {env: cleanEnv, timeout: 5000});
|
|
38
|
+
if (probe.status === 0) return {bash, git};
|
|
39
|
+
}
|
|
40
|
+
if (env.ALIPAY_GIT_BASH) fail('NOT_GIT_BASH', 'ALIPAY_GIT_BASH');
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"validationCoverage": [],
|
|
4
|
+
"osVersionAllowlistRequired": false,
|
|
5
|
+
"automaticSetupRetries": 0,
|
|
6
|
+
"explicitRepairRetries": 1,
|
|
7
|
+
"nodeMinimumMajor": 18,
|
|
8
|
+
"jq": {
|
|
9
|
+
"version": "1.7.1",
|
|
10
|
+
"sha256": "7451fbbf37feffb9bf262bd97c54f0da558c63f0748e64152dd87b0a07b6d6ab",
|
|
11
|
+
"url": "https://github.com/jqlang/jq/releases/download/jq-1.7.1/jq-windows-amd64.exe"
|
|
12
|
+
}
|
|
13
|
+
}
|