@tera-system/core 0.1.3 → 0.1.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/MANIFEST.json +1 -1
- package/README.md +80 -23
- package/package.json +1 -1
- package/scripts/install.js +123 -11
package/MANIFEST.json
CHANGED
package/README.md
CHANGED
|
@@ -61,49 +61,106 @@ node scripts/install.js --target /path/to/workspace
|
|
|
61
61
|
| تثبيت الحزمة على مشاريعك | نسخ المحتوى لمنتج منافس |
|
|
62
62
|
| استخدامها في مشاريعك | توزيعها دون اتفاق ترخيص |
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## كيفية تفعيل الرخصة (للعملاء)
|
|
67
|
+
|
|
68
|
+
### الخطوة 1: التثبيت
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm install @tera-system/core
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
عند التثبيت الأول، تعمل الحزمة في **وضع تجريبي** (Trial). يمكنك استخدامها لكن بعض الميزات قد تكون محدودة.
|
|
75
|
+
|
|
76
|
+
### الخطوة 2: شراء الرخصة
|
|
77
|
+
|
|
78
|
+
تواصل معنا للحصول على مفتاح التفعيل:
|
|
79
|
+
|
|
80
|
+
> **البريد:** teranoo.com@gmail.com
|
|
81
|
+
|
|
82
|
+
سنرسل لك مفتاحاً فريداً يحتوي على:
|
|
83
|
+
- **المستوى** (Basic / Premium)
|
|
84
|
+
- **اسم المشروع**
|
|
85
|
+
- **تاريخ الانتهاء**
|
|
86
|
+
- **عدد المقاعد**
|
|
87
|
+
|
|
88
|
+
### الخطوة 3: تفعيل المفتاح
|
|
89
|
+
|
|
90
|
+
#### الطريقة 1: عبر متغير البيئة (مُوصى به)
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Windows PowerShell
|
|
94
|
+
$env:TERA_LICENSE_KEY = "المفتاح_الذي_تلقيناه"
|
|
65
95
|
|
|
66
|
-
|
|
67
|
-
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
96
|
+
# ثم أعد التثبيت
|
|
97
|
+
npm install @tera-system/core
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
#### الطريقة 2: عبر ملف إعدادات
|
|
101
|
+
|
|
102
|
+
أنشئ ملف `.tera/license.json` في جذر مشروعك:
|
|
71
103
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
- **مع مفتاح**: يعمل بالكامل
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"licenseKey": "المفتاح_الذي_تلقيناه"
|
|
107
|
+
}
|
|
108
|
+
```
|
|
78
109
|
|
|
79
|
-
|
|
110
|
+
ثم شغّل:
|
|
80
111
|
|
|
81
112
|
```bash
|
|
82
|
-
|
|
83
|
-
/tera-update
|
|
113
|
+
node scripts/tera-license.mjs check --key "المفتاح_الذي_تلقيناه"
|
|
84
114
|
```
|
|
85
115
|
|
|
86
|
-
###
|
|
116
|
+
### الخطوة 4: التحقق
|
|
87
117
|
|
|
88
118
|
```bash
|
|
89
|
-
|
|
119
|
+
# تحقق من حالة الرخصة
|
|
120
|
+
node scripts/tera-license.mjs check --key "المفتاح_الذي_تلقيناه"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**النتيجة المتوقعة:**
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"state": "Active",
|
|
128
|
+
"reason": "valid",
|
|
129
|
+
"payload": {
|
|
130
|
+
"tier": "premium",
|
|
131
|
+
"project": "اسم_مشروعك",
|
|
132
|
+
"expiresAt": "2027-08-21T00:00:00.000Z"
|
|
133
|
+
}
|
|
134
|
+
}
|
|
90
135
|
```
|
|
91
136
|
|
|
92
137
|
---
|
|
93
138
|
|
|
94
|
-
##
|
|
139
|
+
## حالات الرخصة
|
|
95
140
|
|
|
96
|
-
|
|
141
|
+
| الحالة | المعنى | ماذا يحدث |
|
|
142
|
+
|--------|--------|-----------|
|
|
143
|
+
| `Active` | رخصة صالحة | يعمل بالكامل ✅ |
|
|
144
|
+
| `Grace` | منتهية (ضمن 7 أيام) | يعمل مع تحذير ⚠️ |
|
|
145
|
+
| `Expired` | منتهية (خارج السماح) | يتوقف عن العمل ❌ |
|
|
146
|
+
| `Invalid` | مفتاح خاطئ | لا يعمل ❌ |
|
|
147
|
+
| `Unlicensed` | بدون مفتاح | وضع تجريبي 🔓 |
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## تحديث الرخصة
|
|
152
|
+
|
|
153
|
+
### للحصول على إصدار أحدث:
|
|
97
154
|
|
|
98
155
|
```bash
|
|
99
156
|
npm install @tera-system/core@latest
|
|
100
157
|
```
|
|
101
158
|
|
|
102
|
-
|
|
159
|
+
### لتجديد الاشتراك:
|
|
103
160
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
161
|
+
تواصل معنا قبل انتهاء الرخصة وسنرسل مفتاحاً جديداً.
|
|
162
|
+
|
|
163
|
+
---
|
|
107
164
|
|
|
108
165
|
## الدعم
|
|
109
166
|
|
package/package.json
CHANGED
package/scripts/install.js
CHANGED
|
@@ -213,23 +213,135 @@ fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
|
213
213
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
214
214
|
|
|
215
215
|
/* ------------------- license state (non-blocking, never breaks install) ------------------- */
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* License verification with API-first strategy + offline fallback.
|
|
219
|
+
*
|
|
220
|
+
* Flow:
|
|
221
|
+
* 1. Try online API verification (if API URL is configured)
|
|
222
|
+
* 2. On API failure → fallback to offline Ed25519 verification
|
|
223
|
+
* 3. Never blocks install — always continues
|
|
224
|
+
*
|
|
225
|
+
* Environment variables:
|
|
226
|
+
* TERA_LICENSE_API — API base URL (e.g. https://teranoo.com/api/license)
|
|
227
|
+
* TERA_LICENSE_KEY — Manual license key override
|
|
228
|
+
*/
|
|
229
|
+
const LICENSE_API_URL = process.env.TERA_LICENSE_API || "https://teranoo.com/api/license";
|
|
230
|
+
const LICENSE_TIMEOUT_MS = 5000; // 5 seconds max for API call
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Verify license via remote API (online).
|
|
234
|
+
* Returns: { ok: boolean, state: string, reason: string, expiresAt?: string, source: "api" }
|
|
235
|
+
*/
|
|
236
|
+
async function verifyViaAPI(licenseKey) {
|
|
237
|
+
if (!licenseKey) return { ok: false, state: "Unlicensed", reason: "no-key", source: "api" };
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
const controller = new AbortController();
|
|
241
|
+
const timeout = setTimeout(() => controller.abort(), LICENSE_TIMEOUT_MS);
|
|
242
|
+
|
|
243
|
+
const res = await fetch(`${LICENSE_API_URL}/verify`, {
|
|
244
|
+
method: "POST",
|
|
245
|
+
headers: { "Content-Type": "application/json" },
|
|
246
|
+
body: JSON.stringify({ licenseKey }),
|
|
247
|
+
signal: controller.signal,
|
|
248
|
+
});
|
|
249
|
+
clearTimeout(timeout);
|
|
250
|
+
|
|
251
|
+
if (!res.ok) {
|
|
252
|
+
return { ok: false, state: "Invalid", reason: `api-http-${res.status}`, source: "api" };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const data = await res.json();
|
|
256
|
+
return {
|
|
257
|
+
ok: data.valid === true,
|
|
258
|
+
state: data.state || "Invalid",
|
|
259
|
+
reason: data.reason || "api-response",
|
|
260
|
+
expiresAt: data.expiresAt || null,
|
|
261
|
+
source: "api",
|
|
262
|
+
};
|
|
263
|
+
} catch (err) {
|
|
264
|
+
// Network error, timeout, abort — fall through to offline
|
|
265
|
+
return { ok: false, state: "Unknown", reason: `api-error: ${err.message}`, source: "api-fallback" };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Verify license offline (Ed25519 signature check).
|
|
271
|
+
* Returns: { ok: boolean, state: string, reason: string, expiresAt?: string, source: "offline" }
|
|
272
|
+
*/
|
|
273
|
+
function verifyOffline(licenseKey) {
|
|
220
274
|
const state = assessLicenseState(licenseKey);
|
|
221
|
-
|
|
275
|
+
return {
|
|
276
|
+
ok: state.state === "Active" || state.state === "Grace",
|
|
222
277
|
state: state.state,
|
|
223
278
|
reason: state.reason,
|
|
224
|
-
licenseKey: licenseKey || null,
|
|
225
279
|
expiresAt: state.expiresAt || null,
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
} catch (err) {
|
|
230
|
-
console.log(` [license ] skipped (${err.message}) — install continues`);
|
|
280
|
+
graceUntil: state.graceUntil || null,
|
|
281
|
+
source: "offline",
|
|
282
|
+
};
|
|
231
283
|
}
|
|
232
284
|
|
|
285
|
+
/**
|
|
286
|
+
* Combined verification: API-first, offline fallback.
|
|
287
|
+
* Always returns a result — never throws.
|
|
288
|
+
*/
|
|
289
|
+
async function verifyLicense(licenseKey) {
|
|
290
|
+
// Step 1: Try API (unless explicitly disabled)
|
|
291
|
+
if (process.env.TERA_OFFLINE_ONLY === "1") {
|
|
292
|
+
return verifyOffline(licenseKey);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const apiResult = await verifyViaAPI(licenseKey);
|
|
296
|
+
|
|
297
|
+
// If API succeeded and gave a definitive answer, use it
|
|
298
|
+
if (apiResult.source === "api" && apiResult.ok !== undefined) {
|
|
299
|
+
return apiResult;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Step 2: Fallback to offline
|
|
303
|
+
const offlineResult = verifyOffline(licenseKey);
|
|
304
|
+
|
|
305
|
+
// If offline confirms API's failure, use offline (more trusted)
|
|
306
|
+
if (!apiResult.ok && !offlineResult.ok) {
|
|
307
|
+
return offlineResult;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// If API failed but offline says active, trust offline (API might be down)
|
|
311
|
+
if (!apiResult.ok && offlineResult.ok) {
|
|
312
|
+
return { ...offlineResult, reason: `offline-verified (api-unavailable: ${apiResult.reason})` };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Default: trust API
|
|
316
|
+
return apiResult;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Run license check (async wrapper, non-blocking)
|
|
320
|
+
(async () => {
|
|
321
|
+
try {
|
|
322
|
+
const stateFile = path.join(target, ".tera", "license.state.json");
|
|
323
|
+
let licenseKey = (readLicenseState(stateFile) || {}).licenseKey;
|
|
324
|
+
if (!licenseKey) licenseKey = process.env.TERA_LICENSE_KEY || undefined;
|
|
325
|
+
|
|
326
|
+
const result = await verifyLicense(licenseKey);
|
|
327
|
+
|
|
328
|
+
writeLicenseState(stateFile, {
|
|
329
|
+
state: result.state,
|
|
330
|
+
reason: result.reason,
|
|
331
|
+
licenseKey: licenseKey || null,
|
|
332
|
+
expiresAt: result.expiresAt || null,
|
|
333
|
+
graceUntil: result.graceUntil || null,
|
|
334
|
+
source: result.source,
|
|
335
|
+
checkedAt: new Date().toISOString(),
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const sourceTag = result.source === "api" ? "☁️" : result.source === "offline" ? "🔒" : "🔄";
|
|
339
|
+
console.log(` [license ] ${sourceTag} state=${result.state} (${result.reason})`);
|
|
340
|
+
} catch (err) {
|
|
341
|
+
console.log(` [license ] skipped (${err.message}) — install continues`);
|
|
342
|
+
}
|
|
343
|
+
})();
|
|
344
|
+
|
|
233
345
|
console.log("\n summary:");
|
|
234
346
|
console.log(` installed : ${stats.installed}`);
|
|
235
347
|
console.log(` updated : ${stats.updated}`);
|