@ade-syofyan/feature-map-pro 0.0.0-stage → 0.21.0-pro.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +3 -0
- package/README.md +23 -2
- package/bin/feature-map-pro.mjs +29 -0
- package/lib/installer.mjs +147 -0
- package/package.json +31 -4
- package/release.json +24 -0
package/LICENSE
ADDED
package/README.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Feature Map Pro Installer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Installer gratis untuk Feature Map Pro. Fitur premium memerlukan lisensi per email.
|
|
4
|
+
Source mesin Pro tidak termasuk dalam paket npm; binary diunduh dari rilis resmi.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npx @ade-syofyan/feature-map-pro@preview install
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Masukkan email pembelian dan kode lisensi dari Ade Syofyan. Anda juga bisa memakai
|
|
11
|
+
`--license-file /path/to/buyer.license`. Jangan memasukkan kode ke argumen command.
|
|
12
|
+
Setelah aktif, buka sesi agent baru. Status: `npx @ade-syofyan/feature-map-pro@preview license status`.
|
|
13
|
+
|
|
14
|
+
Platform yang tersedia tercantum di release.json: macOS Apple Silicon, Windows x64,
|
|
15
|
+
dan Linux x64 (glibc, Ubuntu 22.04 atau lebih baru). Windows ARM, Linux ARM dan
|
|
16
|
+
Alpine/musl belum didukung. Windows memakai tar bawaan Windows 10/11. Node 20+, tar, Git, serta Python 3
|
|
17
|
+
untuk hook/core Free diperlukan. Installer memasang skill Codex; `--activate-only`
|
|
18
|
+
mengaktifkan tanpa memasang skill. Plugin Claude Code dapat dipasang manual dari
|
|
19
|
+
folder paket melalui pengelola plugin, dengan satu sumber feature-map aktif.
|
|
20
|
+
|
|
21
|
+
Lisensi diverifikasi offline sebelum instalasi skill dan setiap command premium.
|
|
22
|
+
Instalasi lama disalin ke folder backup di luar folder skills. Core Free tetap MIT.
|
|
23
|
+
Kompilasi mempersulit pembacaan source; bukan jaminan anti-reverse-engineering
|
|
24
|
+
atau pencegahan penyalinan binary. Pembelian/harga: ade.syofyan@gmail.com.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import {fileURLToPath} from 'node:url';
|
|
4
|
+
import {activateAndInstall,ensurePayload,installSkills,runEngine} from '../lib/installer.mjs';
|
|
5
|
+
|
|
6
|
+
const manifest=JSON.parse(fs.readFileSync(fileURLToPath(new URL('../release.json',import.meta.url)),'utf8'));
|
|
7
|
+
const args=process.argv.slice(2);
|
|
8
|
+
if(!args.length||args[0]==='--help'||args[0]==='help') {
|
|
9
|
+
console.log(`Feature Map Pro ${manifest.version}\n\nPemakaian:\n feature-map-pro install [--email email] [--license-file file] [--activate-only]\n feature-map-pro license status\n feature-map-pro <perintah-pro> [opsi]\n\nInstaller gratis; fitur Pro memerlukan lisensi pembelian.\nDukungan platform mengikuti paket yang tersedia pada rilis.`);
|
|
10
|
+
} else {
|
|
11
|
+
try {
|
|
12
|
+
const root=await ensurePayload(manifest);
|
|
13
|
+
if(args[0]==='install') {
|
|
14
|
+
const activation=[];let activateOnly=false;
|
|
15
|
+
for(let i=1;i<args.length;i++) {
|
|
16
|
+
if(args[i]==='--activate-only') {activateOnly=true;continue;}
|
|
17
|
+
if(!['--email','--license-file'].includes(args[i])||!args[i+1]) throw new Error('Opsi instalasi tidak dikenal. Gunakan --help.');
|
|
18
|
+
activation.push(args[i]==='--license-file'?'--file':args[i],args[++i]);
|
|
19
|
+
}
|
|
20
|
+
activateAndInstall(()=>runEngine(root,['license','activate',...activation]),()=>{
|
|
21
|
+
const backup=activateOnly?null:installSkills(root);
|
|
22
|
+
console.log('Pro aktif. Buka sesi baru untuk memuat instruksi Pro.');
|
|
23
|
+
if(backup) console.log('Salinan skill sebelumnya: '+backup);
|
|
24
|
+
});
|
|
25
|
+
} else {
|
|
26
|
+
process.exitCode=runEngine(root,args).status??2;
|
|
27
|
+
}
|
|
28
|
+
} catch(error) {console.error('Gagal: '+error.message);process.exitCode=2;}
|
|
29
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import {createHash,randomUUID} from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import {spawnSync} from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
const MAX_DOWNLOAD=384*1024*1024;
|
|
8
|
+
const HEX=/^[a-f0-9]{64}$/;
|
|
9
|
+
const VERSION=/^\d+\.\d+\.\d+(?:-[a-z0-9.]+)?$/;
|
|
10
|
+
const DOWNLOAD_HOSTS=new Set(['github.com','release-assets.githubusercontent.com','objects.githubusercontent.com']);
|
|
11
|
+
|
|
12
|
+
function trustedURL(value) {
|
|
13
|
+
const url=new URL(value);
|
|
14
|
+
if(url.protocol!=='https:'||url.username||url.password||!DOWNLOAD_HOSTS.has(url.hostname))
|
|
15
|
+
throw new Error('URL unduhan tidak tepercaya.');
|
|
16
|
+
return url;
|
|
17
|
+
}
|
|
18
|
+
export function selectArtifact(manifest,platform=process.platform,arch=process.arch) {
|
|
19
|
+
if(manifest?.schema_version!==1||!VERSION.test(manifest.version)||!manifest.artifacts)
|
|
20
|
+
throw new Error('Manifest rilis tidak valid.');
|
|
21
|
+
const entry=manifest.artifacts[`${platform}-${arch}`];
|
|
22
|
+
if(!entry) throw new Error(`Paket belum tersedia untuk platform ${platform}-${arch}.`);
|
|
23
|
+
if(!HEX.test(entry.sha256)||!HEX.test(entry.engine_sha256)||!Number.isSafeInteger(entry.size)||entry.size<1||entry.size>MAX_DOWNLOAD)
|
|
24
|
+
throw new Error('Metadata unduhan tidak valid.');
|
|
25
|
+
const url=trustedURL(entry.url);
|
|
26
|
+
if(url.hostname!=='github.com'||!url.pathname.startsWith('/ade-syofyan/feature-map-installer/releases/download/'))
|
|
27
|
+
throw new Error('Lokasi rilis tidak tepercaya.');
|
|
28
|
+
return entry;
|
|
29
|
+
}
|
|
30
|
+
export function verifyChecksum(data,expected) {
|
|
31
|
+
if(!HEX.test(expected)||createHash('sha256').update(data).digest('hex')!==expected)
|
|
32
|
+
throw new Error('Checksum paket berbeda. Unduhan dibatalkan.');
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
export function validateArchiveNames(listing) {
|
|
36
|
+
const names=listing.split(/\r?\n/).filter(Boolean);
|
|
37
|
+
if(!names.length) throw new Error('Arsip kosong.');
|
|
38
|
+
for(const name of names) {
|
|
39
|
+
if(!/^feature-map-pro\/(?:[A-Za-z0-9._+@/-]*)$/.test(name)||name.split('/').some(p=>p==='..'||p==='.'))
|
|
40
|
+
throw new Error('Path arsip tidak aman.');
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
export function activateAndInstall(activate,install) {
|
|
45
|
+
const result=activate();
|
|
46
|
+
if(result.status!==0) throw new Error('Aktivasi lisensi gagal. Instalasi skill dihentikan.');
|
|
47
|
+
return install();
|
|
48
|
+
}
|
|
49
|
+
export async function download(entry) {
|
|
50
|
+
let url=trustedURL(entry.url);
|
|
51
|
+
for(let redirects=0;redirects<=5;redirects++) {
|
|
52
|
+
const response=await fetch(url,{redirect:'manual',signal:AbortSignal.timeout(180000)});
|
|
53
|
+
if([301,302,303,307,308].includes(response.status)) {
|
|
54
|
+
const location=response.headers.get('location');
|
|
55
|
+
if(!location) throw new Error('Redirect unduhan tidak valid.');
|
|
56
|
+
url=trustedURL(new URL(location,url).href);continue;
|
|
57
|
+
}
|
|
58
|
+
if(!response.ok||!response.body) throw new Error(`Unduhan gagal (${response.status}).`);
|
|
59
|
+
const chunks=[];let size=0;
|
|
60
|
+
for await(const part of response.body) {
|
|
61
|
+
size+=part.byteLength;
|
|
62
|
+
if(size>entry.size||size>MAX_DOWNLOAD) throw new Error('Ukuran unduhan melebihi metadata rilis.');
|
|
63
|
+
chunks.push(Buffer.from(part));
|
|
64
|
+
}
|
|
65
|
+
if(size!==entry.size) throw new Error('Unduhan belum lengkap.');
|
|
66
|
+
const data=Buffer.concat(chunks);verifyChecksum(data,entry.sha256);return data;
|
|
67
|
+
}
|
|
68
|
+
throw new Error('Terlalu banyak redirect unduhan.');
|
|
69
|
+
}
|
|
70
|
+
export function enginePath(root,platform=process.platform) { return path.join(root,'runtime','engine',platform==='win32'?'feature-map-engine.exe':'feature-map-engine'); }
|
|
71
|
+
function engineValid(root,entry,platform) {
|
|
72
|
+
try {
|
|
73
|
+
const file=enginePath(root,platform);
|
|
74
|
+
if(fs.lstatSync(file).isSymbolicLink()||!fs.statSync(file).isFile()) return false;
|
|
75
|
+
verifyChecksum(fs.readFileSync(file),entry.engine_sha256);return true;
|
|
76
|
+
} catch {return false;}
|
|
77
|
+
}
|
|
78
|
+
function tar(args) {
|
|
79
|
+
const result=spawnSync('tar',args,{encoding:'utf8',maxBuffer:8*1024*1024});
|
|
80
|
+
if(result.error||result.status!==0) throw new Error('Paket tidak bisa dibaca. Pastikan tar tersedia.');
|
|
81
|
+
return result.stdout;
|
|
82
|
+
}
|
|
83
|
+
export async function ensurePayload(manifest,{home=process.env.FEATURE_MAP_PRO_HOME||path.join(os.homedir(),'.local','share','feature-map-pro'),fetchArtifact=download,platform=process.platform,arch=process.arch}={}) {
|
|
84
|
+
const entry=selectArtifact(manifest,platform,arch);
|
|
85
|
+
const base=path.resolve(home,'releases',manifest.version,`${platform}-${arch}`);
|
|
86
|
+
const root=path.join(base,'feature-map-pro');
|
|
87
|
+
const marker=path.join(base,'artifact.sha256');
|
|
88
|
+
if(fs.existsSync(base)&&fs.lstatSync(base).isSymbolicLink()) throw new Error('Folder instalasi tidak boleh berupa symlink.');
|
|
89
|
+
if(fs.existsSync(marker)&&fs.readFileSync(marker,'utf8')===entry.sha256&&engineValid(root,entry,platform)) return root;
|
|
90
|
+
const parent=path.dirname(base);fs.mkdirSync(parent,{recursive:true});
|
|
91
|
+
const staging=fs.mkdtempSync(path.join(parent,'.download-'));
|
|
92
|
+
try {
|
|
93
|
+
console.log('Mengunduh mesin Feature Map Pro…');
|
|
94
|
+
const data=await fetchArtifact(entry);verifyChecksum(data,entry.sha256);
|
|
95
|
+
const archive=path.join(staging,'download.tar.gz');fs.writeFileSync(archive,data,{mode:0o600});
|
|
96
|
+
validateArchiveNames(tar(['-tzf',archive]));
|
|
97
|
+
const types=tar(['-tvzf',archive]).split(/\r?\n/).filter(Boolean);
|
|
98
|
+
if(types.some(line=>!['-','d'].includes(line[0]))) throw new Error('Arsip mengandung link atau file khusus.');
|
|
99
|
+
const extracted=path.join(staging,'extracted');fs.mkdirSync(extracted);
|
|
100
|
+
tar(['-xzf',archive,'-C',extracted,'--no-same-owner']);
|
|
101
|
+
const extractedRoot=path.join(extracted,'feature-map-pro');
|
|
102
|
+
if(!engineValid(extractedRoot,entry,platform)) throw new Error('Mesin Pro tidak cocok dengan rilis.');
|
|
103
|
+
const previous=base+'.previous-'+randomUUID();
|
|
104
|
+
const hadPrevious=fs.existsSync(base);
|
|
105
|
+
if(hadPrevious) fs.renameSync(base,previous);
|
|
106
|
+
try {fs.renameSync(extracted,base);} catch(error) {if(hadPrevious) fs.renameSync(previous,base);throw error;}
|
|
107
|
+
fs.writeFileSync(marker,entry.sha256,{mode:0o600});
|
|
108
|
+
if(hadPrevious) fs.rmSync(previous,{recursive:true,force:true});
|
|
109
|
+
return root;
|
|
110
|
+
} finally {fs.rmSync(staging,{recursive:true,force:true});}
|
|
111
|
+
}
|
|
112
|
+
export function installSkills(root,codexHome=process.env.CODEX_HOME||path.join(os.homedir(),'.codex')) {
|
|
113
|
+
const names=['feature-map','feature-map-pro'];
|
|
114
|
+
const id=randomUUID();const staging=path.join(codexHome,'feature-map-stage',id);
|
|
115
|
+
const backups=path.join(codexHome,'feature-map-backups',id);
|
|
116
|
+
const targets=names.map(name=>path.join(codexHome,'skills',name));
|
|
117
|
+
for(const target of targets) if(fs.existsSync(target)&&fs.lstatSync(target).isSymbolicLink())
|
|
118
|
+
throw new Error('Folder skill berupa symlink. Salin konfigurasi Anda sebelum mengganti instalasi.');
|
|
119
|
+
fs.mkdirSync(staging,{recursive:true});
|
|
120
|
+
const moved=[];const installed=[];
|
|
121
|
+
try {
|
|
122
|
+
for(const name of names) {
|
|
123
|
+
const source=path.join(root,'codex-skill',name);
|
|
124
|
+
if(!fs.existsSync(path.join(source,'SKILL.md'))) throw new Error('Skill tidak lengkap pada paket Pro.');
|
|
125
|
+
fs.cpSync(source,path.join(staging,name),{recursive:true});
|
|
126
|
+
fs.writeFileSync(path.join(staging,name,'plugin-root.txt'),root+'\n');
|
|
127
|
+
}
|
|
128
|
+
fs.mkdirSync(path.join(codexHome,'skills'),{recursive:true});
|
|
129
|
+
for(let i=0;i<names.length;i++) {
|
|
130
|
+
if(fs.existsSync(targets[i])) {
|
|
131
|
+
fs.mkdirSync(backups,{recursive:true});const backup=path.join(backups,names[i]);
|
|
132
|
+
fs.renameSync(targets[i],backup);moved.push([backup,targets[i]]);
|
|
133
|
+
}
|
|
134
|
+
fs.renameSync(path.join(staging,names[i]),targets[i]);installed.push(targets[i]);
|
|
135
|
+
}
|
|
136
|
+
} catch(error) {
|
|
137
|
+
for(const target of installed.reverse()) fs.rmSync(target,{recursive:true,force:true});
|
|
138
|
+
for(const [backup,target] of moved.reverse()) fs.renameSync(backup,target);
|
|
139
|
+
throw error;
|
|
140
|
+
} finally {fs.rmSync(staging,{recursive:true,force:true});}
|
|
141
|
+
return moved.length?backups:null;
|
|
142
|
+
}
|
|
143
|
+
export function runEngine(root,args,{stdio='inherit',...options}={}) {
|
|
144
|
+
const result=spawnSync(enginePath(root),args,{stdio,...options});
|
|
145
|
+
if(result.error) throw new Error('Mesin Pro tidak bisa dijalankan: '+result.error.message);
|
|
146
|
+
return result;
|
|
147
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ade-syofyan/feature-map-pro",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.21.0-pro.2",
|
|
4
|
+
"description": "Installer and launcher for licensed Feature Map Pro",
|
|
5
|
+
"private": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"feature-map-pro": "bin/feature-map-pro.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/",
|
|
12
|
+
"lib/",
|
|
13
|
+
"release.json",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=20"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "node --test test/installer.test.mjs"
|
|
22
|
+
},
|
|
23
|
+
"author": "Ade Syofyan <ade.syofyan@gmail.com>",
|
|
24
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public",
|
|
27
|
+
"tag": "preview"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/ade-syofyan/feature-map-installer.git"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/release.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"version": "0.21.0-pro.2",
|
|
4
|
+
"artifacts": {
|
|
5
|
+
"darwin-arm64": {
|
|
6
|
+
"sha256": "fa90315978ee367eb169fb885d360d8e9830769752105e050eb7f95ac49b5a7d",
|
|
7
|
+
"engine_sha256": "189d9b537742f508de6bc2a296245a19d9422e91ce56edf7c55a9fee033266df",
|
|
8
|
+
"size": 14320567,
|
|
9
|
+
"url": "https://github.com/ade-syofyan/feature-map-installer/releases/download/v0.21.0-pro.2/feature-map-pro-0.21.0-pro.2-darwin-arm64.tar.gz"
|
|
10
|
+
},
|
|
11
|
+
"linux-x64": {
|
|
12
|
+
"sha256": "39bf4a0c239c0de291c27c7d7e684fef795f2de5e855072bc46868f2832c7ba4",
|
|
13
|
+
"engine_sha256": "5da00bbd833bc15c3ad0d98017e0a7127df861ac446455768b23b2950f7fee1a",
|
|
14
|
+
"size": 18223616,
|
|
15
|
+
"url": "https://github.com/ade-syofyan/feature-map-installer/releases/download/v0.21.0-pro.2/feature-map-pro-0.21.0-pro.2-linux-x64.tar.gz"
|
|
16
|
+
},
|
|
17
|
+
"win32-x64": {
|
|
18
|
+
"sha256": "948db51f8583753b68dd64d92db4f333601f4c51f9585e8924e2ab71f75c1ca0",
|
|
19
|
+
"engine_sha256": "9a4c4cb6cbe767eb02b9afa9a5e2425194ac6de6f2f593bd716ad53f87424ece",
|
|
20
|
+
"size": 11664438,
|
|
21
|
+
"url": "https://github.com/ade-syofyan/feature-map-installer/releases/download/v0.21.0-pro.2/feature-map-pro-0.21.0-pro.2-win32-x64.tar.gz"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|