> For the complete documentation index, see [llms.txt](https://developers-apps-in-toss.toss.im/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers-apps-in-toss.toss.im/documentation/api-and-sdk-en/unity/build/build-process.md).

# Build pipeline

How a Unity project becomes deployable `.ait` explains what the SDK does internally until it becomes a package.

> **Target**: SDK contributors. If your goal is to build games using the SDK [Build Profiles](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-profiles)and [Build customization](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-customization)this document is required.

### Two-stage pipeline structure

The build is divided into the stage where Unity produces the WebGL output and the stage where that output is repackaged into a web project and packaged with granite.

```
┌─────────────────────────────────────────────────────────────────────┐
│                        Entry Points                                 │
│  Menu: Build & Package  │  Build Window  │  Server Start/Restart    │
│  AppsInTossMenu.cs      │  AppsInTossBuildWindow.cs                 │
└─────────────┬───────────────────────────────────────────────────────┘
              │
              ▼
┌─────────────────────────────────────────────────────────────────────┐
│  AITConvertCore.DoExport(buildWebGL, doPackaging, cleanBuild,       │
│                          profile, profileName)                      │
│  or DoExportAsync(...)                                             │
└─────────────┬───────────────────────────────────────────────────────┘
              │
     ┌────────┴────────────────────────────────┐
     ▼                                         ▼
┌──────────────────────┐          ┌────────────────────────────────┐
│  Phase 1: WebGL Build│          │  Phase 2: Packaging            │
│  BuildWebGL()        │          │  GenerateMiniAppPackage()      │
│                      │          │  → AITPackageBuilder           │
│  - Init()            │          │    .PackageWebGLBuild()        │
│  - BuildPipeline     │          │                                │
│  - .ait-build-info   │          │  2a. Copy BuildConfig          │
│                      │          │  2b. Copy WebGL→public         │
│  Output: webgl/      │          │  2c. Replace placeholders      │
│                      │          │  2d. Insert loading screen     │
│                      │          │  2e. pnpm install              │
│                      │          │  2f. granite build             │
│                      │          │                                │
│                      │          │  Output: ait-build/dist/       │
└──────────────────────┘          └────────────────────────────────┘
```

#### Call matrix

| Entry point               | buildWebGL | doPackaging | cleanBuild |
| ------------------------- | ---------- | ----------- | ---------- |
| `Build & Package`         | `true`     | `true`      | `false`    |
| `Build & Package (clean)` | `true`     | `true`      | `true`     |
| `Deploy (Test)`           | `true`     | `true`      | `false`    |
| `Deploy (Production)`     | `true`     | `true`      | `true`     |
| `Dev Server Start`        | `true`     | `true`      | `false`    |
| `Restart Server`          | `true`     | `true`      | `false`    |
| `Restart (server-only)`   | —          | —           | —          |

> **Note**: `Restart (server-only)`is `DoExport`restarts only the granite process without calling it.

### Phase 0 Initialization

#### Template synchronization

`AITTemplateManager.EnsureWebGLTemplatesExist`copies the SDK's WebGL templates into the project before the build.

SDK template lookup order:

1. `Packages/im.toss.apps-in-toss-unity-sdk/WebGLTemplates/`
2. `Packages/com.appsintoss.miniapp/WebGLTemplates/`
3. Assembly path-based (`typeof(AITConvertCore).Assembly.Location` above)

If the project `Assets/WebGLTemplates/AITTemplate/`is absent, the whole template is copied; if present, it is updated based on markers to preserve user-customized areas. Below **Template merge timing** section.

#### Build settings

`AITBuildInitializer.Init`This automatically configures Unity PlayerSettings.

| Settings              | Value                          | Notes                                                                                |
| --------------------- | ------------------------------ | ------------------------------------------------------------------------------------ |
| WebGL Template        | `PROJECT:AITTemplate`          | Hardcoded                                                                            |
| Linker Target         | `Wasm`                         | Hardcoded                                                                            |
| Scripting Backend     | `IL2CPP`                       | Hardcoded                                                                            |
| Memory Size           | 256\~1536MB                    | Default values by Unity version (user override possible)                             |
| Compression           | `Brotli`                       | Default. `decompressionFallback`is enabled, so it is available in all Unity versions |
| Threading             | `false`                        | Default (mobile browser compatibility)                                               |
| Data Caching          | `false`                        | Default value                                                                        |
| `nameFilesAsHashes`   | User settings (default `true`) | forced only in Unity 2021.x `false` — `true`then the Bee build loop bug occurs       |
| Engine Code Stripping | User settings                  | —                                                                                    |
| Managed Stripping     | `High`                         | Default value                                                                        |
| IL2CPP Config         | User settings                  | —                                                                                    |

The single source of truth for defaults is `AITEditorScriptObject`the `GetDefault*` is a static method. Only the Dev Server profile lowers compression `Disabled`to — profile-specific differences are [Build Profiles](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-profiles)documented there.

Default memory by version:

* Unity 2021.3: 256MB
* Unity 2022.3: 512MB
* Unity 6 (2023.3+): 1024MB
* Unity 2024.2+: 1536MB

Environment variable overrides applied to the profile are `AITBuildInitializer.ApplyEnvironmentVariableOverrides`handles it. The list of variables and values are [Build Profiles](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-profiles)the canonical source.

#### Config validation

`DoExport`at startup `UnityUtil.GetEditorConf()`reads the settings asset, and if the asset itself cannot be found `INVALID_APP_CONFIG`is returned.

Leaving the app ID or icon URL empty does not block the build. The app ID is only a condition that disables the build button in the Configuration window (`AITEditorScriptObject.IsAppNameValid`) only, and the icon URL is only format-checked when entered. In other words, if you build with empty values, `%AIT_ICON_URL%` and so on are replaced with empty strings in the resulting package.

### Phase 1 WebGL build

`AITConvertCore.BuildWebGL()`

#### Execution flow

```
1. AITBuildInitializer.Init(profile)
   ├── Automatically configure PlayerSettings
   ├── Apply environment variable overrides
   └── Output build profile log

2. If cleanBuild:
   └── Delete the webgl/ directory

3. BuildPipeline.BuildPlayer()
   ├── scenes: EditorBuildSettings.scenes (checked only)
   ├── locationPathName: "{projectPath}/webgl"
   ├── target: WebGL
   └── options: BuildOptions.None (add BuildOptions.CleanBuildCache if cleanBuild)

4. Check BuildReport
   ├── Success → write .ait-build-info.json
   └── Failure → AITErrorReporter.SetBuildReport(report) + return error

5. Write build marker: webgl/.ait-build-info.json
```

#### Build marker

After a successful WebGL build `webgl/.ait-build-info.json`writes metadata to it. The schema is `AITConvertCore.cs`the `AITBuildInfo` class.

```json
{
    "sdkVersion": "1.7.0",
    "buildTime": "2024-03-01T12:00:00.0000000Z",
    "compressionFormat": 2,
    "decompressionFallback": true,
    "profileName": "Production",
    "unityVersion": "6000.2.15f1"
}
```

| Field                   | Description                                                                                                    |
| ----------------------- | -------------------------------------------------------------------------------------------------------------- |
| `sdkVersion`            | SDK package version                                                                                            |
| `buildTime`             | UTC ISO 8601 build time                                                                                        |
| `compressionFormat`     | `PlayerSettings.WebGL.compressionFormat` int value (0=Disabled, 1=Gzip, 2=Brotli)                              |
| `decompressionFallback` | `PlayerSettings.WebGL.decompressionFallback`. If enabled, the output file extension becomes `.unityweb`becomes |
| `profileName`           | "Development" or "Production"                                                                                  |
| `unityVersion`          | `Application.unityVersion`                                                                                     |

`ReadBuildMarker(webglPath)`reads the marker and `AITBuildInfo`returns it, and if the file is missing or parsing fails `null`.

Uses of the marker:

* **Build cache validation** (`ShouldForceCleanBuild`) — automatically performs a clean build if the Unity version mismatches or the marker is missing
* **Compression format detection** (`CopyWebGLToPublic`) — `compressionFormat`and `decompressionFallback`to determine the exact extension

#### Build cache validity check

`ShouldForceCleanBuild(outputPath, cleanBuild)`validates the existing cache before the WebGL build.

```
1. cleanBuild=true → always clean build
2. No webgl/ folder → new build (clean not needed)
3. No build marker → clean build (older SDK version or corruption)
4. Unity version mismatch → clean build (ensure build output compatibility)
5. Missing Build/*.loader.js → clean build (required file missing)
6. All checks pass → incremental build
```

Unity's `BuildPipeline`performs incremental builds by default, so `webgl/`if it remains, only changed assets are rebuilt. `cleanBuild=true`if `webgl/`delete it and `BuildOptions.CleanBuildCache`perform a full build with it.

### Phase 2 packaging

`AITPackageBuilder.PackageWebGLBuild()` (sync) or `PackageWebGLBuildAsync()` (async). The two paths `PreparePackaging()`share common preparation logic through it.

```
PreparePackaging() ← common to sync/async
Wait for Node.js/pnpm installation
Create the ait-build/ directory
CopyBuildConfigFromTemplate()
Copy SDK BuildConfig~/ → ait-build/
Copy pnpm-lock.yaml
CopyWebGLToPublic()
├── Verify webgl/Build/ (detect files by compression format)
├── Copy Build → ait-build/public/Build/
├── Copy TemplateData → ait-build/public/TemplateData/
├── Copy Runtime → ait-build/public/Runtime/
├── Replace index.html placeholders → ait-build/index.html
├── Insert loading screen
└── Placeholder validation
Check pnpm path
ValidateNodeModulesIntegrity()

Sync path: RunPnpmInstallSync() → RunGraniteBuildSync()
Async path: RunPnpmInstallAsync() → RunGraniteBuildAsync()

Output: ait-build/dist/
```

#### BuildConfig copy

`CopyBuildConfigFromTemplate`of this SDK `WebGLTemplates/AITTemplate/BuildConfig~/`changed `ait-build/`is copied there.

| File                     | Handling                                                           |
| ------------------------ | ------------------------------------------------------------------ |
| `package.json`           | Merge dependencies                                                 |
| `tsconfig.json`          | Merge compilerOptions                                              |
| `vite.config.ts`         | `%AIT_VITE_HOST%`, `%AIT_VITE_PORT%` Replace                       |
| `granite.config.ts`      | Replace 13 placeholders                                            |
| `apps-in-toss.config.ts` | 3.x configuration file. `granite.config.ts`placeholder set such as |
| `pnpm-lock.yaml`         | copy if present                                                    |

The actual merge rules are in `Package/BuildConfigMerger.cs`in.

#### Copy WebGL to public

`CopyWebGLToPublic`If it is `webgl/` the output `ait-build/` is reorganized into the following structure.

```
webgl/
├── Build/
│   ├── webgl.loader.js          → ait-build/public/Build/
│   ├── webgl.data               → ait-build/public/Build/
│   ├── webgl.framework.js       → ait-build/public/Build/
│   └── webgl.wasm               → ait-build/public/Build/
├── TemplateData/                → ait-build/public/TemplateData/
├── Runtime/                     → ait-build/public/Runtime/
└── index.html                   → ait-build/index.html (after replacement)
```

In this process, placeholder replacement and loading screen insertion happen together. The replacement rules are below **Placeholder replacement** section. The loading screen's behavior and customization are [Loading screen customization](https://developers-apps-in-toss.toss.im/documentation/unity/build/loading-screen-customization)the canonical source.

#### pnpm install

**Determining whether to skip installation.** To avoid rerunning install on every build, immediately after a successful install `Package/PnpmInstallStateMarker.cs`is `package.json`·`pnpm-lock.yaml`the content hash and pnpm version of `ait-build/node_modules/.ait-install-state.json`are recorded there. On the next build, if all these values match and `node_modules` integrity verification passes, install is skipped.

The marker `node_modules` **inside** is placed there because `NodeModulesValidator.CleanNodeModules`is `node_modules`if you delete the entire NodeModulesValidator.CleanNodeModules, the marker is invalidated too — this automatically aligns with the clean step in the retry policy. Any case where judgment is impossible, such as when the marker is missing or parsing fails, is treated fail-closed as "cannot skip." This is because the cost of an incorrect skip (build failure) is greater than the cost of unnecessary reinstalling (time wasted).

The kill switch is the environment variable `AIT_DISABLE_INSTALL_SKIP`. `1`/`true`if set, it disables skipping, and if the value cannot be interpreted, it logs a warning and also disables skipping — it operates fail-safe so a typo can't neutralize the kill switch.

**3-step retry.** When not skipping `PnpmInstallStages` proceeds in the order defined in the array.

```
┌──────────────────────────────────────┐
│  ValidateNodeModulesIntegrity()      │
│  web-framework version mismatch?      │
│  → delete node_modules and reinstall  │
└─────────────┬────────────────────────┘
              │
              ▼
┌──────────────────────────────────────┐
│  1st: pnpm install --frozen-lockfile │  ← fastest (no lockfile changes)
│  Success? → done                     │
│  Failure? ↓                          │
├──────────────────────────────────────┤
│  2nd: pnpm install                   │  ← allow lockfile updates
│       --no-frozen-lockfile           │
│  Success? → done                     │
│  Failure? ↓                          │
├──────────────────────────────────────┤
│  3rd: CleanNodeModules()             │  ← delete node_modules + .npm-cache
│       + pnpm install                 │
│         --no-frozen-lockfile         │
│  Success? → done                     │
│  Failure? → FAIL_NPM_BUILD error     │
└──────────────────────────────────────┘
```

`ValidateNodeModulesIntegrity()`decision order:

1. `node_modules/`If absent, valid (freshly installed)
2. `node_modules/.pnpm/` If the directory does not exist, invalid (stale modules)
3. `package.json`in `@apps-in-toss/web-framework` Extract version
4. `node_modules/.pnpm/@apps-in-toss+web-framework@{version}*/` Check existence — if version mismatch or package missing, warn and mark invalid

#### granite build

```bash
pnpm run build   # → run granite build
```

If it fails `CleanNodeModules()` → `pnpm install --no-frozen-lockfile` → `pnpm run build`retry once with it, and if it still fails `FAIL_NPM_BUILD`is returned.

The output is `ait-build/dist/`and here `.ait` if the file is missing `DIST_FOLDER_MISSING` or `AIT_FILE_MISSING`follow.

### File signature detection and verification

`AITBuildValidator`verifies the existence and integrity of WebGL outputs.

#### Search patterns by compression format

`GetFilePatterns(compressionFormat, decompressionFallback)`Determine the search patterns according to this build marker value.

| Condition                      | data pattern      | framework pattern         | wasm pattern      |
| ------------------------------ | ----------------- | ------------------------- | ----------------- |
| `decompressionFallback = true` | `*.data.unityweb` | `*.framework.js.unityweb` | `*.wasm.unityweb` |
| `0` Disabled                   | `*.data`          | `*.framework.js`          | `*.wasm`          |
| `1` Gzip                       | `*.data.gz`       | `*.framework.js.gz`       | `*.wasm.gz`       |
| `2` Brotli                     | `*.data.br`       | `*.framework.js.br`       | `*.wasm.br`       |
| Otherwise (fallback)           | `*.data*`         | `*.framework.js*`         | `*.wasm*`         |

`decompressionFallback`If this is enabled, it takes precedence over the compression format. Since the loader is not compressed, it is always `*.loader.js`.

#### File detection

`FindFileInBuild(buildPath, pattern, isRequired)`Finds files using a glob pattern. `*.data*` The same trailing wildcard also `*.data.meta`matches, so `.meta`is excluded from the results — if it isn't, `LastWriteTime` in the sorting `.meta`is selected as the newest and the wrong file name is returned.

| Pattern           | Required | Description        |
| ----------------- | -------- | ------------------ |
| `*.loader.js`     | Yes      | Unity WebGL loader |
| `*.data*`         | Yes      | Game data          |
| `*.framework.js*` | Yes      | Unity framework    |
| `*.wasm*`         | Yes      | WebAssembly binary |
| `*.symbols.json*` | No       | Debug symbols      |

**Automatically cleaned up when duplicate matches occur.** If multiple files match one pattern, `LastWriteTime` sort them in descending order (descending file name order in case of a tie), keep only the newest one, and `.meta`delete the rest together. We changed from leaving only warning logs because it happened repeatedly on every build and created Sentry noise. If any file fails to delete, an informational log recommending a Clean Build is left.

**Missing required file.** `isRequired=true`If a pattern can't be found, only the first line is sent to Sentry (so fingerprints are grouped stably by pattern), and the remaining diagnostic lines are left only in the console. Diagnostics include the search path, the following string, and the actual file list in the Build folder (or the fact that it is empty).

```
If this file is missing, a 'createUnityInstance is not defined' error occurs at runtime.
```

The return value is an empty string, and in the caller `REQUIRED_FILE_MISSING`follow.

#### Placeholder substitution validation

`ValidatePlaceholderSubstitution(content, filePath)`This regex `%[A-Z_]+%`finds unsubstituted placeholders.

Critical (error + build failure):

* `%UNITY_WEBGL_LOADER_URL%`
* `%UNITY_WEBGL_DATA_URL%`
* `%UNITY_WEBGL_FRAMEWORK_URL%`
* `%UNITY_WEBGL_CODE_URL%`

Other `%...%` patterns only produce warnings. Empty path patterns like the following are also treated as critical.

```html
src="Build/"     ← means loader.js is missing
"Build/"         ← means the data file is missing
Build/",         ← empty file name after the separator
```

`apps-in-toss.config.ts`In the case of SDK\_GENERATED, unsubstituted values are hard errors, but SDK placeholders left in USER\_CONFIG or keys moved in 3.x remain warnings — because SDK values take precedence when merged, the build result is normal.

#### Build completion report

`PrintBuildReport(buildProjectPath, distPath)`is `ait-build/public/Build/`scans for the four required patterns and one optional pattern and prints whether they exist, along with their file sizes. If a required pattern is missing, `Debug.LogError`as `[Missing!]`is displayed, and optional patterns are shown only when present.

### Placeholder replacement

#### index.html

`AITPackageBuilder.CopyWebGLToPublic()`is performed in.

**Unity standard**

| placeholder               | source                                  |
| ------------------------- | --------------------------------------- |
| `%UNITY_WEB_NAME%`        | `PlayerSettings.productName`            |
| `%UNITY_WIDTH%`           | `PlayerSettings.defaultWebScreenWidth`  |
| `%UNITY_HEIGHT%`          | `PlayerSettings.defaultWebScreenHeight` |
| `%UNITY_COMPANY_NAME%`    | `PlayerSettings.companyName`            |
| `%UNITY_PRODUCT_NAME%`    | `PlayerSettings.productName`            |
| `%UNITY_PRODUCT_VERSION%` | `PlayerSettings.bundleVersion`          |

**Unity WebGL URL** — build fails if not substituted.

| placeholder                   | Substitution value                      |
| ----------------------------- | --------------------------------------- |
| `%UNITY_WEBGL_LOADER_URL%`    | `Build/{loaderFile}`                    |
| `%UNITY_WEBGL_DATA_URL%`      | `Build/{dataFile}`                      |
| `%UNITY_WEBGL_FRAMEWORK_URL%` | `Build/{frameworkFile}`                 |
| `%UNITY_WEBGL_CODE_URL%`      | `Build/{wasmFile}`                      |
| `%UNITY_WEBGL_SYMBOLS_URL%`   | `Build/{symbolsFile}` (or empty string) |

**Legacy file names** — for backward compatibility. Only the file name is substituted, without a path.

| placeholder                        | Substitution value |
| ---------------------------------- | ------------------ |
| `%UNITY_WEBGL_LOADER_FILENAME%`    | `{loaderFile}`     |
| `%UNITY_WEBGL_DATA_FILENAME%`      | `{dataFile}`       |
| `%UNITY_WEBGL_FRAMEWORK_FILENAME%` | `{frameworkFile}`  |
| `%UNITY_WEBGL_CODE_FILENAME%`      | `{wasmFile}`       |
| `%UNITY_WEBGL_SYMBOLS_FILENAME%`   | `{symbolsFile}`    |

**AIT custom**

| placeholder                  | Substitution value   | Description                                                                                                                                                                   |
| ---------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `%AIT_ENABLE_DEBUG_CONSOLE%` | `"true"` / `"false"` | Enable debug console                                                                                                                                                          |
| `%AIT_DEVICE_PIXEL_RATIO%`   | number               | device pixel ratio                                                                                                                                                            |
| `%AIT_ICON_URL%`             | URL string           | App icon URL                                                                                                                                                                  |
| `%AIT_DISPLAY_NAME%`         | string               | App display name                                                                                                                                                              |
| `%AIT_PRIMARY_COLOR%`        | color code           | Brand color (default: `#3182f6`)                                                                                                                                              |
| `%AIT_PRELOAD_TAGS%`         | HTML tag             | `<link rel="preload">` Tags                                                                                                                                                   |
| `%AIT_LOADING_SCREEN%`       | HTML string          | The entire content of the loading screen. [Loading screen customization](https://developers-apps-in-toss.toss.im/documentation/unity/build/loading-screen-customization) Note |

#### Preload tags

`GeneratePreloadTags(dataFile, wasmFile, frameworkFile)`is generated.

```html
<link rel="preload" href="Build/webgl.data" as="fetch">
<link rel="preload" href="Build/webgl.wasm" as="fetch">
```

> **Important**: framework.js is not preloaded. If the Unity loader loads framework.js `<script>` with a tag, `as="fetch"` the preload cache key differs, which can cause duplicate downloads. This increases memory pressure and raises the likelihood of intermittent initialization failures (ASM\_CONSTS errors).

#### granite.config.ts

`Package.BuildConfigMerger.UpdateGraniteConfig()`replaces 13 items.

| placeholder                                 | source                                   |
| ------------------------------------------- | ---------------------------------------- |
| `%AIT_APP_NAME%`                            | `config.appName`                         |
| `%AIT_DISPLAY_NAME%`                        | `config.displayName`                     |
| `%AIT_PRIMARY_COLOR%`                       | `config.primaryColor`                    |
| `%AIT_ICON_URL%`                            | `config.iconUrl`                         |
| `%AIT_BRIDGE_COLOR_MODE%`                   | `config.GetBridgeColorModeString()`      |
| `%AIT_WEBVIEW_TYPE%`                        | `config.GetWebViewTypeString()`          |
| `%AIT_NAVIGATION_BAR%`                      | `config.GetNavigationBarJson()`          |
| `%AIT_ALLOWS_INLINE_MEDIA_PLAYBACK%`        | `config.allowsInlineMediaPlayback`       |
| `%AIT_MEDIA_PLAYBACK_REQUIRES_USER_ACTION%` | `config.mediaPlaybackRequiresUserAction` |
| `%AIT_VITE_HOST%`                           | `config.viteHost`                        |
| `%AIT_VITE_PORT%`                           | `config.vitePort`                        |
| `%AIT_PERMISSIONS%`                         | `config.GetPermissionsJson()`            |
| `%AIT_OUTDIR%`                              | `config.outdir`                          |

#### vite.config.ts

`%AIT_VITE_HOST%` → `config.viteHost`, `%AIT_VITE_PORT%` → `config.vitePort`.

### Template merge timing

`AITTemplateManager`merges the SDK template and the project's custom areas based on markers. The marker syntax and the areas users can edit are [Build customization](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-customization)the source of truth. Here, we only cover when and what gets merged. **when and what** happens.

The merge happens in Phase 0 `EnsureWebGLTemplatesExist`that is, before the Unity WebGL build starts.

```
When updating the SDK:
  ├── index.html:
  │   ├── no markers (older version) → replace with SDK template + warning
  │   └── markers present → preserve USER_HEAD, USER_BODY_END areas, update the rest
  ├── vite.config.ts, granite.config.ts, apps-in-toss.config.ts:
  │   └── preserve USER_CONFIG area, update SDK_GENERATED area
  ├── Runtime/ → always overwrite with SDK version (debug console, etc.)
  └── TemplateData/ → always overwrite with SDK version
```

If an older version of index.html without markers is found, issue the following warning and replace it with the SDK template.

```
[AIT] Template update: Replacing the old version template with a new marker-based template.
⚠️ If the existing index.html had custom changes, reapply them manually to the USER_* marker areas.
```

### Managing Node.js and pnpm

Regardless of the system installation, the SDK downloads and uses its own Node.js. The single source of truth for the version is `AITNodeJSDownloader.cs`the `NODE_VERSION`and `AITPackageManagerHelper.cs`the `PNPM_VERSION`.

`PNPM_VERSION`is `package.json`, `sdk-runtime-generator~/package.json`, `WebGLTemplates/AITTemplate/BuildConfig~/package.json` must always match the `packageManager` fields in all three places. If the values diverge, the pnpm used by the client and the pnpm that updated the lockfile will differ, causing specifier drift.

The installation path is `~/.ait-unity-sdk/nodejs/v{NODE_VERSION}/{platform}/`.

Download mirrors fall back in order.

1. `https://nodejs.org/dist/` (official)
2. `https://cdn.npmmirror.com/binaries/node/`
3. `https://repo.huaweicloud.com/nodejs/`

```
1. Check installation path → skip if it already exists
2. Try mirror 1:
   ├── download .tar.gz (macOS/Linux) or .zip (Windows)
   ├── verify SHA256 checksum ← if it fails, delete the downloaded file + next mirror
   └── extract → temporary folder
3. Mirror 2/3 fallback (same process)
4. Atomically move from temporary folder to final path
5. Install pnpm: corepack enable + corepack prepare
```

Platform-specific SHA256 hashes are `AITNodeJSDownloader.cs`hardcoded in `AITPackageManagerHelper`is responsible.

### Error code

`AITConvertCore.AITExportError` is an enum. The value `7`was `WEBGL_BUILD_INCOMPLETE`before, but `10`\~`13`was removed when it was subdivided more finely.

| Code                              | Value | Short label                 |
| --------------------------------- | ----- | --------------------------- |
| `SUCCEED`                         | 0     | Success                     |
| `NODE_NOT_FOUND`                  | 1     | Node.js not found           |
| `BUILD_WEBGL_FAILED`              | 2     | WebGL build error           |
| `INVALID_APP_CONFIG`              | 3     | App settings error          |
| `NETWORK_ERROR`                   | 4     | Network error               |
| `CANCELLED`                       | 5     | User cancelled              |
| `FAIL_NPM_BUILD`                  | 6     | pnpm build error            |
| `BUILD_FOLDER_MISSING`            | 10    | Build folder missing        |
| `REQUIRED_FILE_MISSING`           | 11    | Required file missing       |
| `INDEX_HTML_MISSING`              | 12    | index.html missing          |
| `PLACEHOLDER_SUBSTITUTION_FAILED` | 13    | Placeholder not substituted |
| `DIST_FOLDER_MISSING`             | 14    | dist folder missing         |
| `AIT_FILE_MISSING`                | 15    | .ait file missing           |

Both the full messages shown to the user and the short labels are owned by `AITExportErrorCatalog`.

```
Build error occurred
  ↓
ShowComplexDialog("빌드 실패", errorMessage, ...)
  ├── "OK" → exit
  └── "Report Issue" → AITErrorReporter.OpenIssueInBrowser()
                     → automatically opens a prefilled issue URL in GitHub Issues
```

### User warnings and dialog conditions

#### Error dialog

| Condition                | Title           | Contents                                       |
| ------------------------ | --------------- | ---------------------------------------------- |
| No SDK loading template  | "Error"         | Unable to find the SDK loading screen template |
| Build folder missing     | "Error"         | Unable to find the WebGL build folder          |
| App name not set         | "Error"         | The app name has not been set                  |
| Deployment key not set   | "Error"         | The deployment key has not been set            |
| pnpm installation failed | "Build failed"  | Failed to install pnpm                         |
| Build canceled           | "Canceled"      | The build was canceled                         |
| Build succeeded          | "Success"       | Build and packaging completed                  |
| Clean completed          | "Completed"     | Clean completed                                |
| Deployment timeout       | "Timeout"       | Deployment timed out                           |
| Port conflict            | "Port conflict" | That port is already in use                    |

#### Confirmation dialog

| Condition               | Behavior                                                                   |
| ----------------------- | -------------------------------------------------------------------------- |
| `AIT/Clean`             | "Delete the webgl/ and ait-build/ folders?"                                |
| Deployment confirmation | "App name: X, version: Y — Deploy?" (shows the memo auto-generated values) |
| Reset settings          | "Reset settings?"                                                          |
| Reset loading screen    | "Reset the loading screen to the default template?"                        |

#### 3-way dialog

| Condition         | Option                |
| ----------------- | --------------------- |
| Build failed      | "OK" / "Report Issue" |
| Deployment failed | "OK" / "Report Issue" |

#### Console warning

| Condition                                        | Message summary                                    |
| ------------------------------------------------ | -------------------------------------------------- |
| Non-fatal placeholder not substituted            | Display the placeholder name                       |
| SDK-managed settings remain in USER\_CONFIG      | Removal recommended (build is normal)              |
| `pnpm install --frozen-lockfile` failed          | Proceed to the next retry step                     |
| web-framework version mismatch                   | Expected vs actual version display                 |
| `node_modules/.pnpm` None                        | stale modules                                      |
| Upgrade previous version template                | Guide for manually reapplying custom modifications |
| No loading screen file                           | An empty loading screen is used                    |
| Failed to write build marker                     | Warnings only (build continues)                    |
| No build marker / Unity version mismatch         | Automatic clean build                              |
| `AIT_DISABLE_INSTALL_SKIP` Unable to parse value | Treated as skip disabled                           |

#### Console errors

| Condition                           | Results                           |
| ----------------------------------- | --------------------------------- |
| `webgl/Build/` Folder not found     | `BUILD_FOLDER_MISSING`            |
| Required WebGL files missing        | `REQUIRED_FILE_MISSING`           |
| `index.html` None                   | `INDEX_HTML_MISSING`              |
| Critical placeholder not replaced   | `PLACEHOLDER_SUBSTITUTION_FAILED` |
| Empty path pattern detected         | `PLACEHOLDER_SUBSTITUTION_FAILED` |
| No dist after granite build         | `DIST_FOLDER_MISSING`             |
| in dist `.ait` None                 | `AIT_FILE_MISSING`                |
| Final pnpm install failure          | Build aborted                     |
| SDK BuildConfig folder not found    | Build aborted                     |
| SDK WebGLTemplates folder not found | Build aborted                     |

### Server lifecycle

The local server only has one Dev Server (the previously existing Production Server was removed starting from 3.0.0 because it became impossible to integrate with sandbox apps — to verify production settings on a real device [use Deploy (Test) in Getting Started](https://developers-apps-in-toss.toss.im/documentation/unity/first-steps/getting-started)use).

| Method                      | Description                                                               |
| --------------------------- | ------------------------------------------------------------------------- |
| `StartServer()`             | Build + start server                                                      |
| `StopServer()`              | Terminate the server process                                              |
| `RestartServer(serverOnly)` | `serverOnly=false`then build + server, `true`then restart only the server |

```
AIT/Dev Server/
├── Start Server              → StartServer() → DoExport(dev) + granite dev
├── Stop Server               → StopServer()
├── Restart Server            → RestartServer(serverOnly: false)
└── Restart Server (server-only) → RestartServer(serverOnly: true)
```

If the target port is already in use, show a "port conflict" dialog, and the user must change the port or terminate the occupying process.

### Error reporting

`AITErrorReporter`is `[InitializeOnLoad]`at editor startup `Application.logMessageReceived`subscribe to and capture all console logs in a ring buffer.

| Buffer        | Maximum size | Capture target                       |
| ------------- | ------------ | ------------------------------------ |
| `errorLogs`   | 50 entries   | `LogType.Error`, `LogType.Exception` |
| `warningLogs` | 30 entries   | `LogType.Warning`                    |
| `infoLogs`    | 20 entries   | `LogType.Log`, `LogType.Assert`      |

`OpenIssueInBrowser(errorCode, profileName)`automatically constructs a GitHub Issue URL with this buffer. The title is `[Build Error] {errorCode}`and the body contains the SDK/Unity/OS versions, profile name, error code and message, app settings, `BuildReport` error (if any), and recent console logs. If the URL exceeds 2000 characters, `infoLogs` → `warningLogs` → `errorLogs` it is progressively trimmed in order.

The scope of logs sent to Sentry and the noise suppression policy are [Sentry integration](https://developers-apps-in-toss.toss.im/documentation/unity/add-features/sentry-integration)in.

### Related documents

* [Build Profiles](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-profiles) — profile-specific setting differences, environment variable overrides
* [Build customization](https://developers-apps-in-toss.toss.im/documentation/unity/build/build-customization) — marker area contracts, web entry point edits
* [Loading screen customization](https://developers-apps-in-toss.toss.im/documentation/unity/build/loading-screen-customization) — loading screen replacement and `AITLoading` API
* [Sentry integration](https://developers-apps-in-toss.toss.im/documentation/unity/add-features/sentry-integration) — error collection and context injection
* [Troubleshooting](https://developers-apps-in-toss.toss.im/documentation/unity/first-steps/faq) — when the build is stuck


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://developers-apps-in-toss.toss.im/documentation/api-and-sdk-en/unity/build/build-process.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
