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 +1 -1
- package/bin/v8s.js +1 -1
- package/package.json +1 -1
- package/src/commands/add.js +4 -4
- package/src/commands/build.js +8 -8
- package/src/commands/deps.js +3 -3
- package/src/commands/init.js +4 -4
- package/src/commands/login.js +2 -2
- package/src/commands/mod.js +13 -13
- package/src/commands/pull.js +5 -5
- package/src/commands/search.js +1 -1
- package/src/commands/upload.js +6 -6
- package/src/common.js +4 -4
- package/src/typegen.js +20 -20
- package/src/vjs.js +5 -5
- package/templates/package.json +1 -1
- package/templates/src/index.ts +7 -7
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 —
|
|
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
package/src/commands/add.js
CHANGED
|
@@ -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
|
-
*
|
|
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] —
|
|
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}`);
|
package/src/commands/build.js
CHANGED
|
@@ -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
|
-
//
|
|
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:
|
|
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
|
|
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
|
-
//
|
|
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) —
|
|
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
|
}
|
package/src/commands/deps.js
CHANGED
|
@@ -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
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// typings
|
|
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();
|
package/src/commands/init.js
CHANGED
|
@@ -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
|
-
*
|
|
11
|
-
* 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
|
-
//
|
|
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
|
-
//
|
|
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'));
|
package/src/commands/login.js
CHANGED
|
@@ -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] —
|
|
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'); //
|
|
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
|
}
|
package/src/commands/mod.js
CHANGED
|
@@ -3,14 +3,14 @@ import { basename, extname } from 'node:path';
|
|
|
3
3
|
import { api, readConfig, readManifest } from '../common.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Mod lifecycle from the CLI: create, describe, add images, submit.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
|
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
|
-
/**
|
|
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 —
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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');
|
package/src/commands/pull.js
CHANGED
|
@@ -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
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* tsconfig
|
|
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
|
-
//
|
|
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
|
|
package/src/commands/search.js
CHANGED
|
@@ -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());
|
package/src/commands/upload.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
// (
|
|
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
|
-
//
|
|
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
|
|
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
|
|
36
|
-
* basic auth
|
|
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
|
-
/**
|
|
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
|
-
//
|
|
6
|
+
// Mod key types from mod.json — the apiVersion 2 API (v8_scripting/access).
|
|
7
7
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
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 = `//
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
94
|
+
/** Mod permissions (permissions[]) */
|
|
95
95
|
export type PermissionKey = ${union(permissions.map((item) => item.key), 'never')};
|
|
96
96
|
|
|
97
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
107
|
+
/** Mod commands (commands[]) */
|
|
108
108
|
export type CommandKey = ${union(commands.map((item) => item.key), 'never')};
|
|
109
109
|
|
|
110
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
139
|
-
*
|
|
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} —
|
|
150
|
+
console.log(`ok: ${path} — key types from mod.json`);
|
|
151
151
|
}
|
|
152
152
|
return true;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
/**
|
|
156
|
-
*
|
|
157
|
-
*
|
|
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
|
-
//
|
|
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; //
|
|
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;
|
package/templates/package.json
CHANGED
package/templates/src/index.ts
CHANGED
|
@@ -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
|
-
//
|
|
5
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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;
|