@defold-typescript/docs 0.9.0
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 +3 -0
- package/guide/README.md +52 -0
- package/guide/add-typescript.md +57 -0
- package/guide/advanced-cli.md +101 -0
- package/guide/agent-runbooks.md +357 -0
- package/guide/api-docs-vs-ts-defold.md +131 -0
- package/guide/debugging.md +106 -0
- package/guide/defold-editor.md +50 -0
- package/guide/editor-setup.md +93 -0
- package/guide/extensions.md +172 -0
- package/guide/getting-started.md +257 -0
- package/guide/init-templates.md +28 -0
- package/guide/migrating-from-ts-defold.md +128 -0
- package/guide/pinning-defold-version.md +190 -0
- package/guide/script-lifecycle.md +220 -0
- package/guide/transpile-diagnostics.md +37 -0
- package/guide/typescript-gotchas.md +374 -0
- package/guide/typescript-vs-lua.md +194 -0
- package/guide/vector-math.md +74 -0
- package/package.json +20 -0
package/README.md
ADDED
package/guide/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
toc-title: Overview
|
|
3
|
+
---
|
|
4
|
+
# Overview
|
|
5
|
+
|
|
6
|
+
Build your [Defold](https://defold.com/) game in [TypeScript](https://www.typescriptlang.org/) and get VSCode's full editor experience — autocomplete, inline type errors, and safe refactors — across the whole Defold API, while still shipping the plain [Lua](https://www.lua.org/) the engine runs.
|
|
7
|
+
|
|
8
|
+
- **The full Defold API, typed** — every module and namespace is typed from the official reference, so `go`, `gui`, `vmath`, `msg`, and the rest autocomplete and type-check as you write.
|
|
9
|
+
- **Compiles to plain Lua** — your TypeScript runs through the battle-tested [TypeScriptToLua](https://typescripttolua.github.io/) (TSTL) compiler down to the Lua Defold already runs: no engine fork, no proprietary runtime, no lock-in.
|
|
10
|
+
- **Typed scripts end to end** — `self`, `on_message`, and `on_input` payloads are typed through `defineScript`, `defineGuiScript`, and `defineRenderScript`.
|
|
11
|
+
- **Fits new and existing projects** — scaffold from scratch, or add the TypeScript surface to a project that already has `game.project` and adopt type safety gradually, one script at a time alongside your existing Lua.
|
|
12
|
+
- **Built for the real loop** — `watch` recompiles beside the Defold editor, live transpile diagnostics surface errors inline, and source maps let you set breakpoints in your `.ts`.
|
|
13
|
+
|
|
14
|
+
End-user documentation for `defold-typescript`: how to scaffold a project, write TypeScript that the toolchain compiles to Lua, and look up the language-and-toolchain quirks you will hit along the way. The repository's `README.md` covers why this project exists and the planning workflow; this folder covers how to use the toolchain.
|
|
15
|
+
|
|
16
|
+
The sections below mirror the top navigation; each lists the pages in its left-sidebar order.
|
|
17
|
+
|
|
18
|
+
## Get started
|
|
19
|
+
|
|
20
|
+
- [Getting started](./getting-started.md) — install Bun, scaffold a new project with `bunx @defold-typescript/cli@latest init`, write a one-screen script, and build to Lua with `bunx @defold-typescript/cli build`.
|
|
21
|
+
- [Starter templates](./init-templates.md) — pick a new-project layout with `init --template <name>`: the opinionated `default` or a blank-script `minimal`.
|
|
22
|
+
- [Existing project](./add-typescript.md) — run `bunx @defold-typescript/cli@latest init` in a folder with `game.project` to add the TypeScript surface without replacing the Defold project.
|
|
23
|
+
- [Editor setup](./editor-setup.md) — open the project in VSCode, use the generated `tsconfig.json`, and run `bunx @defold-typescript/cli watch` beside the Defold editor.
|
|
24
|
+
- [Defold editor](./defold-editor.md) — install Defold, open the generated project folder, attach a compiled script (`.ts.script`, `.ts.gui_script`, or `.ts.render_script`) to its game object, GUI scene, or render pipeline, and run the game (TypeScript is transpiled to Lua by the CLI, not the editor).
|
|
25
|
+
|
|
26
|
+
## Guides
|
|
27
|
+
|
|
28
|
+
- [Transpile diagnostics](./transpile-diagnostics.md) — the scaffolded `@defold-typescript/tstl-plugin` language-service plugin that surfaces TypeScript-to-Lua transpile errors live in the editor, advisory-only and never blocking `tsc --noEmit`.
|
|
29
|
+
- [Debugging](./debugging.md) — step through `.ts` source with breakpoints via the Local Lua Debugger and the scaffolded Bun launch path (no shell, Windows-native), resolving through the emitted `<name>.ts.script.map`.
|
|
30
|
+
- [Pinning the Defold version](./pinning-defold-version.md) — keep the default latest surface, or pin an older Defold version whose API surface is generated on the fly and materialized into a project-local `.defold-types/<version>/`.
|
|
31
|
+
- [Native extensions](./extensions.md) — declare an extension in `game.project` `[dependencies]`, run `defold-typescript resolve` to generate an ambient namespace per `.script_api` into a gitignored `.defold-types/extensions/` surface, and consume it with no import.
|
|
32
|
+
- [Advanced CLI](./advanced-cli.md) — opt-in per-directory API walls with the `wall` command (interactive checkbox and flag forms), the full-surface-by-default policy, and the import-from-subpath rule a wall depends on.
|
|
33
|
+
- [Agent runbooks](./agent-runbooks.md) — harness-neutral procedures for driving the CLI from an automated agent: scaffold a project, [install the agent contract](./agent-runbooks.md#install-the-agent-contract), regenerate extension types, [add and attach a script](./agent-runbooks.md#add-a-script) (build, wire the compiled component, verify), and fix the Lua output over the `--json` envelope, gating on `ok`.
|
|
34
|
+
|
|
35
|
+
## Language
|
|
36
|
+
|
|
37
|
+
- [TypeScript vs Lua](./typescript-vs-lua.md) — the Lua-developer on-ramp: a cheat sheet that translates syntax, tables, modules, and the standard library from Lua to the TypeScript the toolchain expects.
|
|
38
|
+
- [Script lifecycle](./script-lifecycle.md) — type `self`, `on_message`, and `on_input` payloads with `defineScript`, `defineGuiScript`, and `defineRenderScript`.
|
|
39
|
+
- [Vector math](./vector-math.md) — the method-form arithmetic surface (`add`, `sub`, `mul`, `div`, `unm`) on `Vector3`, `Vector4`, `Quaternion`, and `Matrix4`, plus why you cannot write `v3 + v3`.
|
|
40
|
+
- [TypeScript gotchas](./typescript-gotchas.md) — the canonical catalog of TS / TSTL / Defold sharp edges. Today: the unary-minus quirk that silently produces `number` from a `Vector3`. Future entries land here as the toolchain encounters them.
|
|
41
|
+
- [API docs vs `ts-defold-types`](./api-docs-vs-ts-defold.md) — a factual, dimension-by-dimension comparison of the JSDoc that `@defold-typescript/types` and `ts-defold-types` emit (Markdown conversion, dash params, grid-aligned multi-line docs, branded constants, the `@example` trade-off), with a picker for which surface fits your project.
|
|
42
|
+
- [Migrating from `ts-defold`](./migrating-from-ts-defold.md) — move a project off the `@ts-defold/*` stack: the package/tooling map, the step-by-step port via `init` add-TypeScript mode and a `tsconfig.json` reconcile, and the type-surface differences you will hit.
|
|
43
|
+
|
|
44
|
+
## Reference
|
|
45
|
+
|
|
46
|
+
- [API](/api) — the generated `@defold-typescript/types` reference: every documented Defold namespace, grouped alphabetically as cards (site-only; built from the typed surface the toolchain ships).
|
|
47
|
+
- [Lua standard library](/api/base) — the pure-Lua / LuaJIT reference category (`base`, `bit`, …). Types are owned by the `lua-types` dependency the `lua-stdlib-globals` goal adopted; `@defold-typescript/types` does not re-emit them as generated namespaces.
|
|
48
|
+
|
|
49
|
+
## Coming later
|
|
50
|
+
|
|
51
|
+
- Per-module API guide — landing with `builtin-messages-typing` and `script-lifecycle-typing`.
|
|
52
|
+
- Messages guide — `msg.post` payload narrowing, builtin message IDs — landing with `builtin-messages-typing`.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
toc-title: Existing project
|
|
3
|
+
---
|
|
4
|
+
# Add TypeScript to an existing Defold project
|
|
5
|
+
|
|
6
|
+
Use `bunx @defold-typescript/cli@latest init` inside a Defold project that already has `game.project`.
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
cd my-existing-defold-game
|
|
10
|
+
bunx @defold-typescript/cli@latest init
|
|
11
|
+
bun install
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Scaffold with the `@latest` tag: `init` writes your `@defold-typescript/types`
|
|
15
|
+
version pin, so a stale `bunx` cache would pin an older release. Then run
|
|
16
|
+
`bun install` once — `init` only declares the dev dependencies below; `install`
|
|
17
|
+
is what puts them in `node_modules` so the editor can resolve the Defold types.
|
|
18
|
+
|
|
19
|
+
The package is scoped — run it as `@defold-typescript/cli`. The bare
|
|
20
|
+
`bunx defold-typescript` resolves a nonexistent unscoped package and 404s unless
|
|
21
|
+
you have already installed `@defold-typescript/cli` locally.
|
|
22
|
+
|
|
23
|
+
When `game.project` exists, `init` does not synthesize a new Defold project. It only writes the TypeScript surface:
|
|
24
|
+
|
|
25
|
+
- `src/main.ts` — a starter entry script, written only when absent. `main.ts` is your source, not managed config, so an existing one is left untouched (and omitted from the reported `written` list) even under `--force`.
|
|
26
|
+
- `tsconfig.json`
|
|
27
|
+
- `package.json`
|
|
28
|
+
|
|
29
|
+
If `package.json` already exists, the command preserves its existing fields and merges these dev dependencies when they are missing:
|
|
30
|
+
|
|
31
|
+
- `@defold-typescript/types` — pinned to the CLI's own published version (the packages release in lockstep). This is the only `@defold-typescript/*` package your project needs; it is type-only and feeds the editor. The transpiler is a dependency of the CLI itself, pulled in when you run `build`/`watch`, so the scaffold does not add it.
|
|
32
|
+
- `@biomejs/biome`
|
|
33
|
+
|
|
34
|
+
If `package.json` does not exist, the command creates one.
|
|
35
|
+
|
|
36
|
+
## Conflicting config files
|
|
37
|
+
|
|
38
|
+
`init` refuses to overwrite an existing TypeScript or defold-typescript config. Remove or move the conflicting file before running the command.
|
|
39
|
+
|
|
40
|
+
Conflicts today are:
|
|
41
|
+
|
|
42
|
+
- `tsconfig.json`
|
|
43
|
+
- `defold-typescript.config.ts`
|
|
44
|
+
- `defold-typescript.config.mts`
|
|
45
|
+
- `defold-typescript.config.js`
|
|
46
|
+
|
|
47
|
+
Pass `--force` to overwrite a conflicting TS config (in new-project mode, `--force` also lets `init` synthesize into a non-empty directory). `--force` overwrites the config wholesale; it does not merge fields, so any settings you had in `tsconfig.json` are replaced by the scaffold config. `--force` refreshes managed config only — it never overwrites `src/main.ts`, since that file is your source.
|
|
48
|
+
|
|
49
|
+
## Build the Lua output
|
|
50
|
+
|
|
51
|
+
After initialization, write TypeScript under `src/` and run:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
bunx @defold-typescript/cli build
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The default `tsconfig.json` type-checks against `@defold-typescript/types` and writes generated Lua next to each `.ts` source. Files that call a lifecycle factory become Defold components: `defineScript` writes `<name>.ts.script`, `defineGuiScript` writes `<name>.ts.gui_script`, and `defineRenderScript` writes `<name>.ts.render_script`. Helper modules with no lifecycle factory write plain Lua modules such as `src/util.lua`, matching the `require(...)` path emitted for TypeScript imports. The scaffold also drops a `.gitignore` so generated component files, helper `.lua` modules under `src/`, and their `.map` siblings stay out of version control. Set a concrete `outDir` to collect the outputs under a separate tree instead.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
toc-title: Advanced CLI
|
|
3
|
+
---
|
|
4
|
+
# Advanced CLI
|
|
5
|
+
|
|
6
|
+
This page covers `wall`, the opt-in command for narrowing the API surface of a
|
|
7
|
+
source directory to a single script kind.
|
|
8
|
+
|
|
9
|
+
## Full surface by default
|
|
10
|
+
|
|
11
|
+
Defold scopes two namespaces to a script kind: `gui.*` resolves only inside a
|
|
12
|
+
`.gui_script`, and `render.*` only inside a `.render_script`. Every other
|
|
13
|
+
namespace (`go`, `msg`, `vmath`, `sys`, `physics`, …) is available in every kind.
|
|
14
|
+
|
|
15
|
+
By default `defold-typescript` gives you the **full** `@defold-typescript/types`
|
|
16
|
+
surface everywhere. `init`, `build`, and `watch` never change this: they scaffold
|
|
17
|
+
and build against whatever entrypoint your `tsconfig` names and never add, remove,
|
|
18
|
+
or prune a wall. The full surface never rejects a call the engine would allow, but
|
|
19
|
+
it also can't catch a `gui.*` use in a plain `.script`. To get the engine's wall
|
|
20
|
+
at compile time, opt in with `wall`.
|
|
21
|
+
|
|
22
|
+
## What a wall is
|
|
23
|
+
|
|
24
|
+
A wall is a composite `tsconfig.json` written into a single-kind source directory
|
|
25
|
+
that narrows `compilerOptions.types` to that kind's subpath:
|
|
26
|
+
|
|
27
|
+
| Script kind | `types` entrypoint | Namespaces |
|
|
28
|
+
| ----------- | ------------------ | ---------- |
|
|
29
|
+
| `.script` | `@defold-typescript/types/script` | universal only |
|
|
30
|
+
| `.gui_script` | `@defold-typescript/types/gui-script` | universal + `gui` |
|
|
31
|
+
| `.render_script` | `@defold-typescript/types/render-script` | universal + `render` |
|
|
32
|
+
|
|
33
|
+
The root `tsconfig.json` references each walled directory and excludes it from the
|
|
34
|
+
root program, so `tsc -b --noEmit` builds every walled directory against only its
|
|
35
|
+
narrowed surface — a `render.*` use inside a gui-walled directory becomes a compile
|
|
36
|
+
error, while the rest of the project stays full-surface.
|
|
37
|
+
|
|
38
|
+
A directory is **eligible** to be walled only when every `.ts` source in it is one
|
|
39
|
+
kind; a directory mixing kinds cannot be walled, because no single narrowing
|
|
40
|
+
applies. `build` and `watch` never touch walls — they are entirely yours to
|
|
41
|
+
manage.
|
|
42
|
+
|
|
43
|
+
## Interactive
|
|
44
|
+
|
|
45
|
+
Run `wall` with no arguments in a terminal:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
bunx @defold-typescript/cli wall
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
You get a checkbox of every eligible source directory, pre-checked to the
|
|
52
|
+
directories already walled. Checking an unwalled directory walls it; unchecking a
|
|
53
|
+
walled directory removes its wall. Mixed-kind directories appear disabled, with
|
|
54
|
+
their competing kinds shown as the reason. The final selection **is** the desired
|
|
55
|
+
wall set — the command reconciles the project on disk to exactly what you checked.
|
|
56
|
+
|
|
57
|
+
## Flags
|
|
58
|
+
|
|
59
|
+
For agents, CI, and scripted use, pass directories explicitly (a bare `wall` with
|
|
60
|
+
no TTY errors rather than hanging on an unrenderable prompt):
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
# Wall these directories (added to any already walled)
|
|
64
|
+
bunx @defold-typescript/cli wall src/ui src/rendering
|
|
65
|
+
|
|
66
|
+
# Remove a wall
|
|
67
|
+
bunx @defold-typescript/cli wall --remove src/ui
|
|
68
|
+
|
|
69
|
+
# List current and eligible walls (writes nothing)
|
|
70
|
+
bunx @defold-typescript/cli wall --list
|
|
71
|
+
bunx @defold-typescript/cli wall --list --json
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`--json` emits the resulting `directoryWalls` (and, for `--list`, the `eligible`
|
|
75
|
+
set) for machine consumption. `--json` is machine-driven intent, so a bare
|
|
76
|
+
`wall --json` never opens the interactive menu — pass directories or `--list`.
|
|
77
|
+
|
|
78
|
+
## Import the factory from the kind subpath
|
|
79
|
+
|
|
80
|
+
A wall only holds if a walled source imports its lifecycle factory from the **kind
|
|
81
|
+
subpath**, not the main entry:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// src/ui/hud.ts — inside a gui wall
|
|
85
|
+
import { defineGuiScript } from "@defold-typescript/types/gui-script"; // correct
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Importing the same factory from the main `@defold-typescript/types` entry pulls
|
|
89
|
+
*every* `declare global` namespace (including `render`) into the wall's program and
|
|
90
|
+
silently defeats the narrowing — `render.*` would type-check inside a gui wall:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
// Wrong: re-introduces the full surface, defeating the wall
|
|
94
|
+
import { defineGuiScript } from "@defold-typescript/types";
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The interactive snippets scaffolded by `init` already import from the kind
|
|
98
|
+
subpaths. `build` enforces this: a walled source that imports a lifecycle factory
|
|
99
|
+
from the main entry fails the build before transpile, naming the file and the kind
|
|
100
|
+
subpath it should import from instead. (`watch` does not enforce it yet — its
|
|
101
|
+
session path is a separate slice.)
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
---
|
|
2
|
+
toc-title: Agent runbooks
|
|
3
|
+
---
|
|
4
|
+
# Agent runbooks
|
|
5
|
+
|
|
6
|
+
Harness-neutral procedures for driving `defold-typescript` from an automated
|
|
7
|
+
agent. `defold-typescript` is a CLI published to npm (run with `bunx`) plus a
|
|
8
|
+
types package; it ships no
|
|
9
|
+
harness-specific skill or command assets, so the durable interface for an agent
|
|
10
|
+
is the CLI verbs themselves and their machine-readable `--json` output. Every
|
|
11
|
+
runbook below works from any harness: run the command, read the JSON envelope on
|
|
12
|
+
stdout, gate on `ok`.
|
|
13
|
+
|
|
14
|
+
Each one-shot command (`init`, `build`, `resolve`) prints a single JSON object
|
|
15
|
+
when given `--json`. The envelope is always one of two shapes:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{ "command": "<verb>", "ok": true, "written": ["<path>", "..."] }
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{ "command": "<verb>", "ok": false, "error": "<message>" }
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The agent branches on `ok`: on `true`, read `written` for the paths the command
|
|
26
|
+
created or updated; on `false`, read `error` for the failure reason. Some verbs
|
|
27
|
+
add fields to the success envelope (noted per runbook), but `command`, `ok`, and
|
|
28
|
+
either `written` or `error` are always present.
|
|
29
|
+
|
|
30
|
+
## Scaffold a project
|
|
31
|
+
|
|
32
|
+
**Goal:** create a new TypeScript surface — either a fresh project, or the
|
|
33
|
+
TypeScript layer added to an existing Defold project.
|
|
34
|
+
|
|
35
|
+
**Command (fresh project, empty folder):**
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
bunx @defold-typescript/cli@latest init --json
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Command (existing Defold project — run inside the folder that holds
|
|
42
|
+
`game.project`):**
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
bunx @defold-typescript/cli@latest init --json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`init` detects whether a `game.project` is already present and either scaffolds a
|
|
49
|
+
whole new project or adds the TypeScript surface alongside the existing one.
|
|
50
|
+
|
|
51
|
+
**Returns:**
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{ "command": "init", "ok": true, "written": ["tsconfig.json", "src/main.ts", "..."] }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
On failure:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{ "command": "init", "ok": false, "error": "<message>" }
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Reading `ok`:** if `ok` is `true`, the scaffold succeeded and `written` lists
|
|
64
|
+
every file created or modified — use it to know what to open next. If `ok` is
|
|
65
|
+
`false`, stop and surface `error`; nothing was scaffolded.
|
|
66
|
+
|
|
67
|
+
## Install the agent contract
|
|
68
|
+
|
|
69
|
+
**Goal:** drop an agent contract at the project root so any harness (or human)
|
|
70
|
+
opening the repo finds the conventions and a pointer to the installed guide.
|
|
71
|
+
|
|
72
|
+
**Command:**
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
bunx @defold-typescript/cli@latest init-agents --json
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
This writes two files. `AGENTS.md` carries a managed block delimited by HTML
|
|
79
|
+
comment markers; `CLAUDE.md` is the single line `@AGENTS.md`, re-exporting it.
|
|
80
|
+
Only the content **between** the markers is ever rewritten, so any notes you add
|
|
81
|
+
above or below the block survive re-runs untouched. If `AGENTS.md` already exists
|
|
82
|
+
without the markers, the block is appended after one blank line and your prior
|
|
83
|
+
content is left intact; a `CLAUDE.md` that already equals `@AGENTS.md` is left
|
|
84
|
+
byte-for-byte unchanged. The block is versionless — its guide pointer resolves to
|
|
85
|
+
`node_modules/@defold-typescript/cli/docs/guide/README.md`, which the install
|
|
86
|
+
swaps under the same path — so the verb is safe to re-run any time.
|
|
87
|
+
|
|
88
|
+
**Returns:**
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{ "command": "init-agents", "ok": true, "written": ["AGENTS.md", "CLAUDE.md"] }
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
On failure:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{ "command": "init-agents", "ok": false, "error": "<message>" }
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Reading `ok`:** if `ok` is `true`, `written` lists the files touched in order;
|
|
101
|
+
a re-run that changes nothing omits the untouched file. If `ok` is `false`, stop
|
|
102
|
+
and surface `error`; nothing was written.
|
|
103
|
+
|
|
104
|
+
## Regenerate extension types
|
|
105
|
+
|
|
106
|
+
**Goal:** refresh the ambient TypeScript surface for native extensions after a
|
|
107
|
+
`game.project` `[dependencies]` change, so extension namespaces stay in sync with
|
|
108
|
+
the declared archives. This automates the workflow described in
|
|
109
|
+
[Typing native extensions](./extensions.md).
|
|
110
|
+
|
|
111
|
+
**Command (run from the project root, after editing `[dependencies]`):**
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
defold-typescript resolve --json
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`resolve` reads each declared extension's `.script_api`, regenerates the
|
|
118
|
+
gitignored `.defold-types/extensions/` ambient surface, and rewrites its index
|
|
119
|
+
and `package.json` to exactly the declared set.
|
|
120
|
+
|
|
121
|
+
**Returns** the same one-shot envelope keyed `command: "resolve"`, plus an
|
|
122
|
+
`extensions` array reporting provenance for each resolved dependency:
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"command": "resolve",
|
|
127
|
+
"ok": true,
|
|
128
|
+
"written": [".defold-types/extensions/<namespace>.d.ts", "..."],
|
|
129
|
+
"extensions": [
|
|
130
|
+
{
|
|
131
|
+
"url": "<archive url>",
|
|
132
|
+
"provenance": "<cache | download>",
|
|
133
|
+
"namespaces": ["<namespace>"],
|
|
134
|
+
"scriptApiCount": 1,
|
|
135
|
+
"assetOnly": false
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
On failure:
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{ "command": "resolve", "ok": false, "error": "<message>" }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Reading `ok`:** if `ok` is `true`, the extension surface is current — `written`
|
|
148
|
+
lists the regenerated declaration files, and `extensions` records where each one
|
|
149
|
+
came from (`provenance`) and how many `.script_api` files it contributed
|
|
150
|
+
(`scriptApiCount`); an `assetOnly` dependency contributes no types. If `ok` is
|
|
151
|
+
`false`, surface `error`; the existing surface is left untouched.
|
|
152
|
+
|
|
153
|
+
## Add a script
|
|
154
|
+
|
|
155
|
+
**Goal:** add a new gameplay script to a TypeScript Defold project and attach it
|
|
156
|
+
so it runs. There is no `add` verb — the workflow composes ordinary file
|
|
157
|
+
creation with the shipped `build` verb, then a scene-file edit to wire the
|
|
158
|
+
compiled component.
|
|
159
|
+
|
|
160
|
+
**1. Write the source.** One Defold script per file under `src/`, exporting a
|
|
161
|
+
single lifecycle factory as `default` (never two in one file). The factory
|
|
162
|
+
decides the compiled kind:
|
|
163
|
+
|
|
164
|
+
| Source factory | Compiled artifact | Referenced by |
|
|
165
|
+
| -------------- | ----------------- | ------------- |
|
|
166
|
+
| `defineScript` | `<name>.ts.script` | a game object (`.go` / `.collection`) as a component |
|
|
167
|
+
| `defineGuiScript` | `<name>.ts.gui_script` | a GUI scene (`.gui`), as its **Script** property |
|
|
168
|
+
| `defineRenderScript` | `<name>.ts.render_script` | the render pipeline (a `.render` file, set via `game.project`) |
|
|
169
|
+
|
|
170
|
+
A source that calls no factory emits a plain `<name>.lua` module to `import`
|
|
171
|
+
instead. Which hooks to export (`init`, `update`, `fixed_update`, `on_message`,
|
|
172
|
+
`on_input`, `final`, …) and how `self` is typed are covered in
|
|
173
|
+
[Script lifecycle](./script-lifecycle.md).
|
|
174
|
+
|
|
175
|
+
**2. Build** (from the project root):
|
|
176
|
+
|
|
177
|
+
```sh
|
|
178
|
+
bunx @defold-typescript/cli@latest build --json
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Or, if a [`watch --json`](#fix-the-lua-output) is already running, just save the
|
|
182
|
+
file and read its `rebuild` event instead of invoking `build`.
|
|
183
|
+
|
|
184
|
+
**Returns** the one-shot envelope keyed `command: "build"`, plus the build
|
|
185
|
+
context fields:
|
|
186
|
+
|
|
187
|
+
```json
|
|
188
|
+
{
|
|
189
|
+
"command": "build",
|
|
190
|
+
"ok": true,
|
|
191
|
+
"written": ["src/<name>.ts.script", "..."],
|
|
192
|
+
"defoldVersion": "<version>",
|
|
193
|
+
"defoldChannel": "<stable | beta | alpha>",
|
|
194
|
+
"apiSurface": "<surface id>",
|
|
195
|
+
"materializedSurface": "<path | null>"
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
On failure:
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{ "command": "build", "ok": false, "error": "<message>" }
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Reading `ok`:** if `ok` is `true`, the script transpiled — `written` lists the
|
|
206
|
+
emitted artifact (`.ts.script`, `.ts.gui_script`, or `.ts.render_script`) to
|
|
207
|
+
attach next; `defoldVersion` and `apiSurface` record which API surface it was
|
|
208
|
+
built against; `defoldChannel` records the resolved release channel (`stable`
|
|
209
|
+
unless pinned or passed via `--channel`; it does not yet change which surface is
|
|
210
|
+
fetched). If `ok` is `false`, the build failed — surface `error` and follow
|
|
211
|
+
[Fix the Lua output](#fix-the-lua-output).
|
|
212
|
+
|
|
213
|
+
**What `build` writes.** Each `.ts` source under `src/` produces exactly one
|
|
214
|
+
output in the Defold project tree — a script component or a `<name>.lua` module,
|
|
215
|
+
per the table above. `import` is rewritten to `require("<module>")` resolving
|
|
216
|
+
against the emitted `.lua`, so a shared module must be built for its require to
|
|
217
|
+
resolve at runtime. Two runtime modules are synthesized at the output root on
|
|
218
|
+
demand: `lualib_bundle.lua` (when a source uses a TS runtime helper like
|
|
219
|
+
`Object.keys` or spread) and `defold_typescript_timers.lua` (when timers are
|
|
220
|
+
used).
|
|
221
|
+
|
|
222
|
+
Who creates these: typescript-to-lua (TSTL) produces the Lua *content* in
|
|
223
|
+
memory; the CLI writes the *files*, choosing the `.ts.script` / `.ts.gui_script`
|
|
224
|
+
/ `.ts.render_script` / `.lua` name and location. TSTL never touches disk — that
|
|
225
|
+
is why the outputs carry Defold-correct extensions instead of plain `.lua`.
|
|
226
|
+
Treat the `--json` `written` array as the authoritative list of what landed; do
|
|
227
|
+
not infer paths.
|
|
228
|
+
|
|
229
|
+
**3. Attach the compiled script.** Building only produces the artifact; nothing
|
|
230
|
+
runs until a scene references it as a component. Scene files (`.go`,
|
|
231
|
+
`.collection`, `.gui`, `.render`) reference the **compiled** artifact, never the
|
|
232
|
+
`.ts` source. A game object references a `.ts.script` through a `components { … }`
|
|
233
|
+
entry naming its project-root-absolute path:
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
components {
|
|
237
|
+
id: "player"
|
|
238
|
+
component: "/src/player.ts.script"
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
In a standalone `.go` the block appears verbatim. When the game object is
|
|
243
|
+
embedded in a `.collection`, Defold stores the same block as an escaped string
|
|
244
|
+
inside an `embedded_instances { data: "…" }` entry — the form to write when
|
|
245
|
+
editing the file directly. The platformer example shows this in
|
|
246
|
+
[`game/player.collection`](../examples/platformer/game/player.collection):
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
embedded_instances {
|
|
250
|
+
id: "player"
|
|
251
|
+
data: "components {\n"
|
|
252
|
+
" id: \"player\"\n"
|
|
253
|
+
" component: \"/src/player.ts.script\"\n"
|
|
254
|
+
...
|
|
255
|
+
"}\n"
|
|
256
|
+
...
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
A `.ts.gui_script` is referenced by a `.gui` scene's **Script** property and a
|
|
261
|
+
`.ts.render_script` by the render pipeline, the same compiled-artifact rule.
|
|
262
|
+
|
|
263
|
+
**4. Verify the attachment.** Two checks confirm the script is wired in:
|
|
264
|
+
|
|
265
|
+
- Grep the scene files for the compiled name:
|
|
266
|
+
|
|
267
|
+
```sh
|
|
268
|
+
grep -rl "player.ts.script" --include="*.go" --include="*.collection" --include="*.gui" .
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
A hit means a scene references it; no hit means it is orphaned and will not
|
|
272
|
+
run.
|
|
273
|
+
|
|
274
|
+
- Defold compiles only **reachable** resources, so after a Defold build
|
|
275
|
+
(the CLI's `defold build` subcommand) an attached script produces `build/default/src/player.ts.scriptc`
|
|
276
|
+
— the path mirrors the source — while an orphaned script never appears under
|
|
277
|
+
`build/default/` at all. (The `_generated_*` artifacts there are inlined or
|
|
278
|
+
asset-derived resources — embedded game objects, components, textures — never
|
|
279
|
+
referenced script files, which keep their source path.)
|
|
280
|
+
|
|
281
|
+
## Narrow engine callback payloads
|
|
282
|
+
|
|
283
|
+
**Goal:** type an engine callback's untyped payload without hand-rolling `typeof`/`in`/`as`.
|
|
284
|
+
|
|
285
|
+
**Rule for clankers:** when an engine callback hands back an untyped or `unknown`
|
|
286
|
+
payload that is discriminated by a *separate* id/event argument, reach for the
|
|
287
|
+
**provided declaration-only guard** before reaching for `typeof`/`in`/`as`.
|
|
288
|
+
TypeScript cannot correlate two independent parameters, and Defold's ids/event
|
|
289
|
+
constants are pre-hashed `Hash` values or branded numbers — neither works as a
|
|
290
|
+
discriminated-union tag. Each guard below re-introduces the discriminant at the
|
|
291
|
+
use site. They are all **declaration-only**: the transpiler lowers each call to
|
|
292
|
+
its runtime comparison, so there is no runtime Lua symbol and **no import to
|
|
293
|
+
add**.
|
|
294
|
+
|
|
295
|
+
`on_message` — narrow one message with `isMessage`, or route many with `onMessage`:
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
export default defineScript({
|
|
299
|
+
on_message(self, message_id, message) {
|
|
300
|
+
if (isMessage(message_id, message, "contact_point_response")) {
|
|
301
|
+
// message narrowed to the contact_point_response payload — no cast.
|
|
302
|
+
print(message.distance);
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
`window.set_listener` — narrow the callback's `data` with `isWindowEvent`:
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
window.set_listener((self, event, data) => {
|
|
312
|
+
if (isWindowEvent(event, data, window.WINDOW_EVENT_RESIZED)) {
|
|
313
|
+
// data narrowed to { width: number; height: number } — no cast.
|
|
314
|
+
print(data.width, data.height);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
An unknown id/event constant is a compile error, so the guard also catches typos.
|
|
320
|
+
The full narrowing reference, including `onMessage`'s multi-message dispatcher,
|
|
321
|
+
lives in [Script lifecycle](./script-lifecycle.md#receiving-messages-with-type-narrowing)
|
|
322
|
+
and the [`window.set_listener` gotcha](./typescript-gotchas.md#windowsetlistener-hands-event-and-data-as-separate-params).
|
|
323
|
+
|
|
324
|
+
## Fix the Lua output
|
|
325
|
+
|
|
326
|
+
**Goal:** recover from a transpile failure reported by `build` or `watch`.
|
|
327
|
+
|
|
328
|
+
**Command (re-run after each source fix):**
|
|
329
|
+
|
|
330
|
+
```sh
|
|
331
|
+
bunx @defold-typescript/cli@latest build --json
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
On a transpile failure the one-shot `build --json` envelope carries the message:
|
|
335
|
+
|
|
336
|
+
```json
|
|
337
|
+
{ "command": "build", "ok": false, "error": "<message>" }
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Under a long-lived `watch --json`, the same failure arrives as an NDJSON event on
|
|
341
|
+
stdout (one JSON object per line) keyed `command: "watch"`:
|
|
342
|
+
|
|
343
|
+
```json
|
|
344
|
+
{ "command": "watch", "event": "rebuild", "ok": false, "error": "<message>" }
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
The first build emits `event: "build"`; each later rebuild emits
|
|
348
|
+
`event: "rebuild"`. `build` and the transpile-diagnostics pass share one
|
|
349
|
+
diagnostic run, so the `error` names the offending source span.
|
|
350
|
+
|
|
351
|
+
**Reading `ok`:** while `ok` is `false`, read `error` for the failing span,
|
|
352
|
+
then fix the source and rebuild. Two pages route the fix:
|
|
353
|
+
[TypeScript gotchas](./typescript-gotchas.md) for the runtime-semantics traps
|
|
354
|
+
that compile clean but surprise under Lua, and
|
|
355
|
+
[Transpile diagnostics](./transpile-diagnostics.md) for what the diagnostic pass
|
|
356
|
+
surfaces. Repeat until `ok` is `true`, then read `written` as in
|
|
357
|
+
[Add a script](#add-a-script).
|