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.
- package/.claude/settings.local.json +59 -0
- package/.github/workflows/build.yml +33 -0
- package/.github/workflows/npm-publish.yml +24 -0
- package/CLAUDE.md +68 -0
- package/README.md +21 -0
- package/eslint.config.js +34 -0
- package/package.json +28 -0
- package/pnpm-workspace.yaml +5 -0
- package/scripts/barrel-generator.test.ts +155 -0
- package/scripts/barrel-generator.ts +207 -0
- package/scripts/ensure-iesdp.sh +20 -0
- package/scripts/ts-update.sh +15 -0
- package/scripts/ts-update.test.ts +136 -0
- package/scripts/ts-update.ts +457 -0
- package/scripts/utils.ts +32 -0
- package/src/CHANGELOG.md +45 -0
- package/src/README.md +23 -0
- package/src/ambient.d.ts +23 -0
- package/src/bg1/index.d.ts +6 -0
- package/src/bg2/actions.d.ts +3512 -0
- package/src/bg2/align.ids.d.ts +4 -0
- package/src/bg2/animate.ids.d.ts +326 -0
- package/src/bg2/areaflag.ids.d.ts +4 -0
- package/src/bg2/areatype.ids.d.ts +4 -0
- package/src/bg2/astyles.ids.d.ts +11 -0
- package/src/bg2/class.ids.d.ts +135 -0
- package/src/bg2/damages.ids.d.ts +4 -0
- package/src/bg2/difflev.ids.d.ts +4 -0
- package/src/bg2/dir.ids.ts +23 -0
- package/src/bg2/dmgtype.ids.d.ts +4 -0
- package/src/bg2/ea.ids.d.ts +5 -0
- package/src/bg2/gender.ids.d.ts +4 -0
- package/src/bg2/general.ids.d.ts +4 -0
- package/src/bg2/gtimes.ids.d.ts +4 -0
- package/src/bg2/happy.ids.d.ts +4 -0
- package/src/bg2/help.d.ts +42 -0
- package/src/bg2/hotkey.ids.d.ts +4 -0
- package/src/bg2/index.ts +1809 -0
- package/src/bg2/jourtype.ids.d.ts +4 -0
- package/src/bg2/kit.ids.d.ts +4 -0
- package/src/bg2/mflags.ids.d.ts +4 -0
- package/src/bg2/modal.ids.d.ts +14 -0
- package/src/bg2/npc.ids.d.ts +4 -0
- package/src/bg2/object.d.ts +366 -0
- package/src/bg2/object.ts +69 -0
- package/src/bg2/race.ids.d.ts +85 -0
- package/src/bg2/reaction.ids.d.ts +4 -0
- package/src/bg2/scrlev.ids.d.ts +4 -0
- package/src/bg2/scroll.ids.d.ts +4 -0
- package/src/bg2/seq.ids.d.ts +4 -0
- package/src/bg2/shoutids.ids.d.ts +15 -0
- package/src/bg2/slots.ids.d.ts +88 -0
- package/src/bg2/sndslot.ids.d.ts +4 -0
- package/src/bg2/soundoff.ids.d.ts +4 -0
- package/src/bg2/specific.ids.d.ts +4 -0
- package/src/bg2/spell.ids.d.ts +2008 -0
- package/src/bg2/state.ids.d.ts +124 -0
- package/src/bg2/stats.ids.d.ts +4 -0
- package/src/bg2/time.ids.d.ts +4 -0
- package/src/bg2/timeoday.ids.d.ts +4 -0
- package/src/bg2/triggers.d.ts +1082 -0
- package/src/bg2/weather.ids.d.ts +4 -0
- package/src/index.ts +107 -0
- package/src/package.json +21 -0
- package/src/tsconfig.json +11 -0
- 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
|
+
[](https://t.me/bgforge)
|
|
3
|
+
[](https://discord.gg/4Yqfggm)
|
|
4
|
+
[](https://bgforge.net/irc)
|
|
5
|
+
[](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
|
+
```
|
package/eslint.config.js
ADDED
|
@@ -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,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
|