iets-dev 1.0.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.
Files changed (66) hide show
  1. package/.claude/settings.local.json +59 -0
  2. package/.github/workflows/build.yml +33 -0
  3. package/.github/workflows/npm-publish.yml +24 -0
  4. package/CLAUDE.md +68 -0
  5. package/README.md +21 -0
  6. package/eslint.config.js +34 -0
  7. package/package.json +28 -0
  8. package/pnpm-workspace.yaml +5 -0
  9. package/scripts/barrel-generator.test.ts +155 -0
  10. package/scripts/barrel-generator.ts +207 -0
  11. package/scripts/ensure-iesdp.sh +20 -0
  12. package/scripts/ts-update.sh +15 -0
  13. package/scripts/ts-update.test.ts +136 -0
  14. package/scripts/ts-update.ts +457 -0
  15. package/scripts/utils.ts +32 -0
  16. package/src/CHANGELOG.md +45 -0
  17. package/src/README.md +23 -0
  18. package/src/ambient.d.ts +23 -0
  19. package/src/bg1/index.d.ts +6 -0
  20. package/src/bg2/actions.d.ts +3512 -0
  21. package/src/bg2/align.ids.d.ts +4 -0
  22. package/src/bg2/animate.ids.d.ts +326 -0
  23. package/src/bg2/areaflag.ids.d.ts +4 -0
  24. package/src/bg2/areatype.ids.d.ts +4 -0
  25. package/src/bg2/astyles.ids.d.ts +11 -0
  26. package/src/bg2/class.ids.d.ts +135 -0
  27. package/src/bg2/damages.ids.d.ts +4 -0
  28. package/src/bg2/difflev.ids.d.ts +4 -0
  29. package/src/bg2/dir.ids.ts +23 -0
  30. package/src/bg2/dmgtype.ids.d.ts +4 -0
  31. package/src/bg2/ea.ids.d.ts +5 -0
  32. package/src/bg2/gender.ids.d.ts +4 -0
  33. package/src/bg2/general.ids.d.ts +4 -0
  34. package/src/bg2/gtimes.ids.d.ts +4 -0
  35. package/src/bg2/happy.ids.d.ts +4 -0
  36. package/src/bg2/help.d.ts +42 -0
  37. package/src/bg2/hotkey.ids.d.ts +4 -0
  38. package/src/bg2/index.ts +1809 -0
  39. package/src/bg2/jourtype.ids.d.ts +4 -0
  40. package/src/bg2/kit.ids.d.ts +4 -0
  41. package/src/bg2/mflags.ids.d.ts +4 -0
  42. package/src/bg2/modal.ids.d.ts +14 -0
  43. package/src/bg2/npc.ids.d.ts +4 -0
  44. package/src/bg2/object.d.ts +366 -0
  45. package/src/bg2/object.ts +69 -0
  46. package/src/bg2/race.ids.d.ts +85 -0
  47. package/src/bg2/reaction.ids.d.ts +4 -0
  48. package/src/bg2/scrlev.ids.d.ts +4 -0
  49. package/src/bg2/scroll.ids.d.ts +4 -0
  50. package/src/bg2/seq.ids.d.ts +4 -0
  51. package/src/bg2/shoutids.ids.d.ts +15 -0
  52. package/src/bg2/slots.ids.d.ts +88 -0
  53. package/src/bg2/sndslot.ids.d.ts +4 -0
  54. package/src/bg2/soundoff.ids.d.ts +4 -0
  55. package/src/bg2/specific.ids.d.ts +4 -0
  56. package/src/bg2/spell.ids.d.ts +2008 -0
  57. package/src/bg2/state.ids.d.ts +124 -0
  58. package/src/bg2/stats.ids.d.ts +4 -0
  59. package/src/bg2/time.ids.d.ts +4 -0
  60. package/src/bg2/timeoday.ids.d.ts +4 -0
  61. package/src/bg2/triggers.d.ts +1082 -0
  62. package/src/bg2/weather.ids.d.ts +4 -0
  63. package/src/index.ts +107 -0
  64. package/src/package.json +21 -0
  65. package/src/tsconfig.json +11 -0
  66. package/tsconfig.json +19 -0
@@ -0,0 +1,59 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(pnpm install)",
5
+ "Bash(pnpm iesdp-update:*)",
6
+ "Bash(pnpm typecheck:*)",
7
+ "Bash(pnpm lint:*)",
8
+ "Bash(pnpm test:*)",
9
+ "Bash(grep:*)",
10
+ "Bash(pnpm exec tsc:*)",
11
+ "Bash(pnpm exec tsx:*)",
12
+ "WebFetch(domain:iesdp.bgforge.net)",
13
+ "Bash(./scripts/iesdp-update.sh:*)",
14
+ "Bash(pnpm ts-update:*)",
15
+ "Bash(pnpm exec vitest:*)",
16
+ "Bash(find:*)",
17
+ "Bash(ls:*)",
18
+ "WebFetch(domain:ielib.bgforge.net)",
19
+ "Bash(while read file)",
20
+ "Bash(do grep -q \"name: Area\" \"$file\")",
21
+ "Bash(echo:*)",
22
+ "Bash(done)",
23
+ "Bash(git diff:*)",
24
+ "Bash(xargs sed:*)",
25
+ "Bash(git -C /home/magi/data/work/sexydev/bg2/ielib tag --sort=-v:refname)",
26
+ "Bash(git -C /home/magi/data/work/sexydev/bg2/ielib log --oneline --all --grep=\"0.2.0\" -- ts/)",
27
+ "Bash(git -C /home/magi/data/work/sexydev/bg2/ielib log --oneline --all -S '\"\"0.2.0\"\"' -- ts/ielib/package.json)",
28
+ "Bash(fi)",
29
+ "Bash(head:*)",
30
+ "Bash(git -C /home/magi/data/work/sexydev/bg2/iets log --oneline)",
31
+ "Bash(git -C /home/magi/data/work/sexydev/bg2/iets log --oneline --all)",
32
+ "Bash(git -C /home/magi/data/work/sexydev/bg2/iets log --oneline -- ts/)",
33
+ "Bash(git -C /home/magi/data/work/sexydev/bg2/iets log --oneline -- scripts/)",
34
+ "Bash(git -C /home/magi/data/work/sexydev/bg2/iets log --oneline -- package.json tsconfig.json eslint.config.js pnpm-workspace.yaml)",
35
+ "Bash(git -C /home/magi/data/work/sexydev/bg2/iets log --oneline -200)",
36
+ "Bash(git -C /home/magi/data/work/sexydev/bg2/iets log --oneline --name-status -- ts/ scripts/)",
37
+ "Bash(git -C /home/magi/data/work/sexydev/bg2/iets log --all --oneline --name-only --reverse)",
38
+ "Bash(git filter-repo:*)",
39
+ "Bash(git mv:*)",
40
+ "Bash(git -C /home/magi/data/work/sexydev/bg2/iets status)",
41
+ "Bash(pnpm --dir /home/magi/data/work/sexydev/bg2/iets test:*)",
42
+ "WebSearch",
43
+ "Bash(pnpm docs:*)",
44
+ "Bash(pnpm exec typedoc:*)",
45
+ "Bash(pnpm build-docs:*)",
46
+ "WebFetch(domain:starlight.astro.build)",
47
+ "WebFetch(domain:www.npmjs.com)",
48
+ "WebFetch(domain:starlight-typedoc.vercel.app)",
49
+ "WebFetch(domain:github.com)",
50
+ "WebFetch(domain:starlight-typedoc-example.vercel.app)",
51
+ "WebFetch(domain:typedoc.org)",
52
+ "Bash(timeout 15 pnpm serve-docs:*)",
53
+ "Bash(curl:*)",
54
+ "WebFetch(domain:folib.bgforge.net)",
55
+ "WebFetch(domain:raw.githubusercontent.com)",
56
+ "Bash(CI=true pnpm install:*)"
57
+ ]
58
+ }
59
+ }
@@ -0,0 +1,33 @@
1
+ name: Build
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ check:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - name: Checkout
10
+ uses: actions/checkout@v4
11
+
12
+ - name: Install pnpm
13
+ uses: pnpm/action-setup@v4
14
+ with:
15
+ version: 9
16
+
17
+ - name: Install Node.js
18
+ uses: actions/setup-node@v4
19
+ with:
20
+ node-version: "22"
21
+ cache: "pnpm"
22
+
23
+ - name: Install Node dependencies
24
+ run: pnpm install
25
+
26
+ - name: Lint
27
+ run: pnpm lint
28
+
29
+ - name: Type check
30
+ run: pnpm typecheck
31
+
32
+ - name: Test
33
+ run: pnpm test
@@ -0,0 +1,24 @@
1
+ name: NPM publish
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ paths:
8
+ - src/**
9
+ - .github/workflows/**
10
+
11
+ jobs:
12
+ publish:
13
+ runs-on: ubuntu-latest
14
+ permissions:
15
+ contents: read
16
+ id-token: write
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: actions/setup-node@v4
20
+ with:
21
+ node-version: "24"
22
+ registry-url: "https://registry.npmjs.org"
23
+ - run: npm publish --provenance --access public
24
+ working-directory: src
package/CLAUDE.md ADDED
@@ -0,0 +1,68 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project
6
+
7
+ BGforge IETS -- a TypeScript type package for Infinity Engine scripting (Baldur's Gate 2). Published to npm as `@bgforge/iets`. Provides branded types, object specifiers, IDS constant files, and typed action/trigger declarations for IDE integration.
8
+
9
+ ## Commands
10
+
11
+ ```bash
12
+ pnpm install # Install dependencies (never use npm/npx)
13
+ pnpm lint # ESLint (strict type-checked, flat config) on scripts/
14
+ pnpm typecheck # TypeScript strict check on scripts/
15
+ pnpm test # Vitest unit tests
16
+ pnpm ts-update # Regenerate TypeScript declarations from IESDP
17
+ ```
18
+
19
+ Run a single test file:
20
+ ```bash
21
+ pnpm exec vitest run scripts/ts-update.test.ts
22
+ ```
23
+
24
+ Run tests matching a name pattern:
25
+ ```bash
26
+ pnpm exec vitest run -t "pattern"
27
+ ```
28
+
29
+ ## Architecture
30
+
31
+ ### TypeScript package (`src/`)
32
+
33
+ npm package (pnpm workspace) published via `npm-publish.yml` workflow. Provides branded types (`IE<T, B>`), object specifiers, and `.ids.ts` constant files for IDE integration.
34
+
35
+ #### Type branding conventions
36
+
37
+ - Numeric IDS types use `IE<number, "TypeName">` for nominal type safety. Users create custom values with `42 as SpellID`.
38
+ - String resref types (`ResRef`, `SplRef`, `ItmRef`) intentionally use `string & {}` instead of `IE<string, ...>` -- resrefs are almost always raw string literals and branding would require a cast on every usage. Unlike numeric IDS types, there is no finite set of valid resrefs to provide as pre-typed constants.
39
+ - `Action` uses a branded interface so that `ActionOverride` can enforce its argument is an actual action call.
40
+
41
+ #### IDS type naming
42
+
43
+ Some IDS types use a `*ID` suffix (`ClassID`, `GenderID`, `KitID`, etc.) while others use bare names (`Align`, `EA`, `State`, etc.). This is not inconsistency -- the suffix exists to avoid name clashes with trigger/action functions (e.g. `Class()` trigger vs `ClassID` type, `Gender()` trigger vs `GenderID` type). Types without a same-named function use the bare name.
44
+
45
+ ### Code generation pipeline
46
+
47
+ `external/iesdp/` contains IESDP data (dynamically cloned from `BGforgeNet/iesdp`, branch `ielib`, via `scripts/ensure-iesdp.sh` -- not a git submodule).
48
+
49
+ - `scripts/ts-update.ts` reads IESDP YAML/HTML -> generates `src/bg2/actions.d.ts` and `src/bg2/triggers.d.ts`
50
+ - `scripts/barrel-generator.ts` generates `src/bg2/index.ts` barrel file from sibling modules
51
+
52
+ ### Scripts
53
+
54
+ - `scripts/ts-update.ts` -- TypeScript declaration generator
55
+ - `scripts/ts-update.test.ts` -- Tests for ts-update
56
+ - `scripts/ts-update.sh` -- Shell wrapper for ts-update
57
+ - `scripts/barrel-generator.ts` -- Barrel file generator
58
+ - `scripts/barrel-generator.test.ts` -- Tests for barrel generator
59
+ - `scripts/utils.ts` -- Shared utilities (readFile, log, etc.)
60
+ - `scripts/ensure-iesdp.sh` -- IESDP data fetcher
61
+
62
+ ## TypeScript strict settings
63
+
64
+ `tsconfig.json` enables `noUncheckedIndexedAccess` and `noImplicitReturns` -- all array/object index access may be `undefined`, and all code paths must return.
65
+
66
+ ## CI/CD
67
+
68
+ GitHub Actions (`build.yml`): lint + typecheck + test on every push. npm publish (`npm-publish.yml`) triggers on `src/**` changes (Node 24).
package/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # BGforge IETS
2
+ [![Telegram](https://img.shields.io/badge/telegram-join%20%20%20%20%E2%9D%B1%E2%9D%B1%E2%9D%B1-darkorange?logo=telegram)](https://t.me/bgforge)
3
+ [![Discord](https://img.shields.io/discord/420268540700917760?logo=discord&label=discord&color=blue&logoColor=FEE75C)](https://discord.gg/4Yqfggm)
4
+ [![IRC](https://img.shields.io/badge/%23IRC-join%20%20%20%20%E2%9D%B1%E2%9D%B1%E2%9D%B1-darkorange)](https://bgforge.net/irc)
5
+ [![Patreon](https://img.shields.io/badge/Patreon-donate-FF424D?logo=Patreon&labelColor=141518)](https://www.patreon.com/BGforge)
6
+
7
+ TypeScript type definitions for Infinity Engine scripting. Can be used in [TBAF](https://forums.bgforge.net/viewtopic.php?t=448).
8
+
9
+ Provides branded types, object specifiers, IDS constants, and typed action/trigger function declarations for IDE autocompletion and type checking.
10
+
11
+ ### Installation
12
+
13
+ ```bash
14
+ pnpm install @bgforge/iets
15
+ ```
16
+
17
+ ### Usage
18
+
19
+ ```typescript
20
+ import { ActionOverride, MoveToPoint } from "@bgforge/iets/bg2";
21
+ ```
@@ -0,0 +1,34 @@
1
+ /**
2
+ * ESLint configuration for scripts directory.
3
+ *
4
+ * Uses flat config format (ESLint 9+).
5
+ */
6
+
7
+ import eslint from "@eslint/js";
8
+ import tseslint from "typescript-eslint";
9
+
10
+ export default tseslint.config(
11
+ eslint.configs.recommended,
12
+ ...tseslint.configs.strictTypeChecked,
13
+ {
14
+ languageOptions: {
15
+ parserOptions: {
16
+ projectService: true,
17
+ tsconfigRootDir: import.meta.dirname,
18
+ },
19
+ },
20
+ rules: {
21
+ // Allow numbers and booleans in template literals
22
+ "@typescript-eslint/restrict-template-expressions": [
23
+ "error",
24
+ { allowNumber: true, allowBoolean: true },
25
+ ],
26
+ },
27
+ },
28
+ {
29
+ files: ["scripts/**/*.ts"],
30
+ },
31
+ {
32
+ ignores: ["node_modules", "external", "src", "**/*.js"],
33
+ }
34
+ );
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "iets-dev",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "dependencies": {
6
+ "js-yaml": "^4.1.1",
7
+ "jsdom": "^25.0.1",
8
+ "tsx": "^4.21.0",
9
+ "yargs": "^17.7.2"
10
+ },
11
+ "devDependencies": {
12
+ "@eslint/js": "^9.39.2",
13
+ "@types/js-yaml": "^4.0.9",
14
+ "@types/jsdom": "^21.1.7",
15
+ "@types/node": "^22.19.3",
16
+ "@types/yargs": "^17.0.35",
17
+ "eslint": "^9.39.2",
18
+ "typescript": "^5.7.3",
19
+ "typescript-eslint": "^8.54.0",
20
+ "vitest": "^4.0.18"
21
+ },
22
+ "scripts": {
23
+ "lint": "eslint scripts/",
24
+ "typecheck": "tsc --noEmit && tsc --noEmit -p src/tsconfig.json",
25
+ "ts-update": "tsx scripts/ts-update.ts external/iesdp/_data/actions src/bg2/actions.d.ts external/iesdp/scripting/triggers/bg2triggers.htm src/bg2/triggers.d.ts",
26
+ "test": "vitest run"
27
+ }
28
+ }
@@ -0,0 +1,5 @@
1
+ packages:
2
+ - "."
3
+
4
+ onlyBuiltDependencies:
5
+ - esbuild
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Tests for the barrel file generator utilities.
3
+ */
4
+
5
+ import { describe, it, expect } from "vitest";
6
+ import { extractExportedNames, toModulePath, moduleSection, Section, formatExportLine } from "./barrel-generator";
7
+
8
+ describe("extractExportedNames", () => {
9
+ it("extracts type-only exports", () => {
10
+ const content = `import type { IE } from "../index";\n\nexport declare type Align = IE<number, "Align">;`;
11
+ const result = extractExportedNames(content);
12
+ expect(result.types).toEqual(["Align"]);
13
+ expect(result.values).toEqual([]);
14
+ });
15
+
16
+ it("extracts value-only exports (declare const)", () => {
17
+ const content = `export declare const ENEMY: EA;\nexport declare const ALLY: EA;`;
18
+ const result = extractExportedNames(content);
19
+ expect(result.types).toEqual([]);
20
+ expect(result.values).toEqual(["ENEMY", "ALLY"]);
21
+ });
22
+
23
+ it("extracts mixed type and value exports", () => {
24
+ const content = [
25
+ `export declare type Modal = IE<number, "Modal">;`,
26
+ `export declare const NONE: Modal;`,
27
+ `export declare const BATTLESONG: Modal;`,
28
+ ].join("\n");
29
+ const result = extractExportedNames(content);
30
+ expect(result.types).toEqual(["Modal"]);
31
+ expect(result.values).toEqual(["NONE", "BATTLESONG"]);
32
+ });
33
+
34
+ it("extracts declare function exports", () => {
35
+ const content = [
36
+ `export declare function LeaderOf(target?: ObjectPtr): ObjectPtr;`,
37
+ `export declare const Myself: ObjectPtr;`,
38
+ ].join("\n");
39
+ const result = extractExportedNames(content);
40
+ expect(result.types).toEqual([]);
41
+ expect(result.values).toEqual(["LeaderOf", "Myself"]);
42
+ });
43
+
44
+ it("deduplicates overloaded function declarations", () => {
45
+ const content = [
46
+ `export declare function Help(): Action;`,
47
+ `export declare function Help(who: ObjectPtr): boolean;`,
48
+ ].join("\n");
49
+ const result = extractExportedNames(content);
50
+ expect(result.values).toEqual(["Help"]);
51
+ });
52
+
53
+ it("extracts enum exports", () => {
54
+ const content = `export enum Direction {\n S = 0,\n N = 8,\n}`;
55
+ const result = extractExportedNames(content);
56
+ expect(result.types).toEqual([]);
57
+ expect(result.values).toEqual(["Direction"]);
58
+ });
59
+
60
+ it("extracts declare enum exports", () => {
61
+ const content = `export declare enum Animate {\n FIRE_RING = 0x0000,\n CHUNKS = 0x0100,\n}`;
62
+ const result = extractExportedNames(content);
63
+ expect(result.types).toEqual([]);
64
+ expect(result.values).toEqual(["Animate"]);
65
+ });
66
+
67
+ it("extracts const exports (non-declare)", () => {
68
+ const content = `export const NearestEnemyOf = DefaultSelf;`;
69
+ const result = extractExportedNames(content);
70
+ expect(result.types).toEqual([]);
71
+ expect(result.values).toEqual(["NearestEnemyOf"]);
72
+ });
73
+
74
+ it("returns empty arrays for content with no exports", () => {
75
+ const content = `import type { IE } from "../index";\nconst x = 1;`;
76
+ const result = extractExportedNames(content);
77
+ expect(result.types).toEqual([]);
78
+ expect(result.values).toEqual([]);
79
+ });
80
+
81
+ it("ignores import statements", () => {
82
+ const content = [
83
+ `import type { IE } from "../index";`,
84
+ `import { ObjectPtr } from "..";`,
85
+ `export declare type Foo = IE<number, "Foo">;`,
86
+ ].join("\n");
87
+ const result = extractExportedNames(content);
88
+ expect(result.types).toEqual(["Foo"]);
89
+ expect(result.values).toEqual([]);
90
+ });
91
+ });
92
+
93
+ describe("toModulePath", () => {
94
+ it("strips .ts extension", () => {
95
+ expect(toModulePath("foo.ts")).toBe("./foo");
96
+ });
97
+
98
+ it("strips .ts but keeps .d infix", () => {
99
+ expect(toModulePath("foo.d.ts")).toBe("./foo.d");
100
+ });
101
+
102
+ it("strips .ts but keeps .ids.d infix", () => {
103
+ expect(toModulePath("align.ids.d.ts")).toBe("./align.ids.d");
104
+ });
105
+
106
+ it("handles plain .ids.ts files", () => {
107
+ expect(toModulePath("dir.ids.ts")).toBe("./dir.ids");
108
+ });
109
+ });
110
+
111
+ describe("moduleSection", () => {
112
+ it("classifies actions", () => {
113
+ expect(moduleSection("./actions.d")).toBe(Section.Actions);
114
+ });
115
+
116
+ it("classifies triggers", () => {
117
+ expect(moduleSection("./triggers.d")).toBe(Section.Triggers);
118
+ });
119
+
120
+ it("classifies help", () => {
121
+ expect(moduleSection("./help.d")).toBe(Section.Help);
122
+ });
123
+
124
+ it("classifies IDS modules", () => {
125
+ expect(moduleSection("./align.ids.d")).toBe(Section.IDS);
126
+ expect(moduleSection("./object.d")).toBe(Section.IDS);
127
+ expect(moduleSection("./dir.ids")).toBe(Section.IDS);
128
+ });
129
+ });
130
+
131
+ describe("formatExportLine", () => {
132
+ it("formats single name inline", () => {
133
+ expect(formatExportLine("export", ["A"], "./mod"))
134
+ .toBe("export { A } from './mod';");
135
+ });
136
+
137
+ it("formats multiple names one per line", () => {
138
+ const result = formatExportLine("export", ["A", "B", "C"], "./mod");
139
+ expect(result).toBe(
140
+ "export {\n A,\n B,\n C,\n} from './mod';",
141
+ );
142
+ });
143
+
144
+ it("formats export type keyword", () => {
145
+ expect(formatExportLine("export type", ["Foo"], "./mod"))
146
+ .toBe("export type { Foo } from './mod';");
147
+ });
148
+
149
+ it("formats export type with multiple names one per line", () => {
150
+ const result = formatExportLine("export type", ["Bar", "Foo"], "./mod");
151
+ expect(result).toBe(
152
+ "export type {\n Bar,\n Foo,\n} from './mod';",
153
+ );
154
+ });
155
+ });
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Barrel file generator for bg2/index.ts.
3
+ *
4
+ * Scans all TypeScript source files in a directory, extracts their exported
5
+ * names, and generates a barrel re-export file grouped by section.
6
+ */
7
+
8
+ import * as fs from "fs";
9
+ import * as path from "path";
10
+ import { log } from "./utils.js";
11
+
12
+ // Types
13
+
14
+ export interface ModuleExports {
15
+ types: string[];
16
+ values: string[];
17
+ }
18
+
19
+ /**
20
+ * Barrel section indices. Modules are grouped into sections, then sorted
21
+ * alphabetically within each section.
22
+ */
23
+ export const enum Section {
24
+ Actions = 0,
25
+ IDS = 1,
26
+ Triggers = 2,
27
+ Help = 3,
28
+ }
29
+
30
+ // Constants
31
+
32
+ const SECTION_COMMENTS: Readonly<Record<Section, string>> = {
33
+ [Section.Actions]: "// --- Actions (generated from IESDP) ---",
34
+ [Section.IDS]: "// --- IDS types and constants ---",
35
+ [Section.Triggers]: "// --- Triggers (generated from IESDP) ---",
36
+ [Section.Help]: "// Help() is both an action and a trigger -- lives in a separate file.",
37
+ };
38
+
39
+ const OBJECT_MODULES = new Set(["./object.d", "./object"]);
40
+
41
+ const BARREL_HEADER = [
42
+ "/**",
43
+ " * BG2 barrel re-exports.",
44
+ " *",
45
+ " * Generated by ts-update. Do not edit manually.",
46
+ " *",
47
+ " * Uses named re-exports (not export *) so esbuild can statically resolve each",
48
+ " * binding without falling back to runtime __reExport helpers for externalized",
49
+ " * .d.ts modules.",
50
+ " */",
51
+ "",
52
+ ];
53
+
54
+ // Functions
55
+
56
+ /**
57
+ * Extracts exported type and value names from a TypeScript source file.
58
+ * Deduplicates function overloads (same name exported multiple times).
59
+ */
60
+ export function extractExportedNames(fileContent: string): ModuleExports {
61
+ const types = new Set<string>();
62
+ const values = new Set<string>();
63
+
64
+ for (const line of fileContent.split("\n")) {
65
+ let match;
66
+ if ((match = line.match(/^export declare type (\w+)/))) {
67
+ const name = match[1];
68
+ if (name) types.add(name);
69
+ } else if ((match = line.match(/^export declare (?:const|function|enum) (\w+)/))) {
70
+ const name = match[1];
71
+ if (name) values.add(name);
72
+ } else if ((match = line.match(/^export (?:enum|const) (\w+)/))) {
73
+ const name = match[1];
74
+ if (name) values.add(name);
75
+ }
76
+ }
77
+
78
+ return { types: [...types], values: [...values] };
79
+ }
80
+
81
+ /**
82
+ * Computes the import path for a source file relative to the barrel.
83
+ * Strips the `.ts` suffix; keeps `.d` and `.ids.d` infixes intact.
84
+ * - `foo.ids.d.ts` -> `./foo.ids.d`
85
+ * - `foo.d.ts` -> `./foo.d`
86
+ * - `foo.ts` -> `./foo`
87
+ */
88
+ export function toModulePath(filename: string): string {
89
+ return `./${filename.replace(/\.ts$/, "")}`;
90
+ }
91
+
92
+ /**
93
+ * Returns the barrel section for a given module path.
94
+ */
95
+ export function moduleSection(modulePath: string): Section {
96
+ if (modulePath === "./actions.d") return Section.Actions;
97
+ if (modulePath === "./triggers.d") return Section.Triggers;
98
+ if (modulePath === "./help.d") return Section.Help;
99
+ return Section.IDS;
100
+ }
101
+
102
+ /**
103
+ * Formats an export statement with one symbol per line.
104
+ */
105
+ export function formatExportLine(keyword: "export" | "export type", names: readonly string[], modulePath: string): string {
106
+ if (names.length === 1) {
107
+ return `${keyword} { ${names[0]} } from '${modulePath}';`;
108
+ }
109
+
110
+ const lines = [
111
+ `${keyword} {`,
112
+ ...names.map((name) => ` ${name},`),
113
+ `} from '${modulePath}';`,
114
+ ];
115
+ return lines.join("\n");
116
+ }
117
+
118
+ /**
119
+ * Builds sorted module map from source files in a directory.
120
+ * Reads each .ts file (excluding index.ts), extracts exports, sorts names.
121
+ */
122
+ function buildModuleMap(bg2Dir: string): Map<string, ModuleExports> {
123
+ const allFiles = fs.readdirSync(bg2Dir)
124
+ .filter((f) => f !== "index.ts" && f.endsWith(".ts"));
125
+
126
+ const moduleMap = new Map<string, ModuleExports>();
127
+ for (const file of allFiles) {
128
+ const content = fs.readFileSync(path.join(bg2Dir, file), "utf-8");
129
+ const exports = extractExportedNames(content);
130
+ if (exports.types.length === 0 && exports.values.length === 0) {
131
+ continue;
132
+ }
133
+ const sorted: ModuleExports = {
134
+ types: [...exports.types].sort(),
135
+ values: [...exports.values].sort(),
136
+ };
137
+ moduleMap.set(toModulePath(file), sorted);
138
+ }
139
+
140
+ return moduleMap;
141
+ }
142
+
143
+ /**
144
+ * Assembles barrel file output lines from a sorted module map.
145
+ * Groups modules by section with decorative comments.
146
+ */
147
+ function buildBarrelOutput(moduleMap: Map<string, ModuleExports>): string[] {
148
+ const sortedModules = [...moduleMap.keys()].sort((a, b) => {
149
+ const sd = moduleSection(a) - moduleSection(b);
150
+ return sd !== 0 ? sd : a.localeCompare(b);
151
+ });
152
+
153
+ const out: string[] = [...BARREL_HEADER];
154
+ let currentSection: Section | -1 = -1;
155
+ let inObjectGroup = false;
156
+
157
+ for (const modulePath of sortedModules) {
158
+ const section = moduleSection(modulePath);
159
+ const exports = moduleMap.get(modulePath);
160
+ if (!exports) continue;
161
+ const isObject = OBJECT_MODULES.has(modulePath);
162
+
163
+ // Section transition
164
+ if (section !== currentSection) {
165
+ if (currentSection !== -1) out.push("");
166
+ out.push(SECTION_COMMENTS[section]);
167
+ currentSection = section;
168
+ inObjectGroup = false;
169
+ }
170
+
171
+ // Sub-section comments within IDS
172
+ if (section === Section.IDS) {
173
+ if (isObject && !inObjectGroup) {
174
+ out.push("");
175
+ out.push("// --- Object identifiers ---");
176
+ inObjectGroup = true;
177
+ } else if (!isObject && inObjectGroup) {
178
+ out.push("");
179
+ out.push("// --- More IDS types and constants ---");
180
+ inObjectGroup = false;
181
+ }
182
+ }
183
+
184
+ if (exports.types.length > 0) {
185
+ out.push(formatExportLine("export type", exports.types, modulePath));
186
+ }
187
+ if (exports.values.length > 0) {
188
+ out.push(formatExportLine("export", exports.values, modulePath));
189
+ }
190
+ }
191
+
192
+ out.push("");
193
+ return out;
194
+ }
195
+
196
+ /**
197
+ * Generates the bg2/index.ts barrel file by scanning all sibling modules
198
+ * and extracting their exported names.
199
+ */
200
+ export function generateBarrelFile(bg2Dir: string): void {
201
+ const moduleMap = buildModuleMap(bg2Dir);
202
+ const output = buildBarrelOutput(moduleMap);
203
+
204
+ const outputPath = path.join(bg2Dir, "index.ts");
205
+ fs.writeFileSync(outputPath, output.join("\n"), "utf-8");
206
+ log(`Barrel file written to ${outputPath} (${moduleMap.size} modules)`);
207
+ }
@@ -0,0 +1,20 @@
1
+ #!/bin/bash
2
+
3
+ # Shared script to ensure IESDP repository is cloned and on the correct branch.
4
+ # Source this from other scripts: source scripts/ensure-iesdp.sh
5
+
6
+ set -eu -o pipefail
7
+
8
+ ensure_iesdp() {
9
+ local ext_dir="external"
10
+ local iesdp_dir="$ext_dir/iesdp"
11
+ local repo="BGforgeNet/iesdp"
12
+
13
+ if [[ ! -d "$iesdp_dir" ]]; then
14
+ mkdir -p "$ext_dir"
15
+ git clone "https://github.com/$repo/" "$iesdp_dir" >&2 || { echo "Failed to clone IESDP repository" >&2; exit 1; }
16
+ git -C "$iesdp_dir" checkout ielib >&2 || { echo "Failed to checkout ielib branch" >&2; exit 1; }
17
+ fi
18
+
19
+ echo "$iesdp_dir"
20
+ }
@@ -0,0 +1,15 @@
1
+ #!/bin/bash
2
+
3
+ # Runs the TypeScript update script to generate action/trigger declarations.
4
+
5
+ set -xeu -o pipefail
6
+
7
+ source "$(dirname "$0")/ensure-iesdp.sh"
8
+
9
+ iesdp_dir=$(ensure_iesdp)
10
+
11
+ pnpm exec tsx scripts/ts-update.ts \
12
+ "$iesdp_dir/_data/actions" \
13
+ src/bg2/actions.d.ts \
14
+ "$iesdp_dir/scripting/triggers/bg2triggers.htm" \
15
+ src/bg2/triggers.d.ts