pcremotecommander 0.7.0-beta.5 → 0.7.0-beta.6

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.
@@ -1,95 +1,95 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
- const fs=require('node:fs');
4
- const path=require('node:path');
5
- const os=require('node:os');
6
- const https=require('node:https');
7
- const crypto=require('node:crypto');
8
- const {pipeline}=require('node:stream/promises');
9
- const {spawn,spawnSync}=require('node:child_process');
10
-
11
- const version='0.7.0-beta.5';
12
- const sha256='eef6699380b696f28e6016e4c81931d3774dd2a0b6a769ea76cee2dcaa64101e';
13
- const url='https://pc-remote-commander-staging.pcremotecommander.workers.dev/downloads/windows/pcremotecommander-0.7.0-beta.5.tgz';
14
- const maximumBytes=100*1024*1024;
15
-
16
- function cacheRoot(env=process.env) {
17
- if(process.platform!=='win32'||!env.LOCALAPPDATA||!path.isAbsolute(env.LOCALAPPDATA))throw Error('This staging preview requires Windows with a normal user profile.');
18
- return path.join(env.LOCALAPPDATA,'PCRemoteCommander','packages',version);
19
- }
20
- function shaFile(file) {
21
- const hash=crypto.createHash('sha256');
22
- const fd=fs.openSync(file,'r');
23
- try {const buffer=Buffer.allocUnsafe(1024*1024);for(;;){const length=fs.readSync(fd,buffer,0,buffer.length,null);if(!length)break;hash.update(buffer.subarray(0,length));}} finally {fs.closeSync(fd);}
24
- return hash.digest('hex');
25
- }
26
- function verified(file,expected=sha256) {return fs.existsSync(file)&&shaFile(file)===expected;}
27
- function validateEntries(output) {
28
- const entries=output.split(/\r?\n/).filter(Boolean);
29
- if(!entries.length)throw Error('Downloaded runtime archive is empty.');
30
- for(const entry of entries) {
31
- const parts=entry.split('/');
32
- if(parts[0]!=='package'||entry.startsWith('/')||entry.includes('\\')||parts.some(part=>part==='..'))throw Error('Downloaded runtime archive has an unsafe path.');
33
- }
34
- return entries.length;
35
- }
36
- function download(file,request=https.get) {
37
- return new Promise((resolve,reject)=>{
38
- const output=fs.createWriteStream(file,{flags:'wx',mode:0o600});
39
- output.on('error',reject);
40
- const req=request(url,response=>{
41
- if(response.statusCode!==200){response.resume();output.destroy();reject(Error(`Runtime download failed (HTTP ${response.statusCode}). Try again later.`));return;}
42
- const length=Number(response.headers['content-length']);
43
- if(!Number.isFinite(length)||length<1||length>maximumBytes){response.resume();output.destroy();reject(Error('Runtime download has an invalid size.'));return;}
44
- let received=0;
45
- response.on('data',chunk=>{received+=chunk.length;if(received>maximumBytes)response.destroy(Error('Runtime download exceeded its size limit.'));});
46
- pipeline(response,output).then(()=>received===length?resolve():reject(Error('Runtime download was incomplete.')),reject);
47
- });
48
- req.setTimeout(120000,()=>req.destroy(Error('Runtime download timed out.')));
49
- req.on('error',error=>{output.destroy();reject(error);});
50
- });
51
- }
52
- async function ensurePayload({root=cacheRoot(),fetchArchive=download,runTar=spawnSync,expected=sha256}={}) {
53
- const payload=path.join(root,'package');
54
- const archive=path.join(root,'runtime.tgz');
55
- const entry=path.join(payload,'bin','npx-onboard.cjs');
56
- fs.mkdirSync(root,{recursive:true});
57
- if(fs.lstatSync(root).isSymbolicLink())throw Error('Runtime cache cannot be a symbolic link.');
58
- if(fs.existsSync(entry)&&verified(archive,expected))return entry;
59
- if(!verified(archive,expected)) {
60
- const temporary=path.join(root,`runtime-${process.pid}-${crypto.randomBytes(6).toString('hex')}.tgz`);
61
- try {
62
- console.log('Downloading the pinned PC Remote Commander staging runtime...');
63
- await fetchArchive(temporary);
64
- if(!verified(temporary,expected))throw Error('Runtime SHA-256 verification failed. No downloaded code was run.');
65
- if(fs.existsSync(archive))fs.rmSync(archive,{force:true});
66
- fs.renameSync(temporary,archive);
67
- } finally {fs.rmSync(temporary,{force:true});}
68
- }
69
- const staging=fs.mkdtempSync(path.join(root,'unpack-'));
70
- try {
71
- const listing=runTar('tar.exe',['-tzf',archive],{encoding:'utf8',windowsHide:true,maxBuffer:1024*1024});
72
- if(listing.error||listing.status!==0)throw Error('Could not inspect the runtime archive. Windows tar.exe is required.');
73
- validateEntries(listing.stdout);
74
- const extracted=runTar('tar.exe',['-xzf',archive,'-C',staging],{encoding:'utf8',windowsHide:true,maxBuffer:1024*1024});
75
- if(extracted.error||extracted.status!==0)throw Error('Could not unpack the verified runtime archive.');
76
- if(!fs.existsSync(path.join(staging,'package','bin','npx-onboard.cjs')))throw Error('Verified runtime archive is missing its launcher.');
77
- if(path.relative(root,payload)!=='package')throw Error('Unsafe runtime cache path.');
78
- if(fs.existsSync(payload))fs.rmSync(payload,{recursive:true,force:true});
79
- fs.renameSync(path.join(staging,'package'),payload);
80
- } finally {fs.rmSync(staging,{recursive:true,force:true});}
81
- return entry;
82
- }
83
- async function main(argv=process.argv.slice(2)) {
84
- if(Number(process.versions.node.split('.')[0])<20)throw Error('Node.js 20 or newer is required.');
85
- if(['help','--help','-h'].includes(argv[0])) {
86
- console.log('PC Remote Commander staging beta\n\nUsage: pcremotecommander [setup|connect|cli|doctor] [options]\nSetup downloads a pinned runtime, verifies its SHA-256, then starts pairing. Windows and Node.js 20+ are required.');
87
- return;
88
- }
89
- const entry=await ensurePayload();
90
- const child=spawn(process.execPath,[entry,...argv],{stdio:'inherit',windowsHide:false,shell:false});
91
- child.on('error',error=>{console.error('PC Remote Commander runtime could not start: '+error.message);process.exitCode=1;});
92
- child.on('exit',(code,signal)=>{process.exitCode=signal?130:code??1;});
93
- }
94
- if(require.main===module)main().catch(error=>{console.error('PC Remote Commander: '+error.message);process.exitCode=1;});
95
- module.exports={cacheRoot,shaFile,verified,validateEntries,ensurePayload,main};
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ const fs=require('node:fs');
4
+ const path=require('node:path');
5
+ const os=require('node:os');
6
+ const https=require('node:https');
7
+ const crypto=require('node:crypto');
8
+ const {pipeline}=require('node:stream/promises');
9
+ const {spawn,spawnSync}=require('node:child_process');
10
+
11
+ const version='0.7.0-beta.6';
12
+ const sha256='a3688032221bab50aba8a8fd18e26429ab538475cc99d6c6603ffd32e12f5efa';
13
+ const url='https://pc-remote-commander-staging.pcremotecommander.workers.dev/downloads/windows/pcremotecommander-0.7.0-beta.6.tgz';
14
+ const maximumBytes=100*1024*1024;
15
+
16
+ function cacheRoot(env=process.env) {
17
+ if(process.platform!=='win32'||!env.LOCALAPPDATA||!path.isAbsolute(env.LOCALAPPDATA))throw Error('This staging preview requires Windows with a normal user profile.');
18
+ return path.join(env.LOCALAPPDATA,'PCRemoteCommander','packages',version);
19
+ }
20
+ function shaFile(file) {
21
+ const hash=crypto.createHash('sha256');
22
+ const fd=fs.openSync(file,'r');
23
+ try {const buffer=Buffer.allocUnsafe(1024*1024);for(;;){const length=fs.readSync(fd,buffer,0,buffer.length,null);if(!length)break;hash.update(buffer.subarray(0,length));}} finally {fs.closeSync(fd);}
24
+ return hash.digest('hex');
25
+ }
26
+ function verified(file,expected=sha256) {return fs.existsSync(file)&&shaFile(file)===expected;}
27
+ function validateEntries(output) {
28
+ const entries=output.split(/\r?\n/).filter(Boolean);
29
+ if(!entries.length)throw Error('Downloaded runtime archive is empty.');
30
+ for(const entry of entries) {
31
+ const parts=entry.split('/');
32
+ if(parts[0]!=='package'||entry.startsWith('/')||entry.includes('\\')||parts.some(part=>part==='..'))throw Error('Downloaded runtime archive has an unsafe path.');
33
+ }
34
+ return entries.length;
35
+ }
36
+ function download(file,request=https.get) {
37
+ return new Promise((resolve,reject)=>{
38
+ const output=fs.createWriteStream(file,{flags:'wx',mode:0o600});
39
+ output.on('error',reject);
40
+ const req=request(url,response=>{
41
+ if(response.statusCode!==200){response.resume();output.destroy();reject(Error(`Runtime download failed (HTTP ${response.statusCode}). Try again later.`));return;}
42
+ const length=Number(response.headers['content-length']);
43
+ if(!Number.isFinite(length)||length<1||length>maximumBytes){response.resume();output.destroy();reject(Error('Runtime download has an invalid size.'));return;}
44
+ let received=0;
45
+ response.on('data',chunk=>{received+=chunk.length;if(received>maximumBytes)response.destroy(Error('Runtime download exceeded its size limit.'));});
46
+ pipeline(response,output).then(()=>received===length?resolve():reject(Error('Runtime download was incomplete.')),reject);
47
+ });
48
+ req.setTimeout(120000,()=>req.destroy(Error('Runtime download timed out.')));
49
+ req.on('error',error=>{output.destroy();reject(error);});
50
+ });
51
+ }
52
+ async function ensurePayload({root=cacheRoot(),fetchArchive=download,runTar=spawnSync,expected=sha256}={}) {
53
+ const payload=path.join(root,'package');
54
+ const archive=path.join(root,'runtime.tgz');
55
+ const entry=path.join(payload,'bin','npx-onboard.cjs');
56
+ fs.mkdirSync(root,{recursive:true});
57
+ if(fs.lstatSync(root).isSymbolicLink())throw Error('Runtime cache cannot be a symbolic link.');
58
+ if(fs.existsSync(entry)&&verified(archive,expected))return entry;
59
+ if(!verified(archive,expected)) {
60
+ const temporary=path.join(root,`runtime-${process.pid}-${crypto.randomBytes(6).toString('hex')}.tgz`);
61
+ try {
62
+ console.log('Downloading the pinned PC Remote Commander staging runtime...');
63
+ await fetchArchive(temporary);
64
+ if(!verified(temporary,expected))throw Error('Runtime SHA-256 verification failed. No downloaded code was run.');
65
+ if(fs.existsSync(archive))fs.rmSync(archive,{force:true});
66
+ fs.renameSync(temporary,archive);
67
+ } finally {fs.rmSync(temporary,{force:true});}
68
+ }
69
+ const staging=fs.mkdtempSync(path.join(root,'unpack-'));
70
+ try {
71
+ const listing=runTar('tar.exe',['-tzf',archive],{encoding:'utf8',windowsHide:true,maxBuffer:1024*1024});
72
+ if(listing.error||listing.status!==0)throw Error('Could not inspect the runtime archive. Windows tar.exe is required.');
73
+ validateEntries(listing.stdout);
74
+ const extracted=runTar('tar.exe',['-xzf',archive,'-C',staging],{encoding:'utf8',windowsHide:true,maxBuffer:1024*1024});
75
+ if(extracted.error||extracted.status!==0)throw Error('Could not unpack the verified runtime archive.');
76
+ if(!fs.existsSync(path.join(staging,'package','bin','npx-onboard.cjs')))throw Error('Verified runtime archive is missing its launcher.');
77
+ if(path.relative(root,payload)!=='package')throw Error('Unsafe runtime cache path.');
78
+ if(fs.existsSync(payload))fs.rmSync(payload,{recursive:true,force:true});
79
+ fs.renameSync(path.join(staging,'package'),payload);
80
+ } finally {fs.rmSync(staging,{recursive:true,force:true});}
81
+ return entry;
82
+ }
83
+ async function main(argv=process.argv.slice(2)) {
84
+ if(Number(process.versions.node.split('.')[0])<20)throw Error('Node.js 20 or newer is required.');
85
+ if(['help','--help','-h'].includes(argv[0])) {
86
+ console.log('PC Remote Commander staging beta\n\nUsage: pcremotecommander [setup|connect|cli|doctor] [options]\nSetup downloads a pinned runtime, verifies its SHA-256, then starts pairing. Windows and Node.js 20+ are required.');
87
+ return;
88
+ }
89
+ const entry=await ensurePayload();
90
+ const child=spawn(process.execPath,[entry,...argv],{stdio:'inherit',windowsHide:false,shell:false});
91
+ child.on('error',error=>{console.error('PC Remote Commander runtime could not start: '+error.message);process.exitCode=1;});
92
+ child.on('exit',(code,signal)=>{process.exitCode=signal?130:code??1;});
93
+ }
94
+ if(require.main===module)main().catch(error=>{console.error('PC Remote Commander: '+error.message);process.exitCode=1;});
95
+ module.exports={cacheRoot,shaFile,verified,validateEntries,ensurePayload,main};
package/package.json CHANGED
@@ -1,11 +1,22 @@
1
- {
2
- "name": "pcremotecommander",
3
- "version": "0.7.0-beta.5",
4
- "description": "PC Remote Commander Windows staging launcher; downloads a pinned full-control runtime",
5
- "license": "UNLICENSED",
6
- "bin": {"pcremotecommander": "bin/pcremotecommander.cjs"},
7
- "os": ["win32"],
8
- "engines": {"node": ">=20"},
9
- "publishConfig": {"access": "public", "tag": "beta"},
10
- "files": ["bin"]
11
- }
1
+ {
2
+ "name": "pcremotecommander",
3
+ "version": "0.7.0-beta.6",
4
+ "description": "PC Remote Commander Windows staging launcher; downloads a pinned full-control runtime",
5
+ "license": "UNLICENSED",
6
+ "bin": {
7
+ "pcremotecommander": "bin/pcremotecommander.cjs"
8
+ },
9
+ "os": [
10
+ "win32"
11
+ ],
12
+ "engines": {
13
+ "node": ">=20"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public",
17
+ "tag": "beta"
18
+ },
19
+ "files": [
20
+ "bin"
21
+ ]
22
+ }