@swiftuijs/twill 0.2.0 → 0.3.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 (34) hide show
  1. package/README.md +22 -7
  2. package/dist/cli.js +3 -3
  3. package/dist/{compilation-cache-CuCGz02_.js → compilation-cache-7SugqJ4n.js} +2 -2
  4. package/dist/{compilation-cache-CuCGz02_.js.map → compilation-cache-7SugqJ4n.js.map} +1 -1
  5. package/dist/{compiler-DY1N07np.js → compiler-CbNSBUI2.js} +54 -15
  6. package/dist/compiler-CbNSBUI2.js.map +1 -0
  7. package/dist/config.js +1 -1
  8. package/dist/{configuration-CTJjSu_h.js → configuration-BVMf6Q_j.js} +17 -7
  9. package/dist/configuration-BVMf6Q_j.js.map +1 -0
  10. package/dist/defer.d.ts +1 -1
  11. package/dist/doctor.js +1 -1
  12. package/dist/doctor.js.map +1 -1
  13. package/dist/esbuild.js +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/{loader-compiler-DIw9aw5n.js → loader-compiler-D1teTrXw.js} +3 -3
  16. package/dist/{loader-compiler-DIw9aw5n.js.map → loader-compiler-D1teTrXw.js.map} +1 -1
  17. package/dist/loader.js +2 -2
  18. package/dist/{plugin-Bks2hxCJ.js → plugin-BHfiQ2L-.js} +3 -3
  19. package/dist/{plugin-Bks2hxCJ.js.map → plugin-BHfiQ2L-.js.map} +1 -1
  20. package/dist/project.js +14 -10
  21. package/dist/project.js.map +1 -1
  22. package/dist/recovery.d.ts +2 -2
  23. package/dist/rollup.js +1 -1
  24. package/dist/rspack.js +1 -1
  25. package/dist/syntax.js +1 -1
  26. package/dist/{transpile-DG81yfVs.js → transpile-D8P8Lsbx.js} +2 -2
  27. package/dist/{transpile-DG81yfVs.js.map → transpile-D8P8Lsbx.js.map} +1 -1
  28. package/dist/typescript-plugin.cjs +65 -22
  29. package/dist/typescript-plugin.cjs.map +1 -1
  30. package/dist/vite.js +1 -1
  31. package/dist/webpack.js +1 -1
  32. package/package.json +2 -2
  33. package/dist/compiler-DY1N07np.js.map +0 -1
  34. package/dist/configuration-CTJjSu_h.js.map +0 -1
package/README.md CHANGED
@@ -22,7 +22,7 @@ async function readOwned(acquire: () => Promise<TextDocument>) {
22
22
 
23
23
  The successful path stays in view; release sits next to acquisition. Registration is block-scoped, cleanup is awaited explicitly, and `return await` keeps an owned resource alive until its work finishes. The output uses ordinary JS control flow.
24
24
 
25
- **Release line:** 0.1. Twill is an experimental language with a tested compiler, editor and build workflow. Start with one module and evaluate it against your application’s requirements. See [support and compatibility](https://twill.evecalm.com/readiness).
25
+ **Release line:** 0.2. Twill is an experimental language with a tested compiler, editor and build workflow. Start with one module and evaluate it against your application’s requirements. See [support and compatibility](https://twill.evecalm.com/readiness).
26
26
 
27
27
  ## Why use Twill?
28
28
 
@@ -52,7 +52,9 @@ function describe(outcome: Outcome): string {
52
52
  }
53
53
  ```
54
54
 
55
- Ordinary closures become arrows; guards become branches. Direct-return switch expressions become native switches. Twill 0.2.0 also removes the IIFE from standalone identifier initializers; other expression contexts retain it. Inline emission introduces no runtime library. `defer` allocates callbacks and dynamic registrations use a local stack; general component child collection uses arrays. These costs are [documented and measured](https://twill.evecalm.com/performance).
55
+ Ordinary closures become arrows; guards become branches. Direct-return switch expressions become native switches. Twill 0.2.0 also removes the IIFE from standalone identifier initializers; other expression contexts retain it. Inline emission introduces no runtime library. Dynamic/async `defer` and scope-sensitive registrations retain callbacks; dynamic registrations use a local stack; general component child collection uses arrays. These costs are [documented and measured](https://twill.evecalm.com/performance).
56
+
57
+ Twill 0.3.0 emits native `try/finally` for eligible direct synchronous cleanup and reduces repeated editor/configuration work. The older 0.2.0 retains callback-based cleanup. Dynamic registration, general expression lowering and cold initialization still have measured costs; use the [performance guide](https://twill.evecalm.com/performance) to choose and profile hot paths.
56
58
 
57
59
  | File | Use |
58
60
  | ---------------------------- | ----------------------------------------------------------- |
@@ -68,11 +70,11 @@ Twill and native files can import each other through the build adapters, virtual
68
70
 
69
71
  The [playground](https://twill.evecalm.com/playground) runs the actual compiler locally, with live highlighting and generated-source inspection. It does not execute your code or type-check project imports.
70
72
 
71
- Install the compiler as a development dependency in your application. It supports Node 20.19+ or 22.12+. Default inline emission introduces no language runtime dependency into your application bundle. The [optional runtime](https://twill.evecalm.com/runtime) in 0.2.0 shares dynamic synchronous cleanup when explicitly selected.
73
+ Install the compiler as a development dependency in your application. Use Node 24 LTS for a new setup; the package supports `^20.19.0 || >=22.12.0`. Run from the directory containing your `package.json` (run `npm init -y` first for a new project). Default inline emission introduces no language runtime dependency into your application bundle. The [optional runtime](https://twill.evecalm.com/runtime) in 0.2.0 shares dynamic synchronous cleanup when explicitly selected.
72
74
 
73
75
  ```sh
74
- pnpm add -D @swiftuijs/twill
75
- # npm install --save-dev @swiftuijs/twill
76
+ pnpm add -D @swiftuijs/twill@0.3.0
77
+ # npm install --save-dev @swiftuijs/twill@0.3.0
76
78
  ```
77
79
 
78
80
  Add the Vite adapter to your existing configuration:
@@ -92,6 +94,19 @@ pnpm exec twill check -p tsconfig.json
92
94
 
93
95
  Follow [getting started](https://twill.evecalm.com/getting-started) to complete the editor, formatter and linter setup. For React, use the [React Vite adapter](https://twill.evecalm.com/frameworks#react-with-vite-8) to keep Fast Refresh working.
94
96
 
97
+ ## Run a script
98
+
99
+ For a project-local command, install the compiler above, save this as `hello.twill` and run `npm exec -- twill hello.twill 'hello world'` (or `pnpm exec twill hello.twill 'hello world'`):
100
+
101
+ ```typescript
102
+ #!/usr/bin/env twill
103
+ console.log(process.argv.slice(2));
104
+ ```
105
+
106
+ Expected output is `[ 'hello world' ]`. On Linux/macOS, use `chmod +x hello.twill` then `npm exec --call './hello.twill "hello world"'` or `pnpm exec ./hello.twill 'hello world'` for a local shebang. For direct `./hello.twill` execution, install `@swiftuijs/twill` globally with `npm install --global @swiftuijs/twill@0.3.0` and keep `twill` on PATH. Windows uses the explicit CLI.
107
+
108
+ Install `@swiftuijs/twill-shell` in the script's project only when using its subprocess API; a global compiler does not install project imports. Follow [shell scripting](https://twill.evecalm.com/scripting) for complete installation, a portable subprocess example, Node type checking and troubleshooting. Native JS/TS consumers need only the SDK.
109
+
95
110
  ## A complete working loop
96
111
 
97
112
  | Tool | What it provides |
@@ -136,7 +151,7 @@ Install only the tools your project needs. Keep Twill packages on the same relea
136
151
  | `@swiftuijs/twill-shell` | Native argv execution, bounded output and owned process teardown |
137
152
  | `@swiftuijs/twill-runtime` | Optional, versioned synchronous cleanup helpers |
138
153
 
139
- The [shell scripting](https://twill.evecalm.com/scripting) toolkit adds `#!/usr/bin/env twill` executable scripts and content-validated startup caching in 0.2.0. Install/import `@swiftuijs/twill-shell` for subprocesses; its Rust engine and eight-platform prebuilds install automatically. Native JS/TS consumers need no compiler. See [performance](https://twill.evecalm.com/performance#rust-shell-backend) for measured warm gains and cold/polling limits.
154
+ The [shell scripting](https://twill.evecalm.com/scripting) toolkit adds `#!/usr/bin/env twill` executable scripts and content-validated startup caching in 0.2.0. Install/import `@swiftuijs/twill-shell` for subprocesses; native JS/TS consumers need no compiler. npm 0.2.0 still uses the retired Rust addon. Twill 0.3.0 simplifies the SDK to Node spawn/streams with zero production dependencies; cancel and await work before worker/process exit. See the [version and ownership boundary](https://twill.evecalm.com/scripting#execution-and-version-boundary).
140
155
 
141
156
  Install the [Twill VS Code extension](https://marketplace.visualstudio.com/items?itemName=forth-ink.twill) from Marketplace, or run `code --install-extension forth-ink.twill`. The extension bundles its editing tools. Your application installs the compiler for builds and whole-project checks. See the [editor guide](https://twill.evecalm.com/tooling) for configuration.
142
157
 
@@ -152,7 +167,7 @@ Install the [official Twill AI skill](https://twill.evecalm.com/ai) with `npx sk
152
167
 
153
168
  ## Contributing
154
169
 
155
- The repository is a pnpm workspace with Vite builds. See the [development guide](https://github.com/swiftuijs/twill/blob/main/docs/contributing/tooling.md) for checkout, dependency and build commands. Compiler, formatter, linter, export, highlight, optional runtime and shell packages, editor packages, examples and the English documentation site own their code and tests.
170
+ The repository is a pnpm workspace with Vite builds. Tooling packages and editor integrations are developed in Twill, exercising the same public adapters, checker and declaration emitter as applications. The compiler remains bootstrapped from JS/TS; see [developing tooling in Twill](https://github.com/swiftuijs/twill/blob/main/docs/contributing/dogfooding.md). See the [development guide](https://github.com/swiftuijs/twill/blob/main/docs/contributing/tooling.md) for checkout, dependency and build commands. Compiler, formatter, linter, export, highlight, optional runtime and shell packages, editor packages, examples and the English documentation site own their code and tests.
156
171
 
157
172
  ```sh
158
173
  pnpm check
package/dist/cli.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { a as transform } from "./compiler-DY1N07np.js";
3
- import { n as loadConfig } from "./configuration-CTJjSu_h.js";
2
+ import { a as transform } from "./compiler-CbNSBUI2.js";
3
+ import { n as loadConfig } from "./configuration-BVMf6Q_j.js";
4
4
  import { TwillProject } from "./project.js";
5
5
  import { inspectProject } from "./doctor.js";
6
6
  import { emitDeclarations } from "./declarations.js";
7
- import { t as transpile } from "./transpile-DG81yfVs.js";
7
+ import { t as transpile } from "./transpile-D8P8Lsbx.js";
8
8
  import { createRequire } from "node:module";
9
9
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
10
10
  import { dirname, resolve } from "node:path";
@@ -167,6 +167,6 @@ function openCompilationCache() {
167
167
  } catch {}
168
168
  }
169
169
  //#endregion
170
- export { observe as n, openCompilationCache as r, digest as t };
170
+ export { openCompilationCache as i, observationsMatch as n, observe as r, digest as t };
171
171
 
172
- //# sourceMappingURL=compilation-cache-CuCGz02_.js.map
172
+ //# sourceMappingURL=compilation-cache-7SugqJ4n.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"compilation-cache-CuCGz02_.js","names":[],"sources":["../src/compilation-cache.ts"],"sourcesContent":["import {\n closeSync,\n constants,\n existsSync,\n fstatSync,\n lstatSync,\n mkdirSync,\n openSync,\n readFileSync,\n readdirSync,\n realpathSync,\n statSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n type Stats,\n} from 'node:fs';\nimport { createHash, randomUUID } from 'node:crypto';\nimport { createRequire } from 'node:module';\nimport { homedir } from 'node:os';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport type ConfigurationObservation = {\n path: string;\n kind: 'file' | 'directory';\n value: string;\n};\nconst limit = 512 * 1024;\nconst packages = [\n 'typescript',\n 'acorn',\n 'acorn-typescript',\n 'acorn-jsx',\n 'magic-string',\n '@ampproject/remapping',\n '@jridgewell/trace-mapping',\n];\nexport const digest = (source: string | Uint8Array) =>\n createHash('sha256').update(source).digest('hex');\n\nexport function observe(path: string, kind: ConfigurationObservation['kind']): string {\n try {\n if (kind === 'directory') return statSync(path).isDirectory() ? 'directory' : 'absent';\n return digest(readFileSync(path));\n } catch (error) {\n return ['ENOENT', 'ENOTDIR'].includes((error as NodeJS.ErrnoException).code ?? '')\n ? 'absent'\n : 'unavailable';\n }\n}\nexport function observationsMatch(observations: ConfigurationObservation[]): boolean {\n return observations.every(({ path, kind, value }) => {\n const current = observe(path, kind);\n return current !== 'unavailable' && current === value;\n });\n}\n\n/** Hash actual installed code without evaluating any compiler or dependency module. */\nexport function toolchainIdentity(\n location = import.meta.url,\n names = packages,\n): string | undefined {\n try {\n const hash = createHash('sha256').update('twill-loader-cache-v1\\0' + process.version);\n const add = (path: string, bytes: Uint8Array) =>\n hash.update(JSON.stringify([path, digest(bytes)]));\n const directory = dirname(fileURLToPath(location));\n for (const file of readdirSync(directory).sort())\n if (file.endsWith('.js')) add(file, readFileSync(join(directory, file)));\n const seen = new Set<string>();\n const visit = (name: string, from: string) => {\n const require = createRequire(from);\n let root = dirname(require.resolve(name));\n for (;;) {\n const metadata = join(root, 'package.json');\n if (existsSync(metadata) && JSON.parse(readFileSync(metadata, 'utf8')).name === name) break;\n const parent = dirname(root);\n if (parent === root) throw new Error('Package metadata unavailable');\n root = parent;\n }\n root = realpathSync(root);\n if (seen.has(root)) return;\n seen.add(root);\n const metadata = join(root, 'package.json');\n const value = JSON.parse(readFileSync(metadata, 'utf8'));\n add(name, readFileSync(metadata));\n const walk = (folder: string) => {\n for (const entry of readdirSync(folder, { withFileTypes: true }).sort((a, b) =>\n a.name.localeCompare(b.name),\n )) {\n const path = join(folder, entry.name);\n if (entry.isSymbolicLink() && statSync(path).isDirectory())\n throw new Error('Directory symlink requires uncached compilation');\n if (entry.isDirectory() && entry.name !== 'node_modules') walk(path);\n else if (/\\.(?:[cm]?js|json)$/.test(entry.name))\n add(path.slice(root.length), readFileSync(path));\n }\n };\n // TypeScript emission/configuration executes this single compiler bundle;\n // hashing its CLI/server copies would add unrelated cold-start work.\n if (name === 'typescript')\n add('typescript/lib/typescript.js', readFileSync(require.resolve(name)));\n else walk(root);\n for (const dependency of Object.keys(value.dependencies ?? {}).sort())\n visit(dependency, metadata);\n };\n for (const name of names) visit(name, location);\n return hash.digest('hex');\n } catch {\n return undefined;\n }\n}\n\nfunction safeDirectory(directory: string): boolean {\n const info = lstatSync(directory);\n if (!info.isDirectory() || info.isSymbolicLink()) return false;\n if (process.platform === 'win32') return true; // User-selected/profile ACL, not a POSIX mode claim.\n const uid = process.getuid!();\n if (info.uid !== uid || (info.mode & 0o077) !== 0) return false;\n let path = realpathSync(directory);\n for (;;) {\n const parent = lstatSync(path);\n if (parent.uid !== uid && parent.uid !== 0) return false;\n if ((parent.mode & 0o022) !== 0 && (parent.mode & 0o1000) === 0) return false;\n const next = dirname(path);\n if (next === path) return true;\n path = next;\n }\n}\nfunction safeFile(info: Stats): boolean {\n return (\n info.isFile() &&\n info.nlink === 1 &&\n info.size <= limit &&\n (process.platform === 'win32' || (info.uid === process.getuid!() && (info.mode & 0o077) === 0))\n );\n}\n\n/** Fixed slots bound completed storage; collisions are misses, never shared module results. */\nexport class CompilationCache {\n constructor(\n private readonly directory: string,\n private readonly identity: string,\n ) {}\n\n private key(url: string, source: string, dialect: boolean) {\n return digest(JSON.stringify([this.identity, url, digest(source), dialect, 'ES2022']));\n }\n private slot(url: string) {\n return join(this.directory, (parseInt(digest(url).slice(0, 2), 16) % 128) + '.json');\n }\n read(url: string, source: string, dialect: boolean): string | undefined {\n let fd: number | undefined;\n try {\n if (!safeDirectory(this.directory)) return;\n const file = this.slot(url);\n if (lstatSync(file).isSymbolicLink()) return;\n fd = openSync(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));\n if (!safeFile(fstatSync(fd))) return;\n const value = JSON.parse(readFileSync(fd, 'utf8'));\n if (\n value.key !== this.key(url, source, dialect) ||\n typeof value.source !== 'string' ||\n value.digest !== digest(value.source) ||\n !Array.isArray(value.observations) ||\n !value.observations.every(\n (entry: ConfigurationObservation) =>\n entry &&\n typeof entry.path === 'string' &&\n isAbsolute(entry.path) &&\n ['file', 'directory'].includes(entry.kind) &&\n typeof entry.value === 'string',\n ) ||\n !observationsMatch(value.observations)\n )\n return;\n return value.source;\n } catch {\n return;\n } finally {\n if (fd !== undefined) {\n try {\n closeSync(fd);\n } catch {\n /* Cache cleanup is optional too. */\n }\n }\n }\n }\n write(\n url: string,\n source: string,\n dialect: boolean,\n emitted: string,\n observations: ConfigurationObservation[],\n ): void {\n let temporary: string | undefined;\n try {\n const value = JSON.stringify({\n key: this.key(url, source, dialect),\n source: emitted,\n digest: digest(emitted),\n observations,\n });\n if (\n Buffer.byteLength(value) > limit ||\n !safeDirectory(this.directory) ||\n !observationsMatch(observations)\n )\n return;\n temporary = join(this.directory, randomUUID() + '.tmp');\n writeFileSync(temporary, value, { flag: 'wx', mode: 0o600 });\n renameSync(temporary, this.slot(url));\n } catch {\n // An executable cache is optional; its failure never changes script behavior.\n } finally {\n if (temporary !== undefined) {\n try {\n unlinkSync(temporary);\n } catch {\n // Renamed, concurrently removed, or inaccessible; never replace the program's result.\n }\n }\n }\n }\n}\n\nexport function openCompilationCache(): CompilationCache | undefined {\n try {\n const directory = process.env.TWILL_CACHE_DIR ?? join(homedir(), '.twill', 'script-cache-v1');\n if (!isAbsolute(directory)) return;\n mkdirSync(directory, { recursive: true, mode: 0o700 });\n if (!safeDirectory(directory)) return;\n const identity = toolchainIdentity();\n if (identity) return new CompilationCache(resolve(directory), identity);\n } catch {\n // Permissions, unsupported environment or unavailable identity: compile normally.\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"compilation-cache-7SugqJ4n.js","names":[],"sources":["../src/compilation-cache.ts"],"sourcesContent":["import {\n closeSync,\n constants,\n existsSync,\n fstatSync,\n lstatSync,\n mkdirSync,\n openSync,\n readFileSync,\n readdirSync,\n realpathSync,\n statSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n type Stats,\n} from 'node:fs';\nimport { createHash, randomUUID } from 'node:crypto';\nimport { createRequire } from 'node:module';\nimport { homedir } from 'node:os';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport type ConfigurationObservation = {\n path: string;\n kind: 'file' | 'directory';\n value: string;\n};\nconst limit = 512 * 1024;\nconst packages = [\n 'typescript',\n 'acorn',\n 'acorn-typescript',\n 'acorn-jsx',\n 'magic-string',\n '@ampproject/remapping',\n '@jridgewell/trace-mapping',\n];\nexport const digest = (source: string | Uint8Array) =>\n createHash('sha256').update(source).digest('hex');\n\nexport function observe(path: string, kind: ConfigurationObservation['kind']): string {\n try {\n if (kind === 'directory') return statSync(path).isDirectory() ? 'directory' : 'absent';\n return digest(readFileSync(path));\n } catch (error) {\n return ['ENOENT', 'ENOTDIR'].includes((error as NodeJS.ErrnoException).code ?? '')\n ? 'absent'\n : 'unavailable';\n }\n}\nexport function observationsMatch(observations: ConfigurationObservation[]): boolean {\n return observations.every(({ path, kind, value }) => {\n const current = observe(path, kind);\n return current !== 'unavailable' && current === value;\n });\n}\n\n/** Hash actual installed code without evaluating any compiler or dependency module. */\nexport function toolchainIdentity(\n location = import.meta.url,\n names = packages,\n): string | undefined {\n try {\n const hash = createHash('sha256').update('twill-loader-cache-v1\\0' + process.version);\n const add = (path: string, bytes: Uint8Array) =>\n hash.update(JSON.stringify([path, digest(bytes)]));\n const directory = dirname(fileURLToPath(location));\n for (const file of readdirSync(directory).sort())\n if (file.endsWith('.js')) add(file, readFileSync(join(directory, file)));\n const seen = new Set<string>();\n const visit = (name: string, from: string) => {\n const require = createRequire(from);\n let root = dirname(require.resolve(name));\n for (;;) {\n const metadata = join(root, 'package.json');\n if (existsSync(metadata) && JSON.parse(readFileSync(metadata, 'utf8')).name === name) break;\n const parent = dirname(root);\n if (parent === root) throw new Error('Package metadata unavailable');\n root = parent;\n }\n root = realpathSync(root);\n if (seen.has(root)) return;\n seen.add(root);\n const metadata = join(root, 'package.json');\n const value = JSON.parse(readFileSync(metadata, 'utf8'));\n add(name, readFileSync(metadata));\n const walk = (folder: string) => {\n for (const entry of readdirSync(folder, { withFileTypes: true }).sort((a, b) =>\n a.name.localeCompare(b.name),\n )) {\n const path = join(folder, entry.name);\n if (entry.isSymbolicLink() && statSync(path).isDirectory())\n throw new Error('Directory symlink requires uncached compilation');\n if (entry.isDirectory() && entry.name !== 'node_modules') walk(path);\n else if (/\\.(?:[cm]?js|json)$/.test(entry.name))\n add(path.slice(root.length), readFileSync(path));\n }\n };\n // TypeScript emission/configuration executes this single compiler bundle;\n // hashing its CLI/server copies would add unrelated cold-start work.\n if (name === 'typescript')\n add('typescript/lib/typescript.js', readFileSync(require.resolve(name)));\n else walk(root);\n for (const dependency of Object.keys(value.dependencies ?? {}).sort())\n visit(dependency, metadata);\n };\n for (const name of names) visit(name, location);\n return hash.digest('hex');\n } catch {\n return undefined;\n }\n}\n\nfunction safeDirectory(directory: string): boolean {\n const info = lstatSync(directory);\n if (!info.isDirectory() || info.isSymbolicLink()) return false;\n if (process.platform === 'win32') return true; // User-selected/profile ACL, not a POSIX mode claim.\n const uid = process.getuid!();\n if (info.uid !== uid || (info.mode & 0o077) !== 0) return false;\n let path = realpathSync(directory);\n for (;;) {\n const parent = lstatSync(path);\n if (parent.uid !== uid && parent.uid !== 0) return false;\n if ((parent.mode & 0o022) !== 0 && (parent.mode & 0o1000) === 0) return false;\n const next = dirname(path);\n if (next === path) return true;\n path = next;\n }\n}\nfunction safeFile(info: Stats): boolean {\n return (\n info.isFile() &&\n info.nlink === 1 &&\n info.size <= limit &&\n (process.platform === 'win32' || (info.uid === process.getuid!() && (info.mode & 0o077) === 0))\n );\n}\n\n/** Fixed slots bound completed storage; collisions are misses, never shared module results. */\nexport class CompilationCache {\n constructor(\n private readonly directory: string,\n private readonly identity: string,\n ) {}\n\n private key(url: string, source: string, dialect: boolean) {\n return digest(JSON.stringify([this.identity, url, digest(source), dialect, 'ES2022']));\n }\n private slot(url: string) {\n return join(this.directory, (parseInt(digest(url).slice(0, 2), 16) % 128) + '.json');\n }\n read(url: string, source: string, dialect: boolean): string | undefined {\n let fd: number | undefined;\n try {\n if (!safeDirectory(this.directory)) return;\n const file = this.slot(url);\n if (lstatSync(file).isSymbolicLink()) return;\n fd = openSync(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));\n if (!safeFile(fstatSync(fd))) return;\n const value = JSON.parse(readFileSync(fd, 'utf8'));\n if (\n value.key !== this.key(url, source, dialect) ||\n typeof value.source !== 'string' ||\n value.digest !== digest(value.source) ||\n !Array.isArray(value.observations) ||\n !value.observations.every(\n (entry: ConfigurationObservation) =>\n entry &&\n typeof entry.path === 'string' &&\n isAbsolute(entry.path) &&\n ['file', 'directory'].includes(entry.kind) &&\n typeof entry.value === 'string',\n ) ||\n !observationsMatch(value.observations)\n )\n return;\n return value.source;\n } catch {\n return;\n } finally {\n if (fd !== undefined) {\n try {\n closeSync(fd);\n } catch {\n /* Cache cleanup is optional too. */\n }\n }\n }\n }\n write(\n url: string,\n source: string,\n dialect: boolean,\n emitted: string,\n observations: ConfigurationObservation[],\n ): void {\n let temporary: string | undefined;\n try {\n const value = JSON.stringify({\n key: this.key(url, source, dialect),\n source: emitted,\n digest: digest(emitted),\n observations,\n });\n if (\n Buffer.byteLength(value) > limit ||\n !safeDirectory(this.directory) ||\n !observationsMatch(observations)\n )\n return;\n temporary = join(this.directory, randomUUID() + '.tmp');\n writeFileSync(temporary, value, { flag: 'wx', mode: 0o600 });\n renameSync(temporary, this.slot(url));\n } catch {\n // An executable cache is optional; its failure never changes script behavior.\n } finally {\n if (temporary !== undefined) {\n try {\n unlinkSync(temporary);\n } catch {\n // Renamed, concurrently removed, or inaccessible; never replace the program's result.\n }\n }\n }\n }\n}\n\nexport function openCompilationCache(): CompilationCache | undefined {\n try {\n const directory = process.env.TWILL_CACHE_DIR ?? join(homedir(), '.twill', 'script-cache-v1');\n if (!isAbsolute(directory)) return;\n mkdirSync(directory, { recursive: true, mode: 0o700 });\n if (!safeDirectory(directory)) return;\n const identity = toolchainIdentity();\n if (identity) return new CompilationCache(resolve(directory), identity);\n } catch {\n // Permissions, unsupported environment or unavailable identity: compile normally.\n }\n}\n"],"mappings":"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"}
@@ -189,6 +189,15 @@ function parserFor(language) {
189
189
  if (parsers.has(language)) return parsers.get(language);
190
190
  const Base = bases[language];
191
191
  class TwillParser extends Base {
192
+ declareName(name, bindingType, position) {
193
+ if (bindingType === 6 || bindingType === 7) {
194
+ const scope = this.currentScope();
195
+ (scope.types ??= []).push(name);
196
+ if (this.inModule && scope.flags & 1) delete this.undefinedExports[name];
197
+ return;
198
+ }
199
+ super.declareName(name, bindingType, position);
200
+ }
192
201
  skipHeader = null;
193
202
  closures = [];
194
203
  lineStarts = null;
@@ -662,9 +671,10 @@ function parse(source, language = "ts", sourceType = "module") {
662
671
  if (node.type === "DeferStatement") defers.push(node);
663
672
  if (node.type === "TwillSwitchExpression") switches.push(node);
664
673
  if (node.type === "TwillEnumDeclaration") enums.push(node);
665
- for (const [key, value] of Object.entries(node)) {
674
+ for (const key of Object.keys(node)) {
666
675
  if (key === "trailing" || key === "loc") continue;
667
- if (Array.isArray(value)) value.forEach(visit);
676
+ const value = node[key];
677
+ if (Array.isArray(value)) for (const child of value) visit(child);
668
678
  else if (value?.type) visit(value);
669
679
  }
670
680
  };
@@ -697,15 +707,15 @@ var functions = /* @__PURE__ */ new Set([
697
707
  "ArrowFunctionExpression"
698
708
  ]);
699
709
  function children(node, visit) {
700
- for (const [key, value] of Object.entries(node)) {
710
+ for (const key of Object.keys(node)) {
701
711
  if (key === "loc" || key === "trailing") continue;
702
- if (Array.isArray(value)) value.forEach((child) => {
703
- if (child?.type) visit(child);
704
- });
705
- else if (value?.type) visit(value);
712
+ const value = node[key];
713
+ if (Array.isArray(value)) {
714
+ for (const child of value) if (child?.type) visit(child);
715
+ } else if (value?.type) visit(value);
706
716
  }
707
717
  }
708
- /** Scope-local cleanup callbacks; dynamic registrations use lazy stacks. */
718
+ /** Native finally where scope permits; callbacks retain dynamic registration semantics. */
709
719
  function lowerDefers(source, ast, code, language, usedNames, contentStarts, comments, runtime, sourceType, fail) {
710
720
  const scopes = [];
711
721
  const visit = (node, scope, parent) => {
@@ -760,6 +770,35 @@ function lowerDefers(source, ast, code, language, usedNames, contentStarts, comm
760
770
  if (!scope.defers.length) continue;
761
771
  const { block } = scope;
762
772
  const statements = block.body;
773
+ const first = statements.indexOf(scope.defers[0]);
774
+ let native = first >= 0 && scope.defers.every((node) => !node.awaited && statements.includes(node));
775
+ if (native) {
776
+ native = !statements.some((node, index) => node.type === "FunctionDeclaration" || node.type === "TSDeclareFunction" || node.type === "VariableDeclaration" && node.kind.endsWith("using") || index > first && (node.type === "VariableDeclaration" && node.kind !== "var" || node.type.endsWith("Declaration") && node.type !== "VariableDeclaration" || node.type === "GuardStatement" && node.binding));
777
+ const check = (node) => {
778
+ if (node.type === "WithStatement" || node.type === "ThisExpression" || node.type === "VariableDeclarator" && !node.init || node.type === "VariableDeclaration" && node.kind === "var" && (!statements.includes(node) || node.start > scope.defers[0].start) || node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "eval") native = false;
779
+ children(node, check);
780
+ };
781
+ if (native) check(block);
782
+ const cleanupScope = (node) => {
783
+ if (node.type === "FunctionDeclaration" || node.directive || node.type === "VariableDeclaration" && node.kind === "var") native = false;
784
+ if (!functions.has(node.type)) children(node, cleanupScope);
785
+ };
786
+ if (native) for (const node of scope.defers) cleanupScope(node.cleanup.body);
787
+ }
788
+ if (native) {
789
+ for (const node of [...scope.defers].reverse()) {
790
+ code.overwrite(node.start, node.cleanup.body.start, ";try {");
791
+ if (!node.cleanup.body.body.length) {
792
+ code.remove(node.cleanup.body.start, node.cleanup.body.end);
793
+ code.appendLeft(block.end - 1, "\n} finally " + source.slice(node.cleanup.body.start, node.cleanup.body.end) + "\n");
794
+ continue;
795
+ }
796
+ code.prependRight(node.cleanup.body.start, "\n} finally ");
797
+ code.appendLeft(node.cleanup.body.end, "\n");
798
+ if (node.cleanup.body.end !== block.end - 1) code.move(node.cleanup.body.start, node.cleanup.body.end, block.end - 1);
799
+ }
800
+ continue;
801
+ }
763
802
  const single = scope.defers.length === 1 && statements.includes(scope.defers[0]);
764
803
  const stack = fresh(single ? "Cleanup" : "Defers");
765
804
  const failure = single ? "" : fresh("Failure");
@@ -1231,12 +1270,12 @@ var TwillSyntaxError = class extends SyntaxError {
1231
1270
  };
1232
1271
  function walk(node, visit) {
1233
1272
  visit(node);
1234
- for (const [key, value] of Object.entries(node)) {
1235
- if (["loc", "trailing"].includes(key)) continue;
1236
- if (Array.isArray(value)) value.forEach((child) => {
1237
- if (child?.type) walk(child, visit);
1238
- });
1239
- else if (value?.type) walk(value, visit);
1273
+ for (const key of Object.keys(node)) {
1274
+ if (key === "loc" || key === "trailing") continue;
1275
+ const value = node[key];
1276
+ if (Array.isArray(value)) {
1277
+ for (const child of value) if (child?.type) walk(child, visit);
1278
+ } else if (value?.type) walk(value, visit);
1240
1279
  }
1241
1280
  }
1242
1281
  function calleeName(node) {
@@ -1604,4 +1643,4 @@ function generatedPosition(result, line, column) {
1604
1643
  //#endregion
1605
1644
  export { transform as a, originalPosition as i, generatedPosition as n, parse as o, inferLanguage as r, TwillSyntaxError as t };
1606
1645
 
1607
- //# sourceMappingURL=compiler-DY1N07np.js.map
1646
+ //# sourceMappingURL=compiler-CbNSBUI2.js.map