@alipay/alipay-aipay 1.7.3 → 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/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 +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 +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/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
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
import {fileURLToPath} from 'node:url';
|
|
6
|
+
|
|
7
|
+
export const directory = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
export const policy = JSON.parse(fs.readFileSync(path.join(directory,'policy.json'),'utf8'));
|
|
9
|
+
const slash = p => p.replaceAll('\\','/');
|
|
10
|
+
let invocationContext;
|
|
11
|
+
import {fail, run, powershell, psEnv, protect} from './acl.mjs';
|
|
12
|
+
export {fail, run, protect} from './acl.mjs';
|
|
13
|
+
import {telemetryStoragePaths} from '../telemetry.mjs';
|
|
14
|
+
import {findGitBash} from './git-discovery.mjs';
|
|
15
|
+
|
|
16
|
+
export function stateRoot() {
|
|
17
|
+
const root=path.join(process.env.LOCALAPPDATA || path.join(os.homedir(),'AppData/Local'),'alipay-aipay-pre/windows');
|
|
18
|
+
if(fs.existsSync(root)&&fs.lstatSync(root).isSymbolicLink()) fail('STORAGE_FAILED');
|
|
19
|
+
fs.mkdirSync(root,{recursive:true}); protect(root); return root;
|
|
20
|
+
}
|
|
21
|
+
export function readState(root) {
|
|
22
|
+
const file=path.join(root,'environment.json');
|
|
23
|
+
if(!fs.existsSync(file)) return {schemaVersion:1,repairAttempts:0,prepareAttempts:0};
|
|
24
|
+
try {
|
|
25
|
+
const s=JSON.parse(fs.readFileSync(file,'utf8'));
|
|
26
|
+
if(s.schemaVersion!==1||!Number.isInteger(s.repairAttempts)||!Number.isInteger(s.prepareAttempts)) throw new Error();
|
|
27
|
+
return s;
|
|
28
|
+
} catch { fail('ENVIRONMENT_STATE_INVALID'); }
|
|
29
|
+
}
|
|
30
|
+
export function saveState(root,s) {
|
|
31
|
+
const temporary=path.join(root,'environment-'+crypto.randomUUID()+'.tmp');
|
|
32
|
+
fs.writeFileSync(temporary,JSON.stringify(s),{flag:'wx'});
|
|
33
|
+
fs.renameSync(temporary,path.join(root,'environment.json'));
|
|
34
|
+
}
|
|
35
|
+
export function withLock(root,fn) {
|
|
36
|
+
const lock=path.join(root,'environment.lock'); let fd;
|
|
37
|
+
// Concurrent discovery children briefly update the same state. Wait for that
|
|
38
|
+
// short critical section, never replay a check/install/business operation.
|
|
39
|
+
const deadline=Date.now()+2000;
|
|
40
|
+
while(fd===undefined) {
|
|
41
|
+
try { fd=fs.openSync(lock,'wx'); }
|
|
42
|
+
catch(e) {
|
|
43
|
+
if(e.code!=='EEXIST'||Date.now()>=deadline)fail('ENVIRONMENT_BUSY');
|
|
44
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,25);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
try { return fn(); } finally { fs.closeSync(fd); fs.unlinkSync(lock); }
|
|
48
|
+
}
|
|
49
|
+
// Match the existing environment policy: reuse a working jq; the managed
|
|
50
|
+
// installer is version-pinned only when jq is absent or unusable.
|
|
51
|
+
export function locateJq() {
|
|
52
|
+
const systemPath=Object.entries(process.env).find(([k])=>k.toLowerCase()==='path')?.[1]||'';
|
|
53
|
+
const candidates=[path.join(os.homedir(),'.local/bin/jq.exe'),
|
|
54
|
+
...systemPath.split(path.delimiter).filter(p=>path.isAbsolute(p)).map(p=>path.join(p,'jq.exe'))];
|
|
55
|
+
return candidates.find(file=>fs.existsSync(file) &&
|
|
56
|
+
run(file,['--version']).status===0 &&
|
|
57
|
+
run(file,['-n','-e','--arg','value','ok','$value == "ok"']).status===0);
|
|
58
|
+
}
|
|
59
|
+
export function locate() {
|
|
60
|
+
if(process.platform!=='win32') fail('WINDOWS_ADAPTER_REQUIRES_WINDOWS');
|
|
61
|
+
if(Number(process.versions.node.split('.')[0])<policy.nodeMinimumMajor) fail('DEPENDENCY_VERSION','Node');
|
|
62
|
+
// Only a native x64 execution mapping has been implemented. No OS build allowlist.
|
|
63
|
+
const arch=(process.env.PROCESSOR_ARCHITEW6432||process.env.PROCESSOR_ARCHITECTURE||'').toUpperCase();
|
|
64
|
+
if(process.arch!=='x64'||arch!=='AMD64') fail('NO_COMPATIBLE_EXECUTION_MAPPING',arch||'unknown');
|
|
65
|
+
const foundGit=findGitBash();
|
|
66
|
+
if(!foundGit) fail('DEPENDENCY_MISSING','Git Bash');
|
|
67
|
+
const {bash,git}=foundGit;
|
|
68
|
+
const bin=path.join(os.homedir(),'.local/bin');
|
|
69
|
+
const cli=path.join(bin,'alipay-cli.exe'),jq=locateJq();
|
|
70
|
+
if(!jq) fail('DEPENDENCY_MISSING','jq');
|
|
71
|
+
if(!fs.existsSync(cli)) fail('DEPENDENCY_MISSING','alipay-cli');
|
|
72
|
+
const env={...process.env};
|
|
73
|
+
const oldPath=Object.entries(env).find(([k])=>k.toLowerCase()==='path')?.[1]||'';
|
|
74
|
+
for(const k of Object.keys(env)) if(k.toLowerCase()==='path') delete env[k];
|
|
75
|
+
env.PATH=[path.join(directory,'bin'),path.dirname(bash),path.join(git,'usr/bin'),path.dirname(process.execPath),bin,oldPath].join(path.delimiter);
|
|
76
|
+
delete env.ALIPAY_AIPAY_TEST_PLATFORM; delete env.ALIPAY_AIPAY_TEST_MODE;
|
|
77
|
+
delete env.BASH_ENV; delete env.ENV; delete env.SHELLOPTS;
|
|
78
|
+
env.MSYS_NO_PATHCONV='1';env.MSYS2_ARG_CONV_EXCL='*';
|
|
79
|
+
env.ALIPAY_WINDOWS_CLI_RUNNER=slash(path.join(directory,'cli-process.mjs'));
|
|
80
|
+
env.ALIPAY_WINDOWS_NODE=slash(process.execPath);
|
|
81
|
+
env.ALIPAY_WINDOWS_JQ=slash(jq);
|
|
82
|
+
env.ALIPAY_WINDOWS_CLI=slash(cli);
|
|
83
|
+
env.ALIPAY_WINDOWS_CREDENTIALS_DIR=slash(path.join(os.homedir(),'.alipay-cli/credentials'));
|
|
84
|
+
env.ALIPAY_WINDOWS_LOGS_DIR=slash(path.join(process.env.APPDATA||path.join(os.homedir(),'AppData/Roaming'),'alipay-cli/data/logs'));
|
|
85
|
+
env.ALIPAY_WINDOWS_CLI_ENTRY=slash(path.join(directory,'bin/alipay-cli'));
|
|
86
|
+
env.ALIPAY_WINDOWS_ACL_SCRIPT=slash(path.join(directory,'acl-native.mjs'));
|
|
87
|
+
env.ALIPAY_WINDOWS_POWERSHELL=slash(powershell());
|
|
88
|
+
env.NODE_BINARY='node';
|
|
89
|
+
return {bash,git,cli,jq,env};
|
|
90
|
+
}
|
|
91
|
+
function writable(directoryPath) {
|
|
92
|
+
if(!fs.existsSync(directoryPath)) fs.mkdirSync(directoryPath,{recursive:true});
|
|
93
|
+
if(fs.lstatSync(directoryPath).isSymbolicLink()) fail('STORAGE_FAILED');
|
|
94
|
+
const p=path.join(directoryPath,'.aipay-probe-'+crypto.randomUUID());
|
|
95
|
+
try { fs.writeFileSync(p,'probe',{flag:'wx'});fs.renameSync(p,p+'.moved');fs.unlinkSync(p+'.moved'); }
|
|
96
|
+
catch { fail('STORAGE_FAILED'); }
|
|
97
|
+
}
|
|
98
|
+
// Pin the existing telemetry location before changing Bash scratch paths. This
|
|
99
|
+
// never changes the gateway environment, consent, session, run or override files.
|
|
100
|
+
export function privateChildEnvironment(env, temporary) {
|
|
101
|
+
return {...env, ALIPAY_AIPAY_TELEMETRY_DIR: telemetryStoragePaths(env).state_dir,
|
|
102
|
+
TEMP: temporary, TMP: temporary, TMPDIR: slash(temporary)};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function inspectLocal(root) {
|
|
106
|
+
const context=locate();
|
|
107
|
+
const temporary=path.join(root,'tmp');fs.mkdirSync(temporary,{recursive:true});protect(temporary);
|
|
108
|
+
context.env = privateChildEnvironment(context.env, temporary);
|
|
109
|
+
const home=os.homedir();
|
|
110
|
+
for(const p of [path.join(home,'.alipay-cli/credentials'),path.join(process.env.APPDATA||path.join(home,'AppData/Roaming'),'alipay-cli/data/logs')]) {
|
|
111
|
+
fs.mkdirSync(p,{recursive:true});protect(p);writable(p);
|
|
112
|
+
}
|
|
113
|
+
for(const [exe,args,name] of [[context.cli,['version'],'alipay-cli'],[context.jq,['-n','-e','--arg','v','猫 $HOME "x"','$v == "猫 $HOME \\"x\\""'],'jq']]) {
|
|
114
|
+
// jq round-trip is checked in the Bash probe below; this native check verifies launch/version.
|
|
115
|
+
const r=run(exe,name==='jq'?['--version']:args,{env:context.env});
|
|
116
|
+
if(r.status!==0||!r.stdout.trim()) fail('EXECUTION_FAILED',name);
|
|
117
|
+
}
|
|
118
|
+
const r=run(context.bash,['--noprofile','--norc',slash(path.join(directory,'probe.sh'))],{env:context.env});
|
|
119
|
+
if(r.status!==0||r.stdout.trim()!=='WINDOWS_PROBE:READY') fail('SHELL_TRANSPORT_FAILED');
|
|
120
|
+
writable(temporary);
|
|
121
|
+
return context;
|
|
122
|
+
}
|
|
123
|
+
export function ensureReady({retry=false}={}) {
|
|
124
|
+
// Reuse only within this Node invocation. Each new process performs full checks;
|
|
125
|
+
// persisted failures never prevent a read-only check after the user fixes a dependency.
|
|
126
|
+
if(!retry&&invocationContext) {
|
|
127
|
+
locate();return invocationContext.context;
|
|
128
|
+
}
|
|
129
|
+
const root=stateRoot();
|
|
130
|
+
try {
|
|
131
|
+
const context=inspectLocal(root);
|
|
132
|
+
// Diagnostic state is optional after a complete capability check. An
|
|
133
|
+
// abandoned lock must not add two seconds to every subsequent CLI call.
|
|
134
|
+
if (!fs.existsSync(path.join(root,'environment.lock'))) {
|
|
135
|
+
try {
|
|
136
|
+
withLock(root,()=>{
|
|
137
|
+
const state=readState(root);
|
|
138
|
+
if(state.blocked){state.blocked=false;state.reason='';saveState(root,state);}
|
|
139
|
+
});
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (!['ENVIRONMENT_BUSY','ENVIRONMENT_STATE_INVALID'].includes(error.reason)) throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
invocationContext={root,context};return context;
|
|
145
|
+
} catch(e) {
|
|
146
|
+
if (!fs.existsSync(path.join(root,'environment.lock'))) {
|
|
147
|
+
try {
|
|
148
|
+
withLock(root,()=>{const state=readState(root);state.blocked=true;state.reason=e.reason||'CHECK_FAILED';saveState(root,state);});
|
|
149
|
+
} catch (error) {
|
|
150
|
+
if (!['ENVIRONMENT_BUSY','ENVIRONMENT_STATE_INVALID'].includes(error.reason)) throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
throw e;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
export function applyReady(options) {
|
|
157
|
+
// Do not change the gateway's TEMP, telemetry switches or business context.
|
|
158
|
+
return ensureReady(options);
|
|
159
|
+
}
|
|
160
|
+
export function resultError(error) {
|
|
161
|
+
const needsSetup = error.reason === 'DEPENDENCY_MISSING' ||
|
|
162
|
+
(error.reason === 'EXECUTION_FAILED' && error.dependency === 'alipay-cli');
|
|
163
|
+
return {schemaVersion:1,status:needsSetup?'NEEDS_SETUP':'BLOCKED',reason:error.reason||'CHECK_FAILED',dependency:error.dependency||'',validationCoverage:'NOT_VERIFIED'};
|
|
164
|
+
}
|
|
165
|
+
export function report(error) {
|
|
166
|
+
const r=resultError(error);
|
|
167
|
+
process.stderr.write('ALIPAY_AIPAY_INTERNAL:WINDOWS_ENV_RESULT='+JSON.stringify(r)+'\n');
|
|
168
|
+
|
|
169
|
+
}
|
|
170
|
+
if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)) {
|
|
171
|
+
try {
|
|
172
|
+
const c=ensureReady();
|
|
173
|
+
if(process.argv[2]==='shell-env') {
|
|
174
|
+
const quote=s=>"'"+s.replaceAll("'","'\\''")+"'";
|
|
175
|
+
// Only explicit adapter-owned values. Business context and credentials never appear here.
|
|
176
|
+
for(const k of ['PATH','TEMP','TMP','TMPDIR','ALIPAY_AIPAY_TELEMETRY_DIR','ALIPAY_WINDOWS_NODE','ALIPAY_WINDOWS_JQ','ALIPAY_WINDOWS_CLI','ALIPAY_WINDOWS_CREDENTIALS_DIR','ALIPAY_WINDOWS_LOGS_DIR','ALIPAY_WINDOWS_CLI_ENTRY','ALIPAY_WINDOWS_CLI_RUNNER','ALIPAY_WINDOWS_ACL_SCRIPT','ALIPAY_WINDOWS_POWERSHELL','NODE_BINARY','MSYS_NO_PATHCONV','MSYS2_ARG_CONV_EXCL']) {
|
|
177
|
+
const v=k==='PATH'?c.env[k].split(';').map(p=>slash(p).replace(/^([a-zA-Z]):/,(_,d)=>'/'+d.toLowerCase())).join(':'):c.env[k];
|
|
178
|
+
console.log('export '+k+'='+quote(v));
|
|
179
|
+
}
|
|
180
|
+
} else console.error('ALIPAY_AIPAY_INTERNAL:WINDOWS_ENV_RESULT='+JSON.stringify({schemaVersion:1,status:'READY',validationCoverage:'NOT_VERIFIED'}));
|
|
181
|
+
}catch(e){report(e);process.exitCode=78;}
|
|
182
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
import {refreshWindowsCli} from './cli-install.mjs';
|
|
6
|
+
import {findGitBash} from './git-discovery.mjs';
|
|
7
|
+
import {policy,run,stateRoot,readState,saveState,withLock,ensureReady,locateJq,fail,report} from './preflight.mjs';
|
|
8
|
+
|
|
9
|
+
export function claimPreparation(state,{repair=false,authorized=false}={}) {
|
|
10
|
+
if(!authorized) fail('PREPARATION_AUTHORIZATION_REQUIRED');
|
|
11
|
+
if(repair) {
|
|
12
|
+
if(state.repairAttempts>=policy.explicitRepairRetries) fail('RETRY_BUDGET_EXHAUSTED');
|
|
13
|
+
state.repairAttempts++;
|
|
14
|
+
} else if(state.prepareAttempts>=1 || state.repairAttempts>=1) fail('RETRY_BUDGET_EXHAUSTED');
|
|
15
|
+
state.prepareAttempts++;state.blocked=true;state.reason='PREPARATION_INCOMPLETE';
|
|
16
|
+
return state;
|
|
17
|
+
}
|
|
18
|
+
function installAsset(name,asset) {
|
|
19
|
+
const bin=path.join(os.homedir(),'.local/bin');fs.mkdirSync(bin,{recursive:true});
|
|
20
|
+
const target=path.join(bin,name+'.exe');
|
|
21
|
+
const hash=b=>crypto.createHash('sha256').update(b).digest('hex');
|
|
22
|
+
if(fs.existsSync(target)&&hash(fs.readFileSync(target))===asset.sha256)return;
|
|
23
|
+
const temporary=path.join(bin,name+'-'+crypto.randomUUID()+'.download');
|
|
24
|
+
const curl=path.join(process.env.SystemRoot||'C:/Windows','System32/curl.exe');
|
|
25
|
+
try {
|
|
26
|
+
const r=run(curl,['--compressed','--fail','--silent','--show-error','--location','--proto','=https','--proto-redir','=https','--connect-timeout','10','--max-time','60','--output',temporary,asset.url],{timeout:65000});
|
|
27
|
+
if(r.status!==0)fail('DOWNLOAD_FAILED',name);
|
|
28
|
+
if(hash(fs.readFileSync(temporary))!==asset.sha256)fail('INTEGRITY_FAILED',name);
|
|
29
|
+
fs.renameSync(temporary,target);
|
|
30
|
+
if(run(target,name==='jq'?['--version']:['version']).status!==0)fail('EXECUTION_FAILED',name);
|
|
31
|
+
}finally{if(fs.existsSync(temporary))fs.unlinkSync(temporary);}
|
|
32
|
+
}
|
|
33
|
+
export function prepare({authorized=false,repair=false}={}) {
|
|
34
|
+
const root=stateRoot();
|
|
35
|
+
withLock(root,()=>{
|
|
36
|
+
const state=claimPreparation(readState(root),{authorized,repair});saveState(root,state);
|
|
37
|
+
try {
|
|
38
|
+
if(!findGitBash()) {
|
|
39
|
+
const winget=path.join(process.env.LOCALAPPDATA||'','Microsoft/WindowsApps/winget.exe');
|
|
40
|
+
if(!fs.existsSync(winget))fail('MANUAL_GIT_INSTALL_REQUIRED','Git Bash');
|
|
41
|
+
const r=run(winget,['install','--id','Git.Git','--exact','--source','winget','--scope','user','--silent','--disable-interactivity','--accept-package-agreements','--accept-source-agreements'],{timeout:120000});
|
|
42
|
+
if(r.status!==0||!findGitBash())fail('GIT_INSTALL_FAILED','Git Bash');
|
|
43
|
+
}
|
|
44
|
+
if(!locateJq()) installAsset('jq',policy.jq);
|
|
45
|
+
const cli=path.join(os.homedir(),'.local/bin/alipay-cli.exe');
|
|
46
|
+
if(!fs.existsSync(cli)||run(cli,['version']).status!==0) {
|
|
47
|
+
if(refreshWindowsCli()!=='READY') fail('CLI_INSTALL_FAILED','alipay-cli');
|
|
48
|
+
}
|
|
49
|
+
state.blocked=false;state.reason='';saveState(root,state);
|
|
50
|
+
}catch(e){state.blocked=true;state.reason=e.reason||'INSTALL_FAILED';saveState(root,state);throw e;}
|
|
51
|
+
});
|
|
52
|
+
return ensureReady();
|
|
53
|
+
}
|
|
54
|
+
export function parseEnvironmentRequest(args, route=process.env.ALIPAY_AIPAY_WINDOWS_ROUTE) {
|
|
55
|
+
const [mode,...rest]=args;
|
|
56
|
+
const options={};
|
|
57
|
+
for(let i=0;i<rest.length;i+=2) {
|
|
58
|
+
if(!['--product-type','--authorized','--repair-confirmed','--root-intent'].includes(rest[i])||rest[i+1]===undefined||Object.hasOwn(options,rest[i]))fail('ENVIRONMENT_ARGUMENT_INVALID');
|
|
59
|
+
options[rest[i]]=rest[i+1];
|
|
60
|
+
}
|
|
61
|
+
if(options['--product-type']&&!['aipay','webpay','apppay'].includes(options['--product-type']))fail('ENVIRONMENT_ARGUMENT_INVALID');
|
|
62
|
+
for (const key of ['--authorized','--repair-confirmed']) {
|
|
63
|
+
if (options[key] !== undefined && !['true','false'].includes(options[key])) fail('ENVIRONMENT_ARGUMENT_INVALID');
|
|
64
|
+
}
|
|
65
|
+
if (mode === 'retry' && options['--repair-confirmed'] !== 'true') fail('PREPARATION_AUTHORIZATION_REQUIRED');
|
|
66
|
+
const recovering = mode === 'retry' || (mode === 'prepare' && options['--repair-confirmed'] === 'true');
|
|
67
|
+
if (recovering && (!['integration_only','onboarding_only','full_process'].includes(options['--root-intent']) ||
|
|
68
|
+
route !== 'LEGACY')) fail('ENVIRONMENT_ARGUMENT_INVALID');
|
|
69
|
+
if (!recovering && options['--root-intent'] !== undefined) fail('ENVIRONMENT_ARGUMENT_INVALID');
|
|
70
|
+
return {mode,options,recovering};
|
|
71
|
+
}
|
|
72
|
+
export function runEnvironment(args) {
|
|
73
|
+
const {mode,options,recovering}=parseEnvironmentRequest(args);
|
|
74
|
+
try {
|
|
75
|
+
if(['prepare','prepare-jq'].includes(mode))prepare({authorized:options['--authorized']==='true',repair:options['--repair-confirmed']==='true'});
|
|
76
|
+
else if(['check','integration-check','retry'].includes(mode))ensureReady({retry:mode==='retry'&&options['--repair-confirmed']==='true'});
|
|
77
|
+
else fail('ENVIRONMENT_ARGUMENT_INVALID');
|
|
78
|
+
console.log('CLI_ENV:READY');
|
|
79
|
+
if (recovering) process.stderr.write('ALIPAY_AIPAY_INTERNAL:WINDOWS_ROUTE=' +
|
|
80
|
+
JSON.stringify({schemaVersion:1,rootIntent:options['--root-intent'],route:'READY'}) + '\n');
|
|
81
|
+
return 0;
|
|
82
|
+
}catch(e){report(e);return 78;}
|
|
83
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -eu
|
|
3
|
+
case "$(uname -s)" in MINGW*|MSYS*) ;; *) exit 78;; esac
|
|
4
|
+
for tool in cygpath mktemp chmod stat find sed grep awk tr base64 head tail dirname basename cat mv rm mkdir env curl git; do
|
|
5
|
+
command -v "$tool" >/dev/null || exit 78
|
|
6
|
+
done
|
|
7
|
+
[[ "$(cygpath -m "$(command -v alipay-cli)")" == "$ALIPAY_WINDOWS_CLI_ENTRY" ]] || exit 78
|
|
8
|
+
value='猫抓板 "quoted" $HOME /scope/a https://example.com/a?x=1&y=2'
|
|
9
|
+
out=$(printf '%s' "$value" | "$ALIPAY_WINDOWS_JQ" -Rs . | "$ALIPAY_WINDOWS_JQ" -r .)
|
|
10
|
+
[[ "$out" == "$value" ]] || exit 78
|
|
11
|
+
"$ALIPAY_WINDOWS_NODE" -e 'if(process.argv[1]!==process.env.WINDOWS_PROBE_VALUE) process.exit(78)' "$value" >/dev/null 2>&1 && exit 78
|
|
12
|
+
WINDOWS_PROBE_VALUE="$value" "$ALIPAY_WINDOWS_NODE" -e 'if(process.argv[1]!==process.env.WINDOWS_PROBE_VALUE) process.exit(78)' "$value"
|
|
13
|
+
"$ALIPAY_WINDOWS_CLI" version >/dev/null
|
|
14
|
+
printf 'WINDOWS_PROBE:READY\n'
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {spawnSync} from 'node:child_process';
|
|
5
|
+
import {protect} from './acl.mjs';
|
|
6
|
+
|
|
7
|
+
// Pin tempfile itself: environment variables alone allow Python to fall back to
|
|
8
|
+
// another directory if the protected directory becomes unavailable.
|
|
9
|
+
const bootstrap = [
|
|
10
|
+
'import os, runpy, sys, tempfile',
|
|
11
|
+
'tempfile.tempdir = sys.argv[1]',
|
|
12
|
+
'sys.argv = sys.argv[2:]',
|
|
13
|
+
'sys.path[0] = os.path.dirname(sys.argv[0])',
|
|
14
|
+
'runpy.run_path(sys.argv[0], run_name="__main__")'
|
|
15
|
+
].join('\n');
|
|
16
|
+
|
|
17
|
+
// The caller has already validated the single registered A2M script path.
|
|
18
|
+
export function runWindowsPython(python, script, args, {
|
|
19
|
+
env = process.env, tempRoot = os.tmpdir(), protectDirectory = protect, spawn = spawnSync,
|
|
20
|
+
cleanup = directory => fs.rmSync(directory, {recursive: true, force: true})
|
|
21
|
+
} = {}) {
|
|
22
|
+
const temporary = fs.mkdtempSync(path.join(path.resolve(tempRoot), 'alipay-aipay-a2m-'));
|
|
23
|
+
try {
|
|
24
|
+
// Empty until NTFS DACL setup and readback have both succeeded. Children
|
|
25
|
+
// inherit the restricted ACL, including files written directly by curl.
|
|
26
|
+
protectDirectory(temporary);
|
|
27
|
+
const childEnv = Object.fromEntries(Object.entries(env)
|
|
28
|
+
.filter(([key]) => !['TEMP', 'TMP', 'TMPDIR'].includes(key.toUpperCase())));
|
|
29
|
+
Object.assign(childEnv, {TEMP: temporary, TMP: temporary, TMPDIR: temporary});
|
|
30
|
+
return spawn(python.command, [...python.prefix, '-c', bootstrap, temporary, script, ...args], {
|
|
31
|
+
env: childEnv, stdio: 'inherit', shell: false, windowsHide: true
|
|
32
|
+
});
|
|
33
|
+
} finally {
|
|
34
|
+
// Clean only this invocation's scratch directory; never replay the payment.
|
|
35
|
+
try { cleanup(temporary); }
|
|
36
|
+
catch { process.stderr.write('ALIPAY_AIPAY_INTERNAL:WINDOWS_A2M_SCRATCH_CLEANUP=FAILED\n'); }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {fileURLToPath} from 'node:url';
|
|
4
|
+
import {run, fail, protect} from './acl.mjs';
|
|
5
|
+
|
|
6
|
+
const script = fileURLToPath(new URL('../../../integration/modules/scripts/local_402_sandbox_pay.py', import.meta.url));
|
|
7
|
+
const probe = 'import json, os, runpy, sys; assert sys.version_info.major == 3 and sys.platform == "win32"; sys.path.insert(0, os.path.dirname(sys.argv[1])); runpy.run_path(sys.argv[1], run_name="__alipay_probe__"); print(json.dumps({"executable":sys.executable,"version":list(sys.version_info[:3])}))';
|
|
8
|
+
|
|
9
|
+
export function findWindowsPython({env = process.env, execute = run, exists = fs.existsSync, list = fs.readdirSync} = {}) {
|
|
10
|
+
const probeEnv = Object.fromEntries(Object.entries(env).filter(([key]) =>
|
|
11
|
+
!['PYTHON_MANAGER_AUTOMATIC_INSTALL', 'PYLAUNCHER_ALLOW_INSTALL', 'PYLAUNCHER_ALWAYS_INSTALL'].includes(key.toUpperCase())));
|
|
12
|
+
probeEnv.PYTHON_MANAGER_AUTOMATIC_INSTALL = '0';
|
|
13
|
+
const candidates = [{command: 'py', prefix: ['-3']}, {command: 'python', prefix: []}, {command: 'python3', prefix: []}];
|
|
14
|
+
const roots = [env.LOCALAPPDATA && path.win32.join(env.LOCALAPPDATA, 'Programs/Python'), env.ProgramFiles && path.win32.join(env.ProgramFiles, 'Python')].filter(Boolean);
|
|
15
|
+
for (const root of roots) {
|
|
16
|
+
let names = [];
|
|
17
|
+
try { names = list(root).filter(name => /^Python3\d+$/i.test(name)).sort().reverse(); } catch {}
|
|
18
|
+
for (const name of names) candidates.push({command: path.win32.join(root, name, 'python.exe'), prefix: []});
|
|
19
|
+
}
|
|
20
|
+
for (const candidate of candidates) {
|
|
21
|
+
const result = execute(candidate.command, [...candidate.prefix, '-c', probe, script], {env: probeEnv, timeout: 10000});
|
|
22
|
+
if (result.status !== 0) continue;
|
|
23
|
+
try {
|
|
24
|
+
const value = JSON.parse(result.stdout);
|
|
25
|
+
if (value.version?.[0] === 3 && path.win32.isAbsolute(value.executable) && exists(value.executable)) {
|
|
26
|
+
return {command: value.executable, prefix: []};
|
|
27
|
+
}
|
|
28
|
+
} catch {}
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// A separate budget: preparing Python for A2M never consumes or resets the
|
|
34
|
+
// Git/jq/CLI environment repair state, or any business/telemetry state.
|
|
35
|
+
export function prepareWindowsPython({authorized = false, repair = false, find = findWindowsPython,
|
|
36
|
+
execute = run, env = process.env, root, protectRoot = protect, lock, exists = fs.existsSync} = {}) {
|
|
37
|
+
const existing = find({env});
|
|
38
|
+
if (existing) return existing;
|
|
39
|
+
if (!authorized) fail('DEPENDENCY_MISSING', 'Python 3');
|
|
40
|
+
protectRoot(root);
|
|
41
|
+
return lock(root, () => {
|
|
42
|
+
const file = path.join(root, 'python-environment.json');
|
|
43
|
+
let state = {prepareAttempts: 0, repairAttempts: 0};
|
|
44
|
+
if (fs.existsSync(file)) {
|
|
45
|
+
try { state = JSON.parse(fs.readFileSync(file, 'utf8')); } catch { fail('ENVIRONMENT_STATE_INVALID'); }
|
|
46
|
+
}
|
|
47
|
+
if (!Number.isInteger(state.prepareAttempts) || state.prepareAttempts < 0 ||
|
|
48
|
+
!Number.isInteger(state.repairAttempts) || state.repairAttempts < 0) fail('ENVIRONMENT_STATE_INVALID');
|
|
49
|
+
if (repair ? state.repairAttempts >= 1 : state.prepareAttempts >= 1 || state.repairAttempts >= 1) fail('RETRY_BUDGET_EXHAUSTED');
|
|
50
|
+
state.prepareAttempts++; if (repair) state.repairAttempts++;
|
|
51
|
+
fs.writeFileSync(file, JSON.stringify(state));
|
|
52
|
+
const winget = env.LOCALAPPDATA && path.win32.join(env.LOCALAPPDATA, 'Microsoft/WindowsApps/winget.exe');
|
|
53
|
+
if (!winget || !exists(winget)) fail('MANUAL_PYTHON_INSTALL_REQUIRED', 'Python 3');
|
|
54
|
+
const result = execute(winget, ['install', '--id', 'Python.Python.3.14', '--exact', '--source', 'winget',
|
|
55
|
+
'--scope', 'user', '--architecture', 'x64', '--silent', '--disable-interactivity',
|
|
56
|
+
'--accept-package-agreements', '--accept-source-agreements'], {env, timeout: 180000});
|
|
57
|
+
if (result.status !== 0) fail('PYTHON_INSTALL_FAILED', 'Python 3');
|
|
58
|
+
const python = find({env});
|
|
59
|
+
if (!python) fail('PYTHON_VERIFY_FAILED', 'Python 3');
|
|
60
|
+
return python;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function runPythonEnvironment(args) {
|
|
65
|
+
const [mode, ...rest] = args;
|
|
66
|
+
const options = {};
|
|
67
|
+
for (let i = 0; i < rest.length; i += 2) {
|
|
68
|
+
if (!['--authorized', '--repair-confirmed'].includes(rest[i]) ||
|
|
69
|
+
!['true', 'false'].includes(rest[i + 1]) || options[rest[i]] !== undefined) fail('ENVIRONMENT_ARGUMENT_INVALID');
|
|
70
|
+
options[rest[i]] = rest[i + 1];
|
|
71
|
+
}
|
|
72
|
+
if (!['check-python', 'prepare-python'].includes(mode) || (mode === 'check-python' && rest.length)) fail('ENVIRONMENT_ARGUMENT_INVALID');
|
|
73
|
+
const {stateRoot, withLock, report} = await import('./preflight.mjs');
|
|
74
|
+
try {
|
|
75
|
+
// Delay all filesystem state until installation is actually necessary.
|
|
76
|
+
const found = findWindowsPython();
|
|
77
|
+
if (!found) {
|
|
78
|
+
if (mode === 'check-python' || options['--authorized'] !== 'true') fail('DEPENDENCY_MISSING', 'Python 3');
|
|
79
|
+
prepareWindowsPython({authorized: true, repair: options['--repair-confirmed'] === 'true',
|
|
80
|
+
root: stateRoot(), protectRoot: protect, lock: withLock});
|
|
81
|
+
}
|
|
82
|
+
process.stderr.write('ALIPAY_AIPAY_INTERNAL:PYTHON_ENV=READY\n');
|
|
83
|
+
return 0;
|
|
84
|
+
} catch (error) { report(error); return 78; }
|
|
85
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {applyReady, report, resultError, readState, stateRoot} from './preflight.mjs';
|
|
2
|
+
import {prepare} from './prepare.mjs';
|
|
3
|
+
|
|
4
|
+
const intents = ['integration_only', 'onboarding_only', 'full_process'];
|
|
5
|
+
const prefix = 'ALIPAY_AIPAY_INTERNAL:';
|
|
6
|
+
|
|
7
|
+
// This is a routing result, never proof of a successful payment or onboarding.
|
|
8
|
+
export function selectRoute({rootIntent = '', authorized = false, repair = false} = {}) {
|
|
9
|
+
if (rootIntent && !intents.includes(rootIntent)) throw new Error('非法 Windows 根意图');
|
|
10
|
+
let error;
|
|
11
|
+
try {
|
|
12
|
+
applyReady({retry: repair});
|
|
13
|
+
return emit({rootIntent, route: 'READY'});
|
|
14
|
+
} catch (e) { error = e; }
|
|
15
|
+
if (authorized) {
|
|
16
|
+
// A missing dependency or an unlaunchable local CLI permits preparation;
|
|
17
|
+
// network, permissions, architecture, integrity and business errors do not.
|
|
18
|
+
try {
|
|
19
|
+
const state = readState(stateRoot());
|
|
20
|
+
const cliUnusable = error.reason === 'EXECUTION_FAILED' && error.dependency === 'alipay-cli';
|
|
21
|
+
if ((error.reason === 'DEPENDENCY_MISSING' || cliUnusable ||
|
|
22
|
+
(error.reason === 'REPAIR_REQUIRED' && state.reason === 'DEPENDENCY_MISSING')) &&
|
|
23
|
+
state.prepareAttempts === 0 && state.repairAttempts === 0) {
|
|
24
|
+
prepare({authorized: true});
|
|
25
|
+
return emit({rootIntent, route: 'READY'});
|
|
26
|
+
}
|
|
27
|
+
} catch (e) { error = e; }
|
|
28
|
+
}
|
|
29
|
+
report(error);
|
|
30
|
+
return emit({rootIntent, route: 'LEGACY', ...resultError(error)});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function emit(result) {
|
|
34
|
+
const value = {schemaVersion: 1, ...result};
|
|
35
|
+
process.stderr.write(prefix + 'WINDOWS_ROUTE=' + JSON.stringify(value) + '\n');
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function runWindowsEntry(args) {
|
|
40
|
+
const options = {};
|
|
41
|
+
for (let i = 0; i < args.length; i += 2) {
|
|
42
|
+
const key = args[i], value = args[i + 1];
|
|
43
|
+
if (!['--root-intent', '--prepare-authorized', '--repair-confirmed'].includes(key) ||
|
|
44
|
+
value === undefined || options[key] !== undefined) throw new Error('Windows 入口参数非法');
|
|
45
|
+
options[key] = value;
|
|
46
|
+
}
|
|
47
|
+
if (!intents.includes(options['--root-intent'])) throw new Error('必须先识别三种根意图之一');
|
|
48
|
+
for (const key of ['--prepare-authorized', '--repair-confirmed']) {
|
|
49
|
+
if (options[key] !== undefined && !['true', 'false'].includes(options[key])) throw new Error('Windows 入口布尔参数非法');
|
|
50
|
+
}
|
|
51
|
+
return selectRoute({rootIntent: options['--root-intent'],
|
|
52
|
+
authorized: options['--prepare-authorized'] === 'true', repair: options['--repair-confirmed'] === 'true'});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// A caller's LEGACY fact can only disable operations. READY is never a capability token.
|
|
56
|
+
export function consumeRoute(args) {
|
|
57
|
+
const remaining = [];
|
|
58
|
+
let route;
|
|
59
|
+
let seen = false;
|
|
60
|
+
for (let i = 0; i < args.length; i++) {
|
|
61
|
+
if (args[i] !== '--windows-route') { remaining.push(args[i]); continue; }
|
|
62
|
+
if (seen || !['READY','LEGACY'].includes(args[i + 1])) throw new Error('Windows 分流参数非法');
|
|
63
|
+
seen = true;
|
|
64
|
+
route = args[++i];
|
|
65
|
+
}
|
|
66
|
+
return {args: remaining, route};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Native Node messages, telemetry and project inspection do not depend on Bash.
|
|
70
|
+
// READY is never trusted as proof; the actual business executor checks again.
|
|
71
|
+
export function requireBusinessEnvironment() {
|
|
72
|
+
if (process.env.ALIPAY_AIPAY_WINDOWS_ROUTE === 'LEGACY') {
|
|
73
|
+
const error = Object.assign(new Error('WINDOWS_ENVIRONMENT_UNAVAILABLE'), {reason: 'WINDOWS_ENVIRONMENT_UNAVAILABLE'});
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
return applyReady();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function onboardingRoute() {
|
|
80
|
+
if (process.env.ALIPAY_AIPAY_WINDOWS_ROUTE === 'LEGACY') return 'LEGACY';
|
|
81
|
+
try { requireBusinessEnvironment(); return 'READY'; }
|
|
82
|
+
catch (error) { report(error); return 'LEGACY'; }
|
|
83
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Loaded only on real Git for Windows; no Linux/macOS command is replaced.
|
|
3
|
+
_win_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
4
|
+
_win_exports=$(command node "$(cygpath -m "$_win_dir/preflight.mjs")" shell-env) || { exit 78; }
|
|
5
|
+
eval "$_win_exports"
|
|
6
|
+
unset _win_exports
|
|
7
|
+
|
|
8
|
+
windows_protect() {
|
|
9
|
+
"$ALIPAY_WINDOWS_NODE" "$ALIPAY_WINDOWS_ACL_SCRIPT" "$(cygpath -m "$1")" >/dev/null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
git() {
|
|
13
|
+
local args=() previous='' arg
|
|
14
|
+
for arg in "$@"; do
|
|
15
|
+
if [ "$previous" = -C ]; then arg=$(cygpath -m "$arg") || return 78; fi
|
|
16
|
+
args+=("$arg");previous="$arg"
|
|
17
|
+
done
|
|
18
|
+
command git "${args[@]}"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
# jq is a Bash function so large --argjson values never enter a Windows argv.
|
|
22
|
+
# Slurp exactly one JSON value and bind it back to the original variable name.
|
|
23
|
+
jq() {
|
|
24
|
+
local args=() files=() bindings='' filter='' name value file rc arg
|
|
25
|
+
while [ "$#" -gt 0 ]; do
|
|
26
|
+
case "$1" in
|
|
27
|
+
--argjson)
|
|
28
|
+
[ "$#" -ge 3 ] || return 64
|
|
29
|
+
name="$2";value="$3";shift 3
|
|
30
|
+
[[ "$name" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || return 64
|
|
31
|
+
file=$(mktemp "${TMPDIR}/jq-argument.XXXXXX") || return 78
|
|
32
|
+
printf '%s' "$value" >"$file" || { rm -f "$file"; return 78; }
|
|
33
|
+
files+=("$file");args+=(--slurpfile "$name" "$(cygpath -m "$file")")
|
|
34
|
+
bindings+="(if (\$$name|length)==1 then \$$name[0] else error(\"invalid argjson\") end) as \$$name | "
|
|
35
|
+
;;
|
|
36
|
+
--arg)
|
|
37
|
+
[ "$#" -ge 3 ] || return 64
|
|
38
|
+
value="$3"
|
|
39
|
+
if [ "$2" = configPath ]; then value=$(cygpath -m "$value") || return 78; fi
|
|
40
|
+
args+=("$1" "$2" "$value");shift 3 ;;
|
|
41
|
+
--argfile|--slurpfile|--rawfile)
|
|
42
|
+
[ "$#" -ge 3 ] || return 64
|
|
43
|
+
value=$(cygpath -m "$3") || return 78
|
|
44
|
+
args+=("$1" "$2" "$value");shift 3 ;;
|
|
45
|
+
-*) args+=("$1");shift ;;
|
|
46
|
+
*) filter="$1";shift;break ;;
|
|
47
|
+
esac
|
|
48
|
+
done
|
|
49
|
+
if [ -n "$filter" ]; then args+=("$bindings$filter"); fi
|
|
50
|
+
for arg in "$@"; do
|
|
51
|
+
if [[ "$arg" == /* ]] && [ -f "$arg" ]; then args+=("$(cygpath -m "$arg")");else args+=("$arg");fi
|
|
52
|
+
done
|
|
53
|
+
if "$ALIPAY_WINDOWS_JQ" "${args[@]}"; then rc=0; else rc=$?; fi
|
|
54
|
+
[ "${#files[@]}" -eq 0 ] || rm -f "${files[@]}"
|
|
55
|
+
return "$rc"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
node() {
|
|
59
|
+
local args=() catalog='' arg previous='' first=true
|
|
60
|
+
while [ "$#" -gt 0 ]; do
|
|
61
|
+
arg="$1";shift
|
|
62
|
+
if [ "$arg" = --mcc-catalog-json ]; then
|
|
63
|
+
[ "$#" -gt 0 ] || return 64
|
|
64
|
+
catalog="$1";shift;args+=(--mcc-catalog-json @windows-stdin);continue
|
|
65
|
+
fi
|
|
66
|
+
if { [ "$first" = true ] && [[ "$arg" == /* ]]; } || [[ "$previous" =~ ^--(project-path|base-path|config-path|search-base-path)$ ]]; then
|
|
67
|
+
arg=$(cygpath -m "$arg") || return 78
|
|
68
|
+
fi
|
|
69
|
+
args+=("$arg");previous="$arg";first=false
|
|
70
|
+
done
|
|
71
|
+
if [ -n "$catalog" ]; then printf '%s' "$catalog" | "$ALIPAY_WINDOWS_NODE" "${args[@]}"
|
|
72
|
+
else "$ALIPAY_WINDOWS_NODE" "${args[@]}";fi
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Preserve command names and business arguments; only file-upload paths cross
|
|
76
|
+
# the POSIX/native boundary here. No fake CLI and no business response rewriting.
|
|
77
|
+
alipay-cli() {
|
|
78
|
+
local args=("$@")
|
|
79
|
+
if [ "${1:-}" = file ] && [ "${2:-}" = upload ] && [ "$#" -ge 3 ]; then
|
|
80
|
+
args[2]=$(cygpath -m "$3") || return 78
|
|
81
|
+
fi
|
|
82
|
+
"$ALIPAY_WINDOWS_NODE" "$ALIPAY_WINDOWS_CLI_RUNNER" "${args[@]}"
|
|
83
|
+
}
|