@gamedevland/vite 0.0.4 → 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ ## 0.1.2 — 2026-11-04
4
+
5
+ - Release the package and its source under MIT.
6
+ - Add public source, issue tracker and package-directory metadata.
7
+ - Depend on the MIT-licensed engine 0.0.8 release.
8
+ - Make the README configuration example work in native ESM.
9
+
10
+ ## 0.1.1 (withdrawn)
11
+
12
+ - Publication withdrawn; this version must not be reused.
13
+
14
+ ## 0.1.0
15
+
16
+ - Add the `gamedevland poki pack` and `gamedevland poki upload` CLI commands.
17
+ - Configure game ID, build directory, npm checks/build task and archive path in `poki.json`.
18
+ - Package deterministic ZIPs without platform-specific shell utilities or game-specific asset assumptions.
19
+ - Use the official pinned Poki CLI, per-game private credentials or `POKI_UPLOAD_TOKEN`, and verified upload receipts.
20
+ - Keep authentication out of check/build subprocesses and reject source maps, private files, symlinks and leaked upload tokens in build output.
21
+ - Preserve existing Vite configuration exports and browser SDK injection.
package/LICENSE CHANGED
@@ -1,23 +1,21 @@
1
- Gamedev Land SDK License
1
+ MIT License
2
2
 
3
3
  Copyright (c) 2026 Gamedev Land
4
4
 
5
- This license applies to the compiled @gamedevland/vite npm package and its TypeScript declaration files.
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
6
11
 
7
- Permission is granted to install and use the package to build, test, and distribute games, demos,
8
- tutorials, and related web applications.
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
9
14
 
10
- The package may be redistributed only as an unmodified dependency through package managers, deployment
11
- artifacts, or application bundles generated as part of a game or web application.
12
-
13
- You may not:
14
-
15
- - claim that the SDK itself is open source;
16
- - republish, sublicense, sell, or repackage the SDK as a competing SDK, engine, library, or framework;
17
- - remove copyright, license, or package identity notices;
18
- - use the package name, author name, or repository identity to imply endorsement without permission.
19
-
20
- The SDK is provided "as is", without warranty of any kind, express or implied, including but not limited
21
- to the warranties of merchantability, fitness for a particular purpose, and noninfringement. In no event
22
- shall the authors or copyright holders be liable for any claim, damages, or other liability arising from
23
- the SDK or its use.
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -13,18 +13,81 @@ Vite 7 or 8 must be installed in the game project.
13
13
  ## Usage
14
14
 
15
15
  ```ts
16
+ import { fileURLToPath } from 'node:url';
16
17
  import { createGameViteConfig } from '@gamedevland/vite';
17
18
 
18
19
  export default ({ mode }: { readonly mode: string }) =>
19
20
  createGameViteConfig({
20
21
  mode,
21
- repoRoot: __dirname,
22
+ repoRoot: fileURLToPath(new URL('.', import.meta.url)),
22
23
  });
23
24
  ```
24
25
 
25
26
  The package provides the shared Gamedev Land platform configuration, development proxy setup,
26
27
  environment resolution, and platform SDK injection used by engine-based game projects.
27
28
 
29
+ ## Shared Poki tools (Node.js 20.19+)
30
+
31
+ The package also installs the `gamedevland` executable. No game-local upload or ZIP script is needed.
32
+ The CLI uses the project's `poki.json`; it never assumes a particular game name, asset format, font or directory layout.
33
+
34
+ ```json
35
+ {
36
+ "game_id": "your-poki-game-uuid",
37
+ "build_dir": "dist/poki",
38
+ "tooling": {
39
+ "checks": ["typecheck", "lint", "test:poki"],
40
+ "buildScript": "compile:poki",
41
+ "archive": "dist/my-game-poki.zip"
42
+ }
43
+ }
44
+ ```
45
+
46
+ ```json
47
+ {
48
+ "scripts": {
49
+ "compile:poki": "VITE_PLATFORM=poki vite build --outDir ../dist/poki",
50
+ "build:poki": "npm run compile:poki && gamedevland poki pack",
51
+ "poki:upload": "gamedevland poki upload"
52
+ }
53
+ }
54
+ ```
55
+
56
+ ```bash
57
+ npm run build:poki
58
+ npm run poki:upload -- --name "Production v1" --notes "Release notes"
59
+ npx gamedevland poki pack --root /path/to/another-game
60
+ ```
61
+
62
+ `pack` validates an existing build and creates a ZIP with `index.html` at the root. It checks the
63
+ official SDK script and local HTML dependencies. Hidden/private files, source maps, TypeScript sources,
64
+ symlinks and nested archives are rejected. PNG, WAV, SVG, WebP and other game asset formats are allowed.
65
+ Game-specific asset/license budgets remain project tests. ZIP metadata uses a fixed timestamp for reproducibility.
66
+
67
+ `upload` validates all configured npm scripts, resolves authentication, runs checks followed by a fresh build,
68
+ packages it, and invokes `@poki/cli`. It preserves optimized images and does not request public release/review.
69
+ Only a confirmed success response for the configured game and matching Preview/Inspector version counts as success;
70
+ the official CLI's zero exit code alone is insufficient. Temporary upload archives are kept outside the project and cleaned.
71
+ A receipt `poki-upload.json` next to the local ZIP records game/version IDs, URLs, source revision/dirty state and local ZIP SHA-256.
72
+
73
+ Defaults: `checks` is `["typecheck", "lint"]`, `buildScript` is `build:poki`, and `archive` is
74
+ `artifacts/poki/<package-name>.zip`. Add the output directory to `.gitignore`. For projects without these checks,
75
+ explicitly configure the appropriate list (including `[]` when intentional). Paths must stay inside the project,
76
+ and the ZIP must be outside `build_dir` to prevent recursive uploads. `--root` defaults to the current directory.
77
+ Version names default to the git revision plus UTC timestamp; git is optional.
78
+
79
+ ### Authentication
80
+
81
+ CI can set `POKI_UPLOAD_TOKEN`. For local repeated uploads, store the game's token (plain text, no shell command)
82
+ in `$XDG_CONFIG_HOME/poki/game-upload-tokens/<game_id>` or `~/.config/poki/game-upload-tokens/<game_id>`.
83
+ On POSIX systems, the token file must have permissions `0600`.
84
+ Never put credentials in `poki.json`, package metadata or the build. Tokens are passed only to the upload process,
85
+ removed from check/build environments, masked in CLI output and checked against all files being packaged.
86
+
87
+ See the [official Poki CLI](https://github.com/poki/poki-cli). The browser runtime SDK remains separate from these Node.js tools.
88
+
28
89
  ## License
29
90
 
30
- This package is public but not open source. See [LICENSE](./LICENSE).
91
+ MIT. See [LICENSE](./LICENSE). Source code and contributions: [gamedevland/sdk](https://github.com/gamedevland/sdk).
92
+
93
+ Earlier npm releases retain the license included in their respective archives. Third-party dependencies retain their own licenses.
@@ -0,0 +1,4 @@
1
+ /** Public command-line interface; no dependency on a particular game layout. */
2
+ export declare class GamedevlandCli {
3
+ static run(argv: string[], cwd: string): Promise<void>;
4
+ }
@@ -0,0 +1,29 @@
1
+ import { parseArgs } from 'node:util';
2
+ import { PokiArchive } from '../poki/PokiArchive.js';
3
+ import { PokiProject } from '../poki/PokiProject.js';
4
+ import { PokiUpload } from '../poki/PokiUpload.js';
5
+ /** Public command-line interface; no dependency on a particular game layout. */
6
+ export class GamedevlandCli {
7
+ static async run(argv, cwd) {
8
+ const { values, positionals } = parseArgs({ args: argv, allowPositionals: true, options: {
9
+ help: { type: 'boolean', short: 'h' }, root: { type: 'string' },
10
+ name: { type: 'string' }, notes: { type: 'string' },
11
+ } });
12
+ if (values.help || positionals.length === 0) {
13
+ process.stdout.write('Usage: gamedevland poki <pack|upload> [--root <project>] [--name <version>] [--notes <text>]\npack validates/packages an existing build; upload runs configured checks and a fresh build before uploading.\n');
14
+ return;
15
+ }
16
+ if (positionals.length !== 2 || positionals[0] !== 'poki' || !['pack', 'upload'].includes(positionals[1] ?? '')) {
17
+ throw new Error('[gamedevland] Expected poki pack or poki upload; use --help');
18
+ }
19
+ const project = PokiProject.load(values.root ?? cwd);
20
+ if (positionals[1] === 'pack') {
21
+ const archive = await PokiArchive.pack(project, process.env.POKI_UPLOAD_TOKEN);
22
+ process.stdout.write(`[poki] ${project.archive}: ${archive.files} files, ${(archive.bytes / 1048576).toFixed(2)} MiB\n`);
23
+ }
24
+ else {
25
+ await PokiUpload.run(project, { ...(values.name === undefined ? {} : { name: values.name }),
26
+ ...(values.notes === undefined ? {} : { notes: values.notes }) });
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import { GamedevlandCli } from './GamedevlandCli.js';
3
+ class GamedevlandCliEntry {
4
+ static async main() {
5
+ try {
6
+ await GamedevlandCli.run(process.argv.slice(2), process.cwd());
7
+ }
8
+ catch (error) {
9
+ process.stderr.write(`${error instanceof Error ? error.message : '[gamedevland] Command failed'}\n`);
10
+ process.exitCode = 1;
11
+ }
12
+ }
13
+ }
14
+ void GamedevlandCliEntry.main();
@@ -0,0 +1,10 @@
1
+ import type { PokiProject } from './PokiProject.js';
2
+ /** Validates and packages a platform build, preserving arbitrary game asset formats. */
3
+ export declare class PokiArchive {
4
+ static pack(project: PokiProject, secret?: string): Promise<{
5
+ readonly sha256: string;
6
+ readonly bytes: number;
7
+ readonly files: number;
8
+ }>;
9
+ private static collect;
10
+ }
@@ -0,0 +1,64 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { lstat, mkdir, readFile, readdir, realpath, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { zipSync } from 'fflate';
5
+ import { BuildPlatformSdkCatalog } from '../BuildPlatformSdkCatalog.js';
6
+ /** Validates and packages a platform build, preserving arbitrary game asset formats. */
7
+ export class PokiArchive {
8
+ static async pack(project, secret) {
9
+ if (await realpath(project.buildDir) !== project.buildDir)
10
+ throw new Error('[poki] Symlinked build directory is not supported');
11
+ const files = await PokiArchive.collect(project.buildDir);
12
+ if (!files.includes('index.html'))
13
+ throw new Error('[poki] index.html must be at the archive root');
14
+ const zip = {};
15
+ for (const file of files) {
16
+ const content = await readFile(path.join(project.buildDir, file));
17
+ if (secret && content.includes(Buffer.from(secret)))
18
+ throw new Error('[poki] Upload token found in build output');
19
+ Object.defineProperty(zip, file, { value: [content, { mtime: new Date(2000, 0, 1) }], enumerable: true });
20
+ }
21
+ const html = await readFile(path.join(project.buildDir, 'index.html'), 'utf8');
22
+ const sdkUrl = BuildPlatformSdkCatalog.resolveHeadTags('poki')[0]?.attrs?.src;
23
+ const sources = [...html.matchAll(/<script\b[^>]*\bsrc\s*=\s*["']([^"']+)["'][^>]*>/gi)].map(match => match[1]);
24
+ if (typeof sdkUrl !== 'string' || sources.filter(src => src === sdkUrl).length !== 1) {
25
+ throw new Error('[poki] Expected exactly one official Poki SDK script');
26
+ }
27
+ for (const match of html.matchAll(/\b(?:src|href)\s*=\s*["']([^"']+)["']/gi)) {
28
+ const url = match[1];
29
+ if (!url || url.startsWith('#') || /^(?:https?:|data:|blob:|\/\/)/i.test(url))
30
+ continue;
31
+ if (url.startsWith('/'))
32
+ throw new Error(`[poki] Root-relative HTML dependency: ${url}`);
33
+ const file = path.posix.normalize(decodeURIComponent(url.split(/[?#]/)[0] ?? ''));
34
+ if (!files.includes(file))
35
+ throw new Error(`[poki] Missing HTML dependency: ${url}`);
36
+ }
37
+ if (/https?:\/\/(?:localhost|127\.0\.0\.1)(?=[:/"'])|\/src\/main\.ts/i.test(html)) {
38
+ throw new Error('[poki] Development entry or localhost URL in HTML');
39
+ }
40
+ const archive = zipSync(zip, { level: 9 });
41
+ await mkdir(path.dirname(project.archive), { recursive: true });
42
+ await writeFile(project.archive, archive);
43
+ return { sha256: createHash('sha256').update(archive).digest('hex'), bytes: archive.length, files: files.length };
44
+ }
45
+ static async collect(root, prefix = '') {
46
+ const result = [];
47
+ for (const name of (await readdir(path.join(root, prefix))).sort()) {
48
+ const file = prefix ? `${prefix}/${name}` : name;
49
+ if (name.startsWith('.') || /\.(?:map|ts|tsx|tgz|pem|key|zip)$/i.test(name)) {
50
+ throw new Error(`[poki] Source, private or nested archive file in build: ${file}`);
51
+ }
52
+ const stat = await lstat(path.join(root, file));
53
+ if (stat.isSymbolicLink())
54
+ throw new Error(`[poki] Symlink in build: ${file}`);
55
+ if (stat.isDirectory())
56
+ result.push(...await PokiArchive.collect(root, file));
57
+ else if (stat.isFile())
58
+ result.push(file);
59
+ else
60
+ throw new Error(`[poki] Unsupported build entry: ${file}`);
61
+ }
62
+ return result;
63
+ }
64
+ }
@@ -0,0 +1,5 @@
1
+ /** Resolves a per-game secret outside the project or from the CI environment. */
2
+ export declare class PokiCredentials {
3
+ static resolve(gameId: string): string;
4
+ static withoutSecrets(): NodeJS.ProcessEnv;
5
+ }
@@ -0,0 +1,34 @@
1
+ import { readFileSync, statSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import path from 'node:path';
4
+ /** Resolves a per-game secret outside the project or from the CI environment. */
5
+ export class PokiCredentials {
6
+ static resolve(gameId) {
7
+ const envToken = process.env.POKI_UPLOAD_TOKEN;
8
+ if (envToken !== undefined) {
9
+ if (!envToken.trim())
10
+ throw new Error('[poki] POKI_UPLOAD_TOKEN must not be empty');
11
+ return envToken.trim();
12
+ }
13
+ const file = path.join(process.env.XDG_CONFIG_HOME ?? path.join(homedir(), '.config'), 'poki', 'game-upload-tokens', gameId);
14
+ try {
15
+ if (process.platform !== 'win32' && (statSync(file).mode & 0o777) !== 0o600) {
16
+ throw new Error('permissions');
17
+ }
18
+ const token = readFileSync(file, 'utf8').trim();
19
+ if (!token)
20
+ throw new Error('empty');
21
+ return token;
22
+ }
23
+ catch {
24
+ throw new Error(`[poki] Set POKI_UPLOAD_TOKEN or create a private (0600) token file: ${file}`);
25
+ }
26
+ }
27
+ static withoutSecrets() {
28
+ const env = { ...process.env };
29
+ delete env.POKI_UPLOAD_TOKEN;
30
+ delete env.POKI_ACCESS_TOKEN;
31
+ delete env.API_PATH;
32
+ return env;
33
+ }
34
+ }
@@ -0,0 +1,5 @@
1
+ /** Runs npm tasks and captures CLI output without shell-interpolated arguments. */
2
+ export declare class PokiProcess {
3
+ static script(root: string, name: string): Promise<void>;
4
+ static capture(command: string, args: readonly string[], cwd: string, env: NodeJS.ProcessEnv, inherit?: boolean, secret?: string): Promise<string>;
5
+ }
@@ -0,0 +1,35 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { PokiCredentials } from './PokiCredentials.js';
3
+ /** Runs npm tasks and captures CLI output without shell-interpolated arguments. */
4
+ export class PokiProcess {
5
+ static async script(root, name) {
6
+ const npmEntry = process.env.npm_execpath;
7
+ const command = npmEntry ? process.execPath : 'npm';
8
+ const args = npmEntry ? [npmEntry, 'run', name] : ['run', name];
9
+ await PokiProcess.capture(command, args, root, PokiCredentials.withoutSecrets(), true);
10
+ }
11
+ static capture(command, args, cwd, env, inherit = false, secret) {
12
+ return new Promise((resolve, reject) => {
13
+ const child = spawn(command, [...args], { cwd, env, stdio: inherit ? 'inherit' : 'pipe',
14
+ shell: process.platform === 'win32' && command === 'npm' });
15
+ let output = '';
16
+ const timer = inherit ? undefined : setTimeout(() => child.kill('SIGKILL'), 180000);
17
+ const capture = (data) => { output += data.toString('utf8'); };
18
+ child.stdout?.on('data', capture);
19
+ child.stderr?.on('data', capture);
20
+ child.once('error', () => { if (timer)
21
+ clearTimeout(timer); reject(new Error('[poki] Could not start required tool')); });
22
+ child.once('close', code => {
23
+ if (timer)
24
+ clearTimeout(timer);
25
+ const safe = secret ? output.replaceAll(secret, '[REDACTED]') : output;
26
+ if (!inherit)
27
+ process.stdout.write(safe);
28
+ if (code !== 0)
29
+ reject(new Error(`[poki] Tool exited with status ${code ?? 'signal'}`));
30
+ else
31
+ resolve(safe);
32
+ });
33
+ });
34
+ }
35
+ }
@@ -0,0 +1,18 @@
1
+ /** Decodes project-local configuration without game-specific defaults. */
2
+ export declare class PokiProject {
3
+ readonly root: string;
4
+ readonly gameId: string;
5
+ readonly buildDir: string;
6
+ readonly archive: string;
7
+ readonly buildScript: string;
8
+ readonly checks: readonly string[];
9
+ private readonly scripts;
10
+ private static readonly TOOLING;
11
+ private static readonly CONFIG;
12
+ private static readonly PACKAGE;
13
+ private constructor();
14
+ static load(root: string): PokiProject;
15
+ validateScripts(): void;
16
+ private static json;
17
+ private static projectPath;
18
+ }
@@ -0,0 +1,80 @@
1
+ import { readFileSync, realpathSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { SchemaDecoder } from '@gamedevland/engine/validation';
4
+ /** Decodes project-local configuration without game-specific defaults. */
5
+ export class PokiProject {
6
+ root;
7
+ gameId;
8
+ buildDir;
9
+ archive;
10
+ buildScript;
11
+ checks;
12
+ scripts;
13
+ static TOOLING = SchemaDecoder.object({
14
+ buildScript: SchemaDecoder.optional(SchemaDecoder.string({ nonEmpty: true })),
15
+ checks: SchemaDecoder.optional(SchemaDecoder.array(SchemaDecoder.string({ nonEmpty: true }))),
16
+ archive: SchemaDecoder.optional(SchemaDecoder.string({ nonEmpty: true })),
17
+ });
18
+ static CONFIG = SchemaDecoder.object({
19
+ game_id: SchemaDecoder.string({ nonEmpty: true }),
20
+ build_dir: SchemaDecoder.string({ nonEmpty: true }),
21
+ tooling: SchemaDecoder.optional(PokiProject.TOOLING),
22
+ });
23
+ static PACKAGE = SchemaDecoder.object({
24
+ name: SchemaDecoder.string({ nonEmpty: true }),
25
+ scripts: SchemaDecoder.record(),
26
+ });
27
+ constructor(root, gameId, buildDir, archive, buildScript, checks, scripts) {
28
+ this.root = root;
29
+ this.gameId = gameId;
30
+ this.buildDir = buildDir;
31
+ this.archive = archive;
32
+ this.buildScript = buildScript;
33
+ this.checks = checks;
34
+ this.scripts = scripts;
35
+ }
36
+ static load(root) {
37
+ root = realpathSync(root);
38
+ const config = SchemaDecoder.decode(PokiProject.json(path.join(root, 'poki.json')), PokiProject.CONFIG, 'poki.json');
39
+ const pkg = SchemaDecoder.decode(PokiProject.json(path.join(root, 'package.json')), PokiProject.PACKAGE, 'package.json');
40
+ const scripts = Object.fromEntries(Object.entries(pkg.scripts).map(([key, value]) => [key, SchemaDecoder.decode(value, SchemaDecoder.string(), 'package.json', `$.scripts.${key}`)]));
41
+ if (!/^[\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12}$/i.test(config.game_id)) {
42
+ throw new Error('[poki] game_id must be a UUID');
43
+ }
44
+ const buildDir = PokiProject.projectPath(root, config.build_dir);
45
+ const slug = pkg.name.replace(/^@/, '').replace(/[^\w.-]+/g, '-');
46
+ const archive = PokiProject.projectPath(root, config.tooling?.archive ?? `artifacts/poki/${slug}.zip`);
47
+ if (!path.relative(buildDir, archive).startsWith(`..${path.sep}`)) {
48
+ throw new Error('[poki] Archive must be outside build_dir');
49
+ }
50
+ if (!archive.endsWith('.zip'))
51
+ throw new Error('[poki] Archive must end with .zip');
52
+ return new PokiProject(root, config.game_id, buildDir, archive, config.tooling?.buildScript ?? 'build:poki', config.tooling?.checks ?? ['typecheck', 'lint'], scripts);
53
+ }
54
+ validateScripts() {
55
+ for (const name of [...this.checks, this.buildScript]) {
56
+ if (!/^[\w:.-]+$/.test(name) || !this.scripts[name]) {
57
+ throw new Error(`[poki] Missing or invalid package.json script: ${name}`);
58
+ }
59
+ if (/\bgamedevland\s+poki\s+upload\b/.test(this.scripts[name])) {
60
+ throw new Error(`[poki] Recursive upload script: ${name}`);
61
+ }
62
+ }
63
+ }
64
+ static json(file) {
65
+ try {
66
+ return JSON.parse(readFileSync(file, 'utf8'));
67
+ }
68
+ catch {
69
+ throw new Error(`[poki] Cannot read valid JSON: ${file}`);
70
+ }
71
+ }
72
+ static projectPath(root, input) {
73
+ const result = path.resolve(root, input);
74
+ const relative = path.relative(root, result);
75
+ if (!relative || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
76
+ throw new Error('[poki] Build/archive paths must stay inside the project');
77
+ }
78
+ return result;
79
+ }
80
+ }
@@ -0,0 +1,9 @@
1
+ import type { PokiProject } from './PokiProject.js';
2
+ /** Executes a fresh checked build and accepts only an API-confirmed upload. */
3
+ export declare class PokiUpload {
4
+ static run(project: PokiProject, options: {
5
+ readonly name?: string;
6
+ readonly notes?: string;
7
+ }): Promise<void>;
8
+ private static git;
9
+ }
@@ -0,0 +1,57 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
3
+ import { createRequire } from 'node:module';
4
+ import { tmpdir } from 'node:os';
5
+ import path from 'node:path';
6
+ import { PokiArchive } from './PokiArchive.js';
7
+ import { PokiCredentials } from './PokiCredentials.js';
8
+ import { PokiProcess } from './PokiProcess.js';
9
+ /** Executes a fresh checked build and accepts only an API-confirmed upload. */
10
+ export class PokiUpload {
11
+ static async run(project, options) {
12
+ project.validateScripts();
13
+ const secret = PokiCredentials.resolve(project.gameId);
14
+ const revision = PokiUpload.git(project.root, ['rev-parse', '--short', 'HEAD']);
15
+ const dirty = PokiUpload.git(project.root, ['status', '--porcelain']);
16
+ const name = options.name ?? `${revision || 'build'}-${new Date().toISOString().replace(/[-:.]/g, '')}`;
17
+ const notes = options.notes ?? (PokiUpload.git(project.root, ['log', '-1', '--pretty=%s']) || 'Automated game build');
18
+ if (!name.trim())
19
+ throw new Error('[poki] Version name must not be empty');
20
+ for (const task of [...project.checks, project.buildScript])
21
+ await PokiProcess.script(project.root, task);
22
+ const archive = await PokiArchive.pack(project, secret);
23
+ const scratch = await mkdtemp(path.join(tmpdir(), 'gamedevland-poki-'));
24
+ let output;
25
+ try {
26
+ const cli = createRequire(import.meta.url).resolve('@poki/cli/bin/index.js');
27
+ output = await PokiProcess.capture(process.execPath, [cli, 'upload', '--game', project.gameId,
28
+ '--build-dir', project.buildDir, '--name', name, '--notes', notes, '--disable-image-compression'], scratch, { ...PokiCredentials.withoutSecrets(), POKI_UPLOAD_TOKEN: secret }, false, secret);
29
+ }
30
+ finally {
31
+ await rm(scratch, { recursive: true, force: true });
32
+ }
33
+ // @poki/cli 0.1.19 also exits 0 on API errors; require its success response.
34
+ if (!output.includes('Version uploaded successfully'))
35
+ throw new Error('[poki] Upload not confirmed by Poki');
36
+ const preview = output.match(/Preview: (https:\/\/poki\.com\/en\/preview\/[^\s]+)/)?.[1];
37
+ const inspector = output.match(/Inspector: (https:\/\/inspector\.poki\.dev\/[^\s]+)/)?.[1];
38
+ const prefix = `https://poki.com/en/preview/${project.gameId}/`;
39
+ const versionId = preview?.startsWith(prefix) ? preview.slice(prefix.length) : '';
40
+ if (!/^[\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12}$/i.test(versionId) || inspector !== `https://inspector.poki.dev/?game=poki-${versionId}`) {
41
+ throw new Error('[poki] Upload response does not match the configured game/version');
42
+ }
43
+ const receipt = path.join(path.dirname(project.archive), 'poki-upload.json');
44
+ await writeFile(receipt, JSON.stringify({ gameId: project.gameId, versionId, name, revision,
45
+ dirty: Boolean(dirty), uploadedAt: new Date().toISOString(), archive: project.archive,
46
+ archiveSha256: archive.sha256, bytes: archive.bytes, files: archive.files, preview, inspector }, null, 2) + '\n');
47
+ process.stdout.write(`[poki] Receipt: ${receipt}\n`);
48
+ }
49
+ static git(root, args) {
50
+ try {
51
+ return execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], env: PokiCredentials.withoutSecrets() }).trim();
52
+ }
53
+ catch {
54
+ return '';
55
+ }
56
+ }
57
+ }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@gamedevland/vite",
3
- "version": "0.0.4",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "Vite build tooling for Gamedev Land HTML5 games.",
6
- "license": "SEE LICENSE IN LICENSE",
6
+ "license": "MIT",
7
7
  "author": "Gamedev Land",
8
8
  "keywords": [
9
9
  "gamedev",
@@ -15,9 +15,16 @@
15
15
  "main": "./dist/index.js",
16
16
  "types": "./dist/index.d.ts",
17
17
  "sideEffects": false,
18
+ "bin": {
19
+ "gamedevland": "./dist/cli/entry.js"
20
+ },
21
+ "engines": {
22
+ "node": ">=20.19.0"
23
+ },
18
24
  "files": [
19
25
  "dist",
20
26
  "README.md",
27
+ "CHANGELOG.md",
21
28
  "LICENSE"
22
29
  ],
23
30
  "publishConfig": {
@@ -30,15 +37,27 @@
30
37
  }
31
38
  },
32
39
  "scripts": {
40
+ "test": "node --test tests/*.test.mjs",
33
41
  "clean": "rimraf dist *.tsbuildinfo",
34
42
  "pretypecheck": "npm run build -w @gamedevland/engine",
35
43
  "typecheck": "tsc --noEmit",
36
44
  "build": "npm run clean && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json --resolve-full-paths"
37
45
  },
38
46
  "dependencies": {
39
- "@gamedevland/engine": "0.0.5"
47
+ "@gamedevland/engine": "0.0.8",
48
+ "@poki/cli": "0.1.19",
49
+ "fflate": "0.8.3"
40
50
  },
41
51
  "peerDependencies": {
42
52
  "vite": "^7.1.0 || ^8.0.0"
53
+ },
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "git+https://github.com/gamedevland/sdk.git",
57
+ "directory": "packages/vite"
58
+ },
59
+ "homepage": "https://github.com/gamedevland/sdk/tree/main/packages/vite#readme",
60
+ "bugs": {
61
+ "url": "https://github.com/gamedevland/sdk/issues"
43
62
  }
44
63
  }