v8scli 0.4.0 → 0.4.1

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/README.md CHANGED
@@ -10,7 +10,7 @@ one (the scaffolded project has it as a devDependency).
10
10
  npx v8scli init my_mod # scaffold a new mod in ./my_mod
11
11
  cd my_mod && npm install
12
12
 
13
- npx v8scli build # bundle + dist/my_mod.vjs_c для теста на своём сервере
13
+ npx v8scli build # bundle + dist/my_mod.vjs_c for testing on your own server
14
14
  npx v8scli login # portal url + api token (dpt_...)
15
15
  npx v8scli upload # pack sources, create a version on the portal
16
16
  npx v8scli status # versions + review status
package/bin/v8s.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // v8scli — CLI разработчика модов. Команды: init, build, login, upload, status, deps.
2
+ // v8scli — the mod developer CLI. Commands: see `usage` below.
3
3
  import { init } from '../src/commands/init.js';
4
4
  import { build } from '../src/commands/build.js';
5
5
  import { login } from '../src/commands/login.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "v8scli",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "CLI for building and publishing V8 point_script mods (companion of v8_scripting)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -5,10 +5,10 @@ import { api, readConfig, readManifest } from '../common.js';
5
5
  import { deps } from './deps.js';
6
6
 
7
7
  /**
8
- * v8scli add <slug>[@version] — подключить библиотеку точным пином релиза.
9
- * Без версии берётся последний published; typings скачиваются сразу.
8
+ * v8scli add <slug>[@version] — adds a library pinned to an exact release.
9
+ * Without a version, the latest published release is used; typings are downloaded right away.
10
10
  *
11
- * v8scli update [slug] — поднять пин(ы) до последнего published-релиза.
11
+ * v8scli update [slug] — bumps the pin(s) to the latest published release.
12
12
  */
13
13
 
14
14
  async function resolveLibrary(config, slug) {
@@ -75,7 +75,7 @@ export async function remove(args) {
75
75
  raw.dependencies = dependencies;
76
76
  await writeFile('mod.json', `${JSON.stringify(raw, null, 2)}\n`);
77
77
 
78
- // тайпинги больше не нужны
78
+ // the typings are no longer needed
79
79
  await rm(join('.v8s_types', `${slug}.d.ts`), { force: true });
80
80
 
81
81
  console.log(`removed: ${removed}`);
@@ -3,14 +3,14 @@ import { join } from 'node:path';
3
3
  import { readManifest } from '../common.js';
4
4
  import { ensureGenerated, typecheck } from '../typegen.js';
5
5
 
6
- // Локальная самопроверка: тот же контракт, что на портале —
7
- // только файлы мода + SDK, cs_script/point_script остаётся external.
6
+ // Local self-check with the same contract as on the portal:
7
+ // only mod files + the SDK; cs_script/point_script stays external.
8
8
  export async function build() {
9
9
  const esbuild = await import('esbuild');
10
10
  const manifest = await readManifest();
11
11
  const disallowed = new Set();
12
12
 
13
- // apiVersion 2: ключи мода — типами из mod.json, и сборка без проверки типов не идёт
13
+ // apiVersion 2: mod keys are typed from mod.json, and there is no build without a typecheck
14
14
  if (await ensureGenerated(manifest))
15
15
  typecheck();
16
16
 
@@ -28,7 +28,7 @@ export async function build() {
28
28
  build.onResolve({ filter: /.*/ }, (args) => {
29
29
  if (args.path === 'cs_script/point_script') return { path: args.path, external: true };
30
30
  if (args.path.startsWith('.') || args.path.startsWith('/')) return null;
31
- if (args.path === 'v8_scripting' || args.path.startsWith('v8_scripting/')) return null; // SDK из node_modules
31
+ if (args.path === 'v8_scripting' || args.path.startsWith('v8_scripting/')) return null; // SDK from node_modules
32
32
  disallowed.add(args.path);
33
33
  return { path: args.path, external: true };
34
34
  });
@@ -45,14 +45,14 @@ export async function build() {
45
45
  const jsOut = join('dist', `${manifest.slug}.js`);
46
46
  await writeFile(jsOut, bundle);
47
47
 
48
- // .vjs_c рядом с бандлом: его можно сразу положить на свой сервер в
49
- // addons/scripts_dev и проверить мод, не дожидаясь сборки на портале
48
+ // .vjs_c next to the bundle: it can go straight into addons/scripts_dev on your
49
+ // own server to test the mod without waiting for the portal build
50
50
  const { buildVjsC } = await import('../vjs.js');
51
51
  const resource = buildVjsC(bundle);
52
52
  const resourceOut = join('dist', `${manifest.slug}.vjs_c`);
53
53
  await writeFile(resourceOut, resource);
54
54
 
55
55
  console.log(`ok: ${jsOut} (${bundle.length} bytes)`);
56
- console.log(`ok: ${resourceOut} (${resource.length} bytes) — для локального теста: положить в game/csgo/addons/scripts_dev/`);
57
- console.log('релизный артефакт портал собирает сам из исходников');
56
+ console.log(`ok: ${resourceOut} (${resource.length} bytes) — for local testing, put it in game/csgo/addons/scripts_dev/`);
57
+ console.log('the release artifact is built by the portal from the sources');
58
58
  }
@@ -2,9 +2,9 @@ import { mkdir, writeFile } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
3
  import { api, readConfig, readManifest } from '../common.js';
4
4
 
5
- // Скачивает typings модов-библиотек из dependencies в ./.v8s_types/<slug>.d.ts.
6
- // Рантайм-объект библиотеки берётся через Instance.GetInterface("<slug>") —
7
- // typings дают только типы для import type.
5
+ // Downloads the typings of library mods from dependencies into ./.v8s_types/<slug>.d.ts.
6
+ // The library's runtime object is obtained via Instance.GetInterface("<slug>") —
7
+ // typings only provide types for import type.
8
8
  export async function deps() {
9
9
  const config = await readConfig();
10
10
  const manifest = await readManifest();
@@ -7,8 +7,8 @@ const CLI_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
7
7
  const TEMPLATES = join(CLI_ROOT, 'templates');
8
8
 
9
9
  /**
10
- * Пути до локальных чекаутов SDK и CLI для режима --local (пакеты ещё не в npm).
11
- * SDK ищем в V8S_LOCAL_SDK, иначе рядом с CLI (dev_portal/sdk).
10
+ * Paths to local SDK and CLI checkouts for --local mode (changes not yet published to npm).
11
+ * The SDK is taken from V8S_LOCAL_SDK, otherwise from next to the CLI (dev_portal/sdk).
12
12
  */
13
13
  async function resolveLocalPackages() {
14
14
  const sdk = process.env.V8S_LOCAL_SDK ?? join(CLI_ROOT, '..', 'sdk');
@@ -33,13 +33,13 @@ export async function init(args) {
33
33
  await mkdir(dir, { recursive: true });
34
34
  await cp(TEMPLATES, dir, { recursive: true });
35
35
 
36
- // подстановка slug в шаблоны
36
+ // substitute the slug into the templates
37
37
  for (const file of ['mod.json', 'package.json']) {
38
38
  const path = join(dir, file);
39
39
  await writeFile(path, (await readFile(path, 'utf-8')).replaceAll('__SLUG__', slug));
40
40
  }
41
41
 
42
- // локальный режим: SDK и CLI ставятся из чекаутов (file:), реестр не нужен
42
+ // local mode: the SDK and CLI are installed from checkouts (file:), no registry needed
43
43
  if (local) {
44
44
  const path = join(dir, 'package.json');
45
45
  const manifest = JSON.parse(await readFile(path, 'utf-8'));
@@ -1,7 +1,7 @@
1
1
  import { createInterface } from 'node:readline/promises';
2
2
  import { api, readConfig, writeConfig, CONFIG_PATH } from '../common.js';
3
3
 
4
- // v8scli login [portalUrl] [token] — с аргументами работает без интерактива (CI-friendly).
4
+ // v8scli login [portalUrl] [token] — with arguments it runs non-interactively (CI-friendly).
5
5
  export async function login(args = []) {
6
6
  const config = await readConfig();
7
7
  let [portalUrl, token] = args;
@@ -22,7 +22,7 @@ export async function login(args = []) {
22
22
  if (!token?.startsWith('dpt_')) throw new Error('token must start with dpt_');
23
23
 
24
24
  const next = { portalUrl, token };
25
- await api(next, '/api/registry/health'); // живость портала (и basic auth из url)
25
+ await api(next, '/api/registry/health'); // checks that the portal is reachable (and basic auth from the url)
26
26
  await writeConfig(next);
27
27
  console.log(`saved to ${CONFIG_PATH}`);
28
28
  }
@@ -3,14 +3,14 @@ import { basename, extname } from 'node:path';
3
3
  import { api, readConfig, readManifest } from '../common.js';
4
4
 
5
5
  /**
6
- * Жизненный цикл мода из CLI: создать, описать, дать картинки, отправить.
6
+ * Mod lifecycle from the CLI: create, describe, add images, submit.
7
7
  *
8
- * До этого создание и оформление жили только в веб-панели, и написать мод
9
- * целиком из редактора было нельзя — приходилось прерываться на браузер.
10
- * Все команды работают по API-токену.
8
+ * Previously, creating a mod and filling in its page was only possible in the web panel, so a mod
9
+ * could not be made entirely from the editor — you had to switch to the browser.
10
+ * All commands work with an API token.
11
11
  */
12
12
 
13
- /** modId по slug из mod.json; вызывающему нужен и сам манифест */
13
+ /** modId by the slug from mod.json; the caller needs the manifest itself as well */
14
14
  async function resolveMod(config, manifest) {
15
15
  const { mods } = await api(config, '/api/mods/my');
16
16
  const mod = mods.find((m) => m.slug === manifest.slug);
@@ -18,7 +18,7 @@ async function resolveMod(config, manifest) {
18
18
  return mod;
19
19
  }
20
20
 
21
- /** Последняя версия мода — с ней работают submit и release */
21
+ /** The mod's latest version — the one submit and release work with */
22
22
  async function latestVersion(config, modId) {
23
23
  const { versions } = await api(config, `/api/mods/${modId}`);
24
24
  if (!versions?.length) throw new Error('no versions uploaded yet — run `v8scli upload` first');
@@ -39,7 +39,7 @@ async function uploadImage(config, modId, path, kind) {
39
39
  console.log(`${kind}: ${kind === 'icon' ? mod.iconUrl : mod.coverUrl}`);
40
40
  }
41
41
 
42
- /** v8scli create — заводит мод на портале по mod.json текущей папки */
42
+ /** v8scli create — registers the mod on the portal from mod.json in the current folder */
43
43
  export async function create(args) {
44
44
  const config = await readConfig();
45
45
  const manifest = await readManifest();
@@ -105,14 +105,14 @@ export async function cover(args) {
105
105
  await uploadImage(config, mod.id, args[0], 'cover');
106
106
  }
107
107
 
108
- /** v8scli submit — последнюю загруженную версию на дев-проверку */
108
+ /** v8scli submit — sends the latest uploaded version to dev review */
109
109
  export async function submit() {
110
110
  const config = await readConfig();
111
111
  const mod = await resolveMod(config, await readManifest());
112
112
  const version = await latestVersion(config, mod.id);
113
113
 
114
- // У разработчиков команды загрузка сразу даёт dev_ready — отправлять нечего,
115
- // и это нормальный исход, а не ошибка: сценарий «залил и тестирую» не ломается
114
+ // For team developers, an upload goes straight to dev_ready — there is nothing to submit,
115
+ // and that is a normal outcome, not an error: the "upload and test" flow keeps working
116
116
  if (version.status === 'dev_ready') {
117
117
  console.log(`${version.version}: already dev_ready — ready to test in your lobby`);
118
118
  return;
@@ -127,14 +127,14 @@ export async function submit() {
127
127
  body: JSON.stringify({ action: 'submit_dev' }),
128
128
  });
129
129
 
130
- // У разработчиков команды дев-ревью пропускается, и версия сразу dev_ready
130
+ // For team developers, dev review is skipped and the version goes straight to dev_ready
131
131
  console.log(`${moved.version}: ${moved.status}`);
132
132
  console.log(moved.status === 'dev_ready'
133
133
  ? 'ready to test in your lobby'
134
134
  : 'sent to review');
135
135
  }
136
136
 
137
- /** v8scli release "заметки к релизу" — публикация версии в прод */
137
+ /** v8scli release "release notes" — publishes the version to prod */
138
138
  export async function release(args) {
139
139
  const config = await readConfig();
140
140
  const mod = await resolveMod(config, await readManifest());
@@ -156,7 +156,7 @@ export async function release(args) {
156
156
  console.log(`${moved.version}: ${moved.status} — waiting for an admin to publish it`);
157
157
  }
158
158
 
159
- /** v8scli whoami — чей это токен и можно ли им работать */
159
+ /** v8scli whoami — whose token this is and whether it can be used */
160
160
  export async function whoami() {
161
161
  const config = await readConfig();
162
162
  const { developer } = await api(config, '/api/developers/me');
@@ -8,11 +8,11 @@ import { api, apiBinary, readConfig } from '../common.js';
8
8
  const TEMPLATES = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'templates');
9
9
 
10
10
  /**
11
- * v8scli pull <slug> — скачивает исходники последней версии мода с портала.
11
+ * v8scli pull <slug> — downloads the sources of the mod's latest version from the portal.
12
12
  *
13
- * В папке существующего мода (mod.json с тем же slug) обновляет исходники
14
- * на месте; иначе создаёт ./<slug> с полной обвязкой проекта (package.json,
15
- * tsconfig из шаблона) — «git clone» для мода одной командой.
13
+ * In the folder of an existing mod (mod.json with the same slug), it updates the sources
14
+ * in place; otherwise it creates ./<slug> with the full project setup (package.json,
15
+ * tsconfig from the template) — a one-command "git clone" for a mod.
16
16
  */
17
17
  export async function pull(args) {
18
18
  const slug = args[0];
@@ -32,7 +32,7 @@ export async function pull(args) {
32
32
  const archive = await apiBinary(
33
33
  config, `/api/mods/${mod.id}/versions/${latest.id}/sources`);
34
34
 
35
- // куда разворачивать: текущая папка, если это уже этот мод, иначе ./<slug>
35
+ // where to extract: the current folder if it already is this mod, otherwise ./<slug>
36
36
  let target = process.cwd();
37
37
  let created = false;
38
38
 
@@ -1,6 +1,6 @@
1
1
  import { api, readConfig } from '../common.js';
2
2
 
3
- /** v8scli search [query] — публичный каталог библиотек портала */
3
+ /** v8scli search [query] — the portal's public library catalog */
4
4
  export async function search(args) {
5
5
  const config = await readConfig();
6
6
  const query = encodeURIComponent(args.join(' ').trim());
@@ -2,8 +2,8 @@ import { readFile, writeFile } from 'node:fs/promises';
2
2
  import { api, readConfig, readManifest } from '../common.js';
3
3
  import { ensureGenerated, typecheck } from '../typegen.js';
4
4
 
5
- // Пакует исходники (mod.json + src/ + typings) и создаёт версию на портале.
6
- // Финальный артефакт собирает сам портал — загружаются только исходники.
5
+ // Packs the sources (mod.json + src/ + typings) and creates a version on the portal.
6
+ // The portal builds the final artifact itself — only the sources are uploaded.
7
7
  async function packSources(manifest) {
8
8
  const tar = await import('tar');
9
9
 
@@ -24,8 +24,8 @@ async function packSources(manifest) {
24
24
  return archive;
25
25
  }
26
26
 
27
- // Портал требует версию строго выше последней загруженной. Если локальная не выше
28
- // (забыли поднять после прошлой загрузки) — поднимаем patch сами и пишем в mod.json.
27
+ // The portal requires a version strictly higher than the last uploaded one. If the local one is not higher
28
+ // (not bumped after the previous upload), bump the patch here and write it to mod.json.
29
29
  async function bumpVersion(manifest, lastVersion) {
30
30
  const [major, minor, patch] = lastVersion.split('.').map((part) => parseInt(part, 10));
31
31
  const next = `${major}.${minor}.${patch + 1}`;
@@ -42,11 +42,11 @@ export async function upload() {
42
42
  const config = await readConfig();
43
43
  const manifest = await readManifest();
44
44
 
45
- // Сгенерированные типы — часть исходников: портал собирает мод с тем же модулем, что и build
45
+ // The generated types are part of the sources: the portal builds the mod with the same module as build
46
46
  if (await ensureGenerated(manifest))
47
47
  typecheck();
48
48
 
49
- // modId по slug
49
+ // modId by slug
50
50
  const { mods } = await api(config, '/api/mods/my');
51
51
  const mod = mods.find((m) => m.slug === manifest.slug);
52
52
  if (!mod)
package/src/common.js CHANGED
@@ -31,11 +31,11 @@ export async function readManifest(dir = process.cwd()) {
31
31
  }
32
32
 
33
33
  /**
34
- * Авторизованный запрос к порталу.
35
- * API-токен уходит в X-Api-Token: заголовок Authorization может быть занят
36
- * basic auth стенда (дев за traefik) — его CLI берёт из user:pass в portalUrl.
34
+ * Authenticated request to the portal.
35
+ * The API token goes in X-Api-Token: the Authorization header may already be taken by
36
+ * the environment's basic auth (dev behind traefik) — the CLI takes it from user:pass in portalUrl.
37
37
  */
38
- /** Как api(), но возвращает Buffer — для скачивания архивов исходников */
38
+ /** Like api(), but returns a Buffer — for downloading source archives */
39
39
  export async function apiBinary(config, path) {
40
40
  if (!config.portalUrl) throw new Error('not logged in — run `v8scli login` first');
41
41
  const url = new URL(config.portalUrl);
package/src/typegen.js CHANGED
@@ -3,16 +3,16 @@ import { readFile, writeFile } from 'node:fs/promises';
3
3
  import { spawnSync } from 'node:child_process';
4
4
  import { dirname, join } from 'node:path';
5
5
 
6
- // Типы ключей мода из mod.json — API apiVersion 2 (v8_scripting/access).
6
+ // Mod key types from mod.json — the apiVersion 2 API (v8_scripting/access).
7
7
  //
8
- // Мод спрашивает у платформы разрешения, персональные значения и настройки сервера по ключам из
9
- // манифеста. Чтобы опечатка в ключе или чтение player-ключа как настройки сервера ловились при сборке,
10
- // а не в игре, CLI пишет рядом с entry модуль с типами и готовыми access / commands. Файл —
11
- // часть исходников: его пакует upload, и портал собирает мод с ним же.
8
+ // A mod asks the platform for permissions, per-player values and server settings by keys from the
9
+ // manifest. So that a typo in a key, or reading a player key as a server setting, fails the build
10
+ // rather than the game, the CLI writes a module with the types and ready-made access / commands next
11
+ // to the entry. The file is part of the sources: upload packs it, and the portal builds the mod with it.
12
12
 
13
13
  export const GENERATED_FILE = 'xplay.generated.ts';
14
14
 
15
- const header = `// Сгенерировано v8scli из mod.json — не править руками: build и upload перепишут.
15
+ const header = `// Generated by v8scli from mod.json — do not edit by hand: build and upload overwrite it.
16
16
  `;
17
17
 
18
18
  function literal(value) {
@@ -24,7 +24,7 @@ function union(values, fallback) {
24
24
  return items.length ? items.map(literal).join(' | ') : fallback;
25
25
  }
26
26
 
27
- /** Тип значения настройки манифеста в TypeScript */
27
+ /** TypeScript type of a manifest setting's value */
28
28
  function valueType(setting) {
29
29
  switch (setting.type) {
30
30
  case 'bool':
@@ -49,7 +49,7 @@ function valueType(setting) {
49
49
  }
50
50
  }
51
51
 
52
- /** Все настройки, включая зависимые (dependents), в порядке манифеста */
52
+ /** All settings, including dependents, in manifest order */
53
53
  function flattenSettings(settings, out = []) {
54
54
  for (const setting of settings ?? []) {
55
55
  if (!setting || typeof setting.key !== 'string')
@@ -91,23 +91,23 @@ export function generateAccessModule(manifest) {
91
91
  return `${header}import { createAccess, createCommands } from 'v8_scripting/access';
92
92
  import type { AudienceValue, CommandContext, CommandResult } from 'v8_scripting/access';
93
93
 
94
- /** Разрешения мода (permissions[]) */
94
+ /** Mod permissions (permissions[]) */
95
95
  export type PermissionKey = ${union(permissions.map((item) => item.key), 'never')};
96
96
 
97
- /** Персональные настройки (scope: "player"): у групп и игроков могут быть свои значения */
97
+ /** Per-player settings (scope: "player"): groups and players can have their own values */
98
98
  export interface PlayerValues {
99
99
  ${fields(playerSettings)}
100
100
  }
101
101
 
102
- /** Настройки мода на сервер (scope: "session") — одно значение на всех */
102
+ /** Per-server mod settings (scope: "session") — one value for everyone */
103
103
  export interface SessionSettings {
104
104
  ${fields(sessionSettings)}
105
105
  }
106
106
 
107
- /** Команды мода (commands[]) */
107
+ /** Mod commands (commands[]) */
108
108
  export type CommandKey = ${union(commands.map((item) => item.key), 'never')};
109
109
 
110
- /** Аргументы команд — уже разобранные платформой: player — AccountID, duration — секунды */
110
+ /** Command arguments, already parsed by the platform: player — AccountID, duration — seconds */
111
111
  export interface CommandArgs {
112
112
  ${commandArgs}
113
113
  }
@@ -120,7 +120,7 @@ ${permissionDefaults}
120
120
 
121
121
  const commandsRaw = createCommands<CommandKey>();
122
122
 
123
- /** Обработчик команды с типизированными аргументами */
123
+ /** Command handler with typed arguments */
124
124
  export const commands = {
125
125
  on<K extends CommandKey>(key: K, handler: (context: CommandContext, args: CommandArgs[K]) => CommandResult): () => void {
126
126
  return commandsRaw.on(key, handler as (context: CommandContext, args: Record<string, string | number>) => CommandResult);
@@ -129,14 +129,14 @@ export const commands = {
129
129
  `;
130
130
  }
131
131
 
132
- /** Путь сгенерированного модуля — рядом с entry */
132
+ /** Path of the generated module — next to the entry */
133
133
  export function generatedPath(manifest, dir = process.cwd()) {
134
134
  return join(dir, dirname(manifest.entry), GENERATED_FILE);
135
135
  }
136
136
 
137
137
  /**
138
- * Написать модуль типов для мода apiVersion 2. Переписывает только при изменении — иначе
139
- * вотчеры сборки крутились бы по кругу. false — мод apiVersion 1, типов нет
138
+ * Writes the types module for an apiVersion 2 mod. Rewrites it only when the content changes — otherwise
139
+ * build watchers would loop forever. false — an apiVersion 1 mod, no types
140
140
  */
141
141
  export async function ensureGenerated(manifest, dir = process.cwd()) {
142
142
  if (!(Number(manifest.apiVersion) >= 2))
@@ -147,14 +147,14 @@ export async function ensureGenerated(manifest, dir = process.cwd()) {
147
147
  const current = existsSync(path) ? await readFile(path, 'utf-8') : '';
148
148
  if (current !== source) {
149
149
  await writeFile(path, source);
150
- console.log(`ok: ${path} — типы ключей из mod.json`);
150
+ console.log(`ok: ${path} — key types from mod.json`);
151
151
  }
152
152
  return true;
153
153
  }
154
154
 
155
155
  /**
156
- * Проверка типов проекта. Для модов apiVersion 2 обязательна: ради неё и генерируются ключи.
157
- * Нужен typescript в проекте (он есть в шаблоне init)
156
+ * Typechecks the project. Mandatory for apiVersion 2 mods: the keys are generated for exactly this.
157
+ * Requires typescript in the project (the init template includes it)
158
158
  */
159
159
  export function typecheck(dir = process.cwd()) {
160
160
  const tsc = join(dir, 'node_modules', '.bin', process.platform === 'win32' ? 'tsc.cmd' : 'tsc');
package/src/vjs.js CHANGED
@@ -1,6 +1,6 @@
1
- // Упаковка JS в ресурс .vjs_c — копия портального src/versions/vjs.ts (единственное
2
- // место знания о формате). Нужна для локальных тестов на своём сервере: релизный
3
- // артефакт всё равно собирает портал из исходников.
1
+ // Packs JS into a .vjs_c resource — a copy of the portal's src/versions/vjs.ts (the only
2
+ // place that knows the format). Needed for local tests on your own server: the release
3
+ // artifact is built by the portal from the sources anyway.
4
4
  const RED2_HEX ='0533564b7c161274e9069846aff2e63eb59037e7010000000000004071020000' +
5
5
  '010000000000000027000000070007008f0300008e0200000000000000000000' +
6
6
  '000000000000000078020000cd01000017010000c10000000300000000000000' +
@@ -42,7 +42,7 @@ export function buildVjsC(jsBuffer) {
42
42
  const totalHeaderSize = headerSize + blockMetaSize;
43
43
 
44
44
  const header = Buffer.alloc(totalHeaderSize);
45
- header.writeUInt32LE(0, 0); // fileSize, патчится в конце
45
+ header.writeUInt32LE(0, 0); // fileSize, patched at the end
46
46
  header.writeUInt16LE(HEADER_VERSION, 4);
47
47
  header.writeUInt16LE(RESOURCE_VERSION, 6);
48
48
  header.writeUInt32LE(8, 8);
@@ -61,7 +61,7 @@ export function buildVjsC(jsBuffer) {
61
61
 
62
62
  for (let i = 0; i < blocks.length; i++) {
63
63
  const blockData = blocks[i].data;
64
- const pad = (16 - (currentPos % 16)) % 16; // выравнивание как в Resource.Serialize
64
+ const pad = (16 - (currentPos % 16)) % 16; // alignment as in Resource.Serialize
65
65
  if (pad > 0) {
66
66
  chunks.push(Buffer.alloc(pad, 0));
67
67
  currentPos += pad;
@@ -7,7 +7,7 @@
7
7
  "upload": "v8scli upload"
8
8
  },
9
9
  "devDependencies": {
10
- "v8_scripting": "^0.27.0",
10
+ "v8_scripting": "^0.28.0",
11
11
  "v8scli": "^0.4.0",
12
12
  "typescript": "^5.9.2"
13
13
  }
@@ -1,18 +1,18 @@
1
1
  import { Instance } from "cs_script/point_script";
2
2
  import { runScheduler, setInterval } from "v8_scripting/scheduler";
3
3
 
4
- // Конфиг мода: настройки из лобби (ключи из mod.json settings[]).
5
- // До запуска через платформу GetModConfig может отсутствовать — работаем на дефолтах.
4
+ // Mod config: settings from the lobby (keys from mod.json settings[]).
5
+ // Until the mod runs through the platform, GetModConfig may be missing — fall back to defaults.
6
6
  const config = Instance.GetModConfig?.();
7
7
  Instance.Msg(`mod started, environment: ${config?.environment ?? "dev"}`);
8
8
 
9
- // В рантайме нет своего цикла событий: таймеры и отложенные вызовы двигаются
10
- // отсюда. Без этой строки setInterval/setTimeout/nextFrame молча не сработают.
9
+ // The runtime has no event loop of its own: timers and deferred calls are driven
10
+ // from here. Without this line, setInterval/setTimeout/nextFrame silently never fire.
11
11
  Instance.OnGameFrame(() => runScheduler());
12
12
 
13
- // У ботов (и в момент подключения до создания контроллера) player приходит
14
- // undefined, хотя движковые типы обещают его всегда — проверяйте перед доступом,
15
- // иначе колбэк упадёт на каждом заходе бота.
13
+ // For bots (and while connecting, before the controller exists), player is
14
+ // undefined, even though the engine typings promise it is always set — check it before use,
15
+ // otherwise the callback crashes every time a bot joins.
16
16
  Instance.OnPlayerConnect(({ player }) => {
17
17
  if (!player) {
18
18
  return;