@swiftuijs/twill 0.1.2 → 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.
- package/README.md +57 -14
- package/bin/run.mjs +15 -0
- package/bin/twill.mjs +34 -11
- package/dist/branches.d.ts +6 -3
- package/dist/cli.js +13 -6
- package/dist/cli.js.map +1 -1
- package/dist/compilation-cache-7SugqJ4n.js +172 -0
- package/dist/compilation-cache-7SugqJ4n.js.map +1 -0
- package/dist/compilation-cache.d.ts +21 -0
- package/dist/{compiler-Bi9TNOhX.js → compiler-CbNSBUI2.js} +583 -64
- package/dist/compiler-CbNSBUI2.js.map +1 -0
- package/dist/compiler.d.ts +17 -2
- package/dist/config.d.ts +1 -6
- package/dist/config.js +1 -54
- package/dist/configuration-BVMf6Q_j.js +149 -0
- package/dist/configuration-BVMf6Q_j.js.map +1 -0
- package/dist/configuration.d.ts +12 -0
- package/dist/defer-helper.d.ts +1 -0
- package/dist/defer.d.ts +2 -2
- package/dist/doctor.js +9 -5
- package/dist/doctor.js.map +1 -1
- package/dist/editor.d.ts +2 -0
- package/dist/editor.js +75 -4
- package/dist/editor.js.map +1 -1
- package/dist/enums.d.ts +27 -0
- package/dist/esbuild.js +1 -1
- package/dist/extensions-B1xTkoYP.js +9 -0
- package/dist/extensions-B1xTkoYP.js.map +1 -0
- package/dist/extensions.d.ts +2 -0
- package/dist/{files-D1mERoxc.js → files-BW9geU0_.js} +2 -2
- package/dist/files-BW9geU0_.js.map +1 -0
- package/dist/index.js +2 -1
- package/dist/loader-compiler-D1teTrXw.js +19 -0
- package/dist/loader-compiler-D1teTrXw.js.map +1 -0
- package/dist/loader-compiler.d.ts +4 -0
- package/dist/loader.d.ts +4 -0
- package/dist/loader.js +29 -22
- package/dist/loader.js.map +1 -1
- package/dist/parser.d.ts +2 -0
- package/dist/{plugin-BGabvyT6.js → plugin-BHfiQ2L-.js} +41 -12
- package/dist/plugin-BHfiQ2L-.js.map +1 -0
- package/dist/project.js +39 -11
- package/dist/project.js.map +1 -1
- package/dist/recovery.d.ts +2 -2
- package/dist/rollup.js +1 -1
- package/dist/rspack.js +1 -1
- package/dist/syntax.d.ts +2 -0
- package/dist/syntax.js +1 -1
- package/dist/{transpile-DwVekVFM.js → transpile-D8P8Lsbx.js} +2 -2
- package/dist/{transpile-DwVekVFM.js.map → transpile-D8P8Lsbx.js.map} +1 -1
- package/dist/typescript-plugin.cjs +783 -82
- package/dist/typescript-plugin.cjs.map +1 -1
- package/dist/vite.js +1 -1
- package/dist/webpack.js +1 -1
- package/package.json +8 -4
- package/schemas/twill.schema.json +6 -0
- package/dist/compiler-Bi9TNOhX.js.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/files-D1mERoxc.js.map +0 -1
- package/dist/plugin-BGabvyT6.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Twill is a **TypeScript-based language with Swift-inspired syntax extensions**, built by [forth.ink](https://forth.ink). It extends TypeScript and TSX with guards, `defer`, trailing closures and checked switch expressions. Use TypeScript's type system and existing JavaScript libraries; compile to ordinary JavaScript.
|
|
6
6
|
|
|
7
|
-
[Documentation](https://twill.evecalm.com/) · [Why Twill?](https://twill.evecalm.com/why-twill) · [Playground](https://twill.evecalm.com/playground) · [Getting started](https://twill.evecalm.com/getting-started) · [VS Code extension](https://marketplace.visualstudio.com/items?itemName=forth-ink.twill) · [Compatibility](https://twill.evecalm.com/readiness)
|
|
7
|
+
[Documentation](https://twill.evecalm.com/) · [Why Twill?](https://twill.evecalm.com/why-twill) · [Playground](https://twill.evecalm.com/playground) · [Getting started](https://twill.evecalm.com/getting-started) · [AI skill](https://twill.evecalm.com/ai) · [VS Code extension](https://marketplace.visualstudio.com/items?itemName=forth-ink.twill) · [Compatibility](https://twill.evecalm.com/readiness)
|
|
8
8
|
|
|
9
9
|
```twill
|
|
10
10
|
async function readOwned(acquire: () => Promise<TextDocument>) {
|
|
@@ -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.
|
|
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; other expression
|
|
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.
|
|
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 |
|
|
@@ -112,15 +127,31 @@ Independent [examples](https://github.com/swiftuijs/twill/blob/main/examples/REA
|
|
|
112
127
|
|
|
113
128
|
## Packages
|
|
114
129
|
|
|
130
|
+
The compiler includes adapters for your existing bundler:
|
|
131
|
+
|
|
132
|
+
| Host | Adapter import | Native TS/JSX owner |
|
|
133
|
+
| ------- | -------------------------- | ------------------------ |
|
|
134
|
+
| Vite | `@swiftuijs/twill/vite` | Vite |
|
|
135
|
+
| Rollup | `@swiftuijs/twill/rollup` | Twill adapter by default |
|
|
136
|
+
| Webpack | `@swiftuijs/twill/webpack` | Twill adapter by default |
|
|
137
|
+
| Rspack | `@swiftuijs/twill/rspack` | Twill adapter by default |
|
|
138
|
+
| esbuild | `@swiftuijs/twill/esbuild` | esbuild |
|
|
139
|
+
|
|
140
|
+
Use `@swiftuijs/twill/vite-react` for React Fast Refresh. The [build guide](https://twill.evecalm.com/build-tools#adapter-support) records tested host versions, watch/incremental workflows and the 0.2.0 configuration-refresh behavior. Builds remain transpile-only; run `twill check` separately.
|
|
141
|
+
|
|
115
142
|
Install only the tools your project needs. Keep Twill packages on the same release version.
|
|
116
143
|
|
|
117
|
-
| Package | Purpose
|
|
118
|
-
| ---------------------------------------------------------------------------------------- |
|
|
119
|
-
| [`@swiftuijs/twill`](https://www.npmjs.com/package/@swiftuijs/twill) | Compiler, `twill` CLI, checker, build adapters and Node loader
|
|
120
|
-
| [`@swiftuijs/twill-formatter`](https://www.npmjs.com/package/@swiftuijs/twill-formatter) | Prettier plugin and browser-compatible formatting API
|
|
121
|
-
| [`@swiftuijs/twill-linter`](https://www.npmjs.com/package/@swiftuijs/twill-linter) | ESLint flat configurations and source-mapped fixes
|
|
122
|
-
| [`@swiftuijs/twill-export`](https://www.npmjs.com/package/@swiftuijs/twill-export) | Optional `twill export` command for native TS/TSX output
|
|
123
|
-
| [`@swiftuijs/twill-highlight`](https://www.npmjs.com/package/@swiftuijs/twill-highlight) | Browser/SSR Shiki integration and TextMate grammars
|
|
144
|
+
| Package | Purpose |
|
|
145
|
+
| ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
|
|
146
|
+
| [`@swiftuijs/twill`](https://www.npmjs.com/package/@swiftuijs/twill) | Compiler, `twill` CLI, checker, build adapters and Node loader |
|
|
147
|
+
| [`@swiftuijs/twill-formatter`](https://www.npmjs.com/package/@swiftuijs/twill-formatter) | Prettier plugin and browser-compatible formatting API |
|
|
148
|
+
| [`@swiftuijs/twill-linter`](https://www.npmjs.com/package/@swiftuijs/twill-linter) | ESLint flat configurations and source-mapped fixes |
|
|
149
|
+
| [`@swiftuijs/twill-export`](https://www.npmjs.com/package/@swiftuijs/twill-export) | Optional `twill export` command for native TS/TSX output |
|
|
150
|
+
| [`@swiftuijs/twill-highlight`](https://www.npmjs.com/package/@swiftuijs/twill-highlight) | Browser/SSR Shiki integration and TextMate grammars |
|
|
151
|
+
| `@swiftuijs/twill-shell` | Native argv execution, bounded output and owned process teardown |
|
|
152
|
+
| `@swiftuijs/twill-runtime` | Optional, versioned synchronous cleanup helpers |
|
|
153
|
+
|
|
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).
|
|
124
155
|
|
|
125
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.
|
|
126
157
|
|
|
@@ -130,9 +161,13 @@ Twill is a useful candidate for nullable workflows, owned resources, typed state
|
|
|
130
161
|
|
|
131
162
|
Representative native/dialect bundles are compared for behavior and byte size; release artifacts have enforced size budgets. Synthetic benchmarks do not establish whole-application speed or team productivity. Profile your actual workload and exercise failure paths. See [compatibility](https://twill.evecalm.com/readiness), [performance](https://twill.evecalm.com/performance) and [gradual adoption](https://twill.evecalm.com/adoption).
|
|
132
163
|
|
|
164
|
+
## AI assistance
|
|
165
|
+
|
|
166
|
+
Install the [official Twill AI skill](https://twill.evecalm.com/ai) with `npx skills add swiftuijs/twill --skill twill`. It guides Codex, Claude Code and other Agent Skills clients through application syntax, resource ownership, integrations and verification while distinguishing released features from source prototypes. The skill does not install the compiler or other project tools.
|
|
167
|
+
|
|
133
168
|
## Contributing
|
|
134
169
|
|
|
135
|
-
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 and
|
|
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.
|
|
136
171
|
|
|
137
172
|
```sh
|
|
138
173
|
pnpm check
|
|
@@ -142,3 +177,11 @@ pnpm docs:dev
|
|
|
142
177
|
```
|
|
143
178
|
|
|
144
179
|
See [contributing](https://github.com/swiftuijs/twill/blob/main/CONTRIBUTING.md), [architecture](https://github.com/swiftuijs/twill/blob/main/docs/contributing/architecture.md), [language design](https://twill.evecalm.com/language) and [release instructions](https://github.com/swiftuijs/twill/blob/main/docs/contributing/releasing.md). MIT licensed.
|
|
180
|
+
|
|
181
|
+
## Added in 0.2.0
|
|
182
|
+
|
|
183
|
+
Twill 0.2.0 adds `if const value = lookup() { use(value); }` with branch-local nullish bindings and native destructuring. Group trailing calls in initializers, following Swift’s condition boundary. It emits native branches without a helper, closure or optional wrapper. See [syntax](https://twill.evecalm.com/syntax#branch-nullish-bindings) and [RFC 0018](https://github.com/swiftuijs/twill/blob/main/docs/rfcs/0018-optional-branch-bindings.md).
|
|
184
|
+
|
|
185
|
+
Twill 0.2.0 includes associated-value enums and `match` expressions: `match (state) { case State.loaded({ value }): value; default: 0; }`. Named payload bindings and erased factory descriptors compile to native switches without calling factories. Native switch statements retain JS fallthrough; match and switch expressions return one result without fallthrough. Use the coordinated 0.2.0 packages and extension. Compiler, formatter, linter, highlighter, export and editor changes are validated together. See [syntax and compatibility](https://twill.evecalm.com/syntax#match-expressions) and [RFC 0016](https://github.com/swiftuijs/twill/blob/main/docs/rfcs/0016-pattern-matching.md).
|
|
186
|
+
|
|
187
|
+
For Node scripts, Twill 0.2.0 supports `#!/usr/bin/env twill`, `twill script.twill` and `twill run script.twill`, with Swift-inspired guards, callbacks and cleanup. The Node ESM loader remains available through `node --import @swiftuijs/twill/register`. The [shell SDK](https://twill.evecalm.com/scripting) adds native argv execution, bounded output and process ownership without command compilation. It works independently with ordinary JS/TS; scoped streams, pipelines and shell templates remain deferred.
|
package/bin/run.mjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { resolve, extname } from 'node:path';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import { register } from 'node:module';
|
|
4
|
+
import { realpathSync } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
export async function run(filename, args) {
|
|
7
|
+
const script = resolve(filename);
|
|
8
|
+
const url = pathToFileURL(script).href;
|
|
9
|
+
const native = /^(?:\.[cm]?js|\.[cm]?ts|\.jsx|\.tsx)$/.test(extname(script));
|
|
10
|
+
const entries = native ? [] : [url, pathToFileURL(realpathSync(script)).href];
|
|
11
|
+
process.argv = [process.execPath, script, ...args];
|
|
12
|
+
process.setSourceMapsEnabled(true);
|
|
13
|
+
register('../dist/loader.js', import.meta.url, { data: { entries, cache: true } });
|
|
14
|
+
await import(url);
|
|
15
|
+
}
|
package/bin/twill.mjs
CHANGED
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
const args = process.argv.slice(2);
|
|
3
|
+
const first = args[0];
|
|
4
|
+
const commands = new Set(['compile', 'check', 'doctor', 'declarations', 'export']);
|
|
5
|
+
if (
|
|
6
|
+
first === 'run' ||
|
|
7
|
+
first === '--' ||
|
|
8
|
+
(first && !first.startsWith('-') && !commands.has(first))
|
|
9
|
+
) {
|
|
10
|
+
const usage = 'Usage: twill [run] <script> [arguments...]';
|
|
11
|
+
if (first === 'run' && (args[1] === '--help' || args[1] === '-h')) {
|
|
12
|
+
console.log(usage);
|
|
13
|
+
} else {
|
|
14
|
+
let index = first === 'run' || first === '--' ? 1 : 0;
|
|
15
|
+
if (first === 'run' && args[index] === '--') index++;
|
|
16
|
+
if (!args[index]) {
|
|
17
|
+
console.error(usage);
|
|
18
|
+
process.exitCode = 1;
|
|
19
|
+
} else {
|
|
20
|
+
const { run } = await import('./run.mjs');
|
|
21
|
+
await run(args[index], args.slice(index + 1));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
const { main } = await import('../dist/cli.js');
|
|
26
|
+
main(args)
|
|
27
|
+
.then((code) => {
|
|
28
|
+
process.exitCode = code;
|
|
29
|
+
})
|
|
30
|
+
.catch((error) => {
|
|
31
|
+
console.error(error.message);
|
|
32
|
+
if (error.frame) console.error(error.frame);
|
|
33
|
+
process.exitCode = 1;
|
|
34
|
+
});
|
|
35
|
+
}
|
package/dist/branches.d.ts
CHANGED
|
@@ -6,7 +6,10 @@ type Node = {
|
|
|
6
6
|
end: number;
|
|
7
7
|
[key: string]: any;
|
|
8
8
|
};
|
|
9
|
-
/** Native switches with per-arm scopes
|
|
10
|
-
*
|
|
11
|
-
export declare function lowerSwitchExpressions(source: string, ast: Node, switches: Node[], code: MagicString, language: Language, usedNames: Set<string>,
|
|
9
|
+
/** Native switches with per-arm scopes. Direct returns and standalone variable
|
|
10
|
+
* initializers need no extra function; other expressions keep a lexical IIFE. */
|
|
11
|
+
export declare function lowerSwitchExpressions(source: string, ast: Node, switches: Node[], code: MagicString, language: Language, usedNames: Set<string>, comments: {
|
|
12
|
+
start: number;
|
|
13
|
+
end: number;
|
|
14
|
+
}[], fail: (node: Node, message: string) => never): void;
|
|
12
15
|
export {};
|
package/dist/cli.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { loadConfig } from "./
|
|
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-
|
|
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";
|
|
11
|
-
import { parseArgs } from "node:util";
|
|
12
11
|
import { pathToFileURL } from "node:url";
|
|
12
|
+
import { parseArgs } from "node:util";
|
|
13
13
|
//#region src/cli.ts
|
|
14
14
|
async function main(args = process.argv.slice(2)) {
|
|
15
15
|
const { values, positionals, tokens } = parseArgs({
|
|
@@ -29,6 +29,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
29
29
|
type: "boolean",
|
|
30
30
|
default: false
|
|
31
31
|
},
|
|
32
|
+
runtime: { type: "string" },
|
|
32
33
|
help: {
|
|
33
34
|
type: "boolean",
|
|
34
35
|
short: "h"
|
|
@@ -48,6 +49,10 @@ async function main(args = process.argv.slice(2)) {
|
|
|
48
49
|
}
|
|
49
50
|
});
|
|
50
51
|
const [command, input, ...extra] = positionals;
|
|
52
|
+
if (values.runtime !== void 0) {
|
|
53
|
+
if (!["inline", "external"].includes(values.runtime)) throw new Error("--runtime must be inline or external");
|
|
54
|
+
if (command !== "compile" && !values.help) throw new Error("--runtime is only supported by twill compile; use twill.config.json for project commands");
|
|
55
|
+
}
|
|
51
56
|
if (command === "export") {
|
|
52
57
|
let entry;
|
|
53
58
|
try {
|
|
@@ -62,7 +67,8 @@ async function main(args = process.argv.slice(2)) {
|
|
|
62
67
|
}
|
|
63
68
|
if (values["dry-run"]) throw new Error("--dry-run is only supported by twill export");
|
|
64
69
|
if (values.help || !command) {
|
|
65
|
-
console.log("twill
|
|
70
|
+
console.log("twill <script> [arguments...]\ntwill run <script> [arguments...]");
|
|
71
|
+
console.log("twill compile <file> [-o output.ts] [--js] [--runtime inline|external]\ntwill check [-p tsconfig.json] [--json]\ntwill doctor [-p tsconfig.json] [--json]\ntwill declarations [-p tsconfig.json] [-o dist] [--build] [--json]\ntwill export [-p tsconfig.json] -o ../native-project [--dry-run] [--json]\n\ncompile keeps TypeScript types by default; --js erases types and lowers JSX.\nexport requires the optional @swiftuijs/twill-export package.\nCommands use the optional twill.config.json from the project root.");
|
|
66
72
|
return 0;
|
|
67
73
|
}
|
|
68
74
|
if (extra.length) throw new Error("Unexpected arguments: " + extra.join(" "));
|
|
@@ -107,7 +113,8 @@ async function main(args = process.argv.slice(2)) {
|
|
|
107
113
|
const root = values.project ? dirname(resolve(values.project)) : process.cwd();
|
|
108
114
|
const options = {
|
|
109
115
|
...loadConfig(root, values.project ? resolve(values.project) : void 0),
|
|
110
|
-
filename
|
|
116
|
+
filename,
|
|
117
|
+
...values.runtime === void 0 ? {} : { runtime: values.runtime }
|
|
111
118
|
};
|
|
112
119
|
const source = readFileSync(filename, "utf8");
|
|
113
120
|
const result = values.js ? transpile(source, options) : transform(source, options);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["import { readFileSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { resolve, dirname } from 'node:path';\nimport { parseArgs } from 'node:util';\nimport { createRequire } from 'node:module';\nimport { pathToFileURL } from 'node:url';\nimport { transform } from './compiler.js';\nimport { transpile } from './transpile.js';\nimport { loadConfig } from './config.js';\nimport { TwillProject } from './project.js';\nimport { inspectProject } from './doctor.js';\nimport { emitDeclarations } from './declarations.js';\n\nexport async function main(args = process.argv.slice(2)): Promise<number> {\n const { values, positionals, tokens } = parseArgs({\n args,\n allowPositionals: true,\n tokens: true,\n options: {\n out: { type: 'string', short: 'o' },\n project: { type: 'string', short: 'p' },\n js: { type: 'boolean', default: false },\n help: { type: 'boolean', short: 'h' },\n json: { type: 'boolean', default: false },\n build: { type: 'boolean', default: false },\n 'dry-run': { type: 'boolean', default: false },\n },\n });\n const [command, input, ...extra] = positionals;\n if (command === 'export') {\n // Resolve from the selected project, including under pnpm's isolated\n // dependency layout. Core does not depend on the optional export tool.\n let entry: string;\n try {\n entry = createRequire(resolve(values.project ?? 'package.json')).resolve(\n '@swiftuijs/twill-export/cli',\n );\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'MODULE_NOT_FOUND') throw error;\n throw new Error(\n 'twill export requires @swiftuijs/twill-export in your project. Install it with pnpm add -D @swiftuijs/twill-export.',\n { cause: error },\n );\n }\n const exporter = await import(pathToFileURL(entry).href);\n const index = tokens.find((token) => token.kind === 'positional')!.index;\n return exporter.main(args.filter((_, position) => position !== index));\n }\n if (values['dry-run']) throw new Error('--dry-run is only supported by twill export');\n if (values.help || !command) {\n console.log(\n 'twill compile <file> [-o output.ts] [--js]\\ntwill check [-p tsconfig.json] [--json]\\ntwill doctor [-p tsconfig.json] [--json]\\ntwill declarations [-p tsconfig.json] [-o dist] [--build] [--json]\\ntwill export [-p tsconfig.json] -o ../native-project [--dry-run] [--json]\\n\\ncompile keeps TypeScript types by default; --js erases types and lowers JSX.\\nexport requires the optional @swiftuijs/twill-export package.\\nCommands use the optional twill.config.json from the project root.',\n );\n return 0;\n }\n if (extra.length) throw new Error('Unexpected arguments: ' + extra.join(' '));\n if (command === 'declarations') {\n if (input) throw new Error('Use --project <tsconfig.json> with declarations');\n const result = emitDeclarations(resolve(values.project ?? 'tsconfig.json'), {\n outDir: values.out,\n build: values.build,\n });\n if (values.json) console.log(JSON.stringify(result, null, 2));\n else {\n for (const diagnostic of result.diagnostics)\n console.error(\n `${diagnostic.filename ?? 'tsconfig'}:${diagnostic.line}:${diagnostic.column + 1} TS${diagnostic.code}: ${diagnostic.message}`,\n );\n if (!result.diagnostics.some((item) => item.category === 'error'))\n console.log(\n `Emitted ${result.files.length} declaration/map files from ${result.projects.length} projects.`,\n );\n }\n return result.diagnostics.some((item) => item.category === 'error') ? 1 : 0;\n }\n if (command === 'check' || command === 'doctor') {\n if (input) throw new Error('Use --project <tsconfig.json> with ' + command);\n const project = new TwillProject(resolve(values.project ?? 'tsconfig.json'));\n try {\n if (command === 'doctor') {\n const report = inspectProject(project);\n if (values.json) console.log(JSON.stringify(report, null, 2));\n else {\n console.log(\n `Twill ${report.twillVersion} / TypeScript ${report.typescriptVersion} / Node ${report.nodeVersion}`,\n );\n console.log(\n `Project: ${report.root}\\nSources: ${report.files.twill} Twill, ${report.files.native} native\\nDiagnostics: ${report.diagnostics.length} (${report.diagnosticTimeMs.toFixed(1)} ms)`,\n );\n for (const diagnostic of report.diagnostics)\n console.log(\n `${diagnostic.filename ?? 'tsconfig'}:${diagnostic.line}:${diagnostic.column + 1} TS${diagnostic.code}: ${diagnostic.message}`,\n );\n console.log('Use --json to save the complete project report.');\n }\n return report.ok ? 0 : 1;\n }\n const diagnostics = project.diagnostics();\n if (values.json) console.log(JSON.stringify(diagnostics, null, 2));\n else\n for (const diagnostic of diagnostics)\n console.error(\n `${diagnostic.filename ?? 'tsconfig'}:${diagnostic.line}:${diagnostic.column + 1} TS${diagnostic.code}: ${diagnostic.message}`,\n );\n return diagnostics.some((diagnostic) => diagnostic.category === 'error') ? 1 : 0;\n } finally {\n project.dispose();\n }\n }\n if (command !== 'compile' || !input)\n throw new Error('Expected compile <file> or check. Use --help for usage.');\n const filename = resolve(input);\n const root = values.project ? dirname(resolve(values.project)) : process.cwd();\n const options = {\n ...loadConfig(root, values.project ? resolve(values.project) : undefined),\n filename,\n };\n const source = readFileSync(filename, 'utf8');\n const result = values.js ? transpile(source, options) : transform(source, options);\n if (values.out) {\n const output = resolve(values.out);\n if (output === filename) throw new Error('Output must differ from input');\n mkdirSync(dirname(output), { recursive: true });\n writeFileSync(\n output,\n result.code + `\\n//# sourceMappingURL=${output.split(/[\\\\/]/).at(-1)}.map\\n`,\n );\n const map = JSON.parse(result.map.toString());\n map.file = output.split(/[\\\\/]/).at(-1);\n writeFileSync(output + '.map', JSON.stringify(map));\n } else process.stdout.write(result.code + '\\n');\n return 0;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["import { readFileSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { resolve, dirname } from 'node:path';\nimport { parseArgs } from 'node:util';\nimport { createRequire } from 'node:module';\nimport { pathToFileURL } from 'node:url';\nimport { transform, type TransformOptions } from './compiler.js';\nimport { transpile } from './transpile.js';\nimport { loadConfig } from './config.js';\nimport { TwillProject } from './project.js';\nimport { inspectProject } from './doctor.js';\nimport { emitDeclarations } from './declarations.js';\n\nexport async function main(args = process.argv.slice(2)): Promise<number> {\n const { values, positionals, tokens } = parseArgs({\n args,\n allowPositionals: true,\n tokens: true,\n options: {\n out: { type: 'string', short: 'o' },\n project: { type: 'string', short: 'p' },\n js: { type: 'boolean', default: false },\n runtime: { type: 'string' },\n help: { type: 'boolean', short: 'h' },\n json: { type: 'boolean', default: false },\n build: { type: 'boolean', default: false },\n 'dry-run': { type: 'boolean', default: false },\n },\n });\n const [command, input, ...extra] = positionals;\n if (values.runtime !== undefined) {\n if (!['inline', 'external'].includes(values.runtime))\n throw new Error('--runtime must be inline or external');\n if (command !== 'compile' && !values.help)\n throw new Error(\n '--runtime is only supported by twill compile; use twill.config.json for project commands',\n );\n }\n if (command === 'export') {\n // Resolve from the selected project, including under pnpm's isolated\n // dependency layout. Core does not depend on the optional export tool.\n let entry: string;\n try {\n entry = createRequire(resolve(values.project ?? 'package.json')).resolve(\n '@swiftuijs/twill-export/cli',\n );\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'MODULE_NOT_FOUND') throw error;\n throw new Error(\n 'twill export requires @swiftuijs/twill-export in your project. Install it with pnpm add -D @swiftuijs/twill-export.',\n { cause: error },\n );\n }\n const exporter = await import(pathToFileURL(entry).href);\n const index = tokens.find((token) => token.kind === 'positional')!.index;\n return exporter.main(args.filter((_, position) => position !== index));\n }\n if (values['dry-run']) throw new Error('--dry-run is only supported by twill export');\n if (values.help || !command) {\n console.log('twill <script> [arguments...]\\ntwill run <script> [arguments...]');\n console.log(\n 'twill compile <file> [-o output.ts] [--js] [--runtime inline|external]\\ntwill check [-p tsconfig.json] [--json]\\ntwill doctor [-p tsconfig.json] [--json]\\ntwill declarations [-p tsconfig.json] [-o dist] [--build] [--json]\\ntwill export [-p tsconfig.json] -o ../native-project [--dry-run] [--json]\\n\\ncompile keeps TypeScript types by default; --js erases types and lowers JSX.\\nexport requires the optional @swiftuijs/twill-export package.\\nCommands use the optional twill.config.json from the project root.',\n );\n return 0;\n }\n if (extra.length) throw new Error('Unexpected arguments: ' + extra.join(' '));\n if (command === 'declarations') {\n if (input) throw new Error('Use --project <tsconfig.json> with declarations');\n const result = emitDeclarations(resolve(values.project ?? 'tsconfig.json'), {\n outDir: values.out,\n build: values.build,\n });\n if (values.json) console.log(JSON.stringify(result, null, 2));\n else {\n for (const diagnostic of result.diagnostics)\n console.error(\n `${diagnostic.filename ?? 'tsconfig'}:${diagnostic.line}:${diagnostic.column + 1} TS${diagnostic.code}: ${diagnostic.message}`,\n );\n if (!result.diagnostics.some((item) => item.category === 'error'))\n console.log(\n `Emitted ${result.files.length} declaration/map files from ${result.projects.length} projects.`,\n );\n }\n return result.diagnostics.some((item) => item.category === 'error') ? 1 : 0;\n }\n if (command === 'check' || command === 'doctor') {\n if (input) throw new Error('Use --project <tsconfig.json> with ' + command);\n const project = new TwillProject(resolve(values.project ?? 'tsconfig.json'));\n try {\n if (command === 'doctor') {\n const report = inspectProject(project);\n if (values.json) console.log(JSON.stringify(report, null, 2));\n else {\n console.log(\n `Twill ${report.twillVersion} / TypeScript ${report.typescriptVersion} / Node ${report.nodeVersion}`,\n );\n console.log(\n `Project: ${report.root}\\nSources: ${report.files.twill} Twill, ${report.files.native} native\\nDiagnostics: ${report.diagnostics.length} (${report.diagnosticTimeMs.toFixed(1)} ms)`,\n );\n for (const diagnostic of report.diagnostics)\n console.log(\n `${diagnostic.filename ?? 'tsconfig'}:${diagnostic.line}:${diagnostic.column + 1} TS${diagnostic.code}: ${diagnostic.message}`,\n );\n console.log('Use --json to save the complete project report.');\n }\n return report.ok ? 0 : 1;\n }\n const diagnostics = project.diagnostics();\n if (values.json) console.log(JSON.stringify(diagnostics, null, 2));\n else\n for (const diagnostic of diagnostics)\n console.error(\n `${diagnostic.filename ?? 'tsconfig'}:${diagnostic.line}:${diagnostic.column + 1} TS${diagnostic.code}: ${diagnostic.message}`,\n );\n return diagnostics.some((diagnostic) => diagnostic.category === 'error') ? 1 : 0;\n } finally {\n project.dispose();\n }\n }\n if (command !== 'compile' || !input)\n throw new Error('Expected compile <file> or check. Use --help for usage.');\n const filename = resolve(input);\n const root = values.project ? dirname(resolve(values.project)) : process.cwd();\n const options = {\n ...loadConfig(root, values.project ? resolve(values.project) : undefined),\n filename,\n ...(values.runtime === undefined\n ? {}\n : { runtime: values.runtime as TransformOptions['runtime'] }),\n };\n const source = readFileSync(filename, 'utf8');\n const result = values.js ? transpile(source, options) : transform(source, options);\n if (values.out) {\n const output = resolve(values.out);\n if (output === filename) throw new Error('Output must differ from input');\n mkdirSync(dirname(output), { recursive: true });\n writeFileSync(\n output,\n result.code + `\\n//# sourceMappingURL=${output.split(/[\\\\/]/).at(-1)}.map\\n`,\n );\n const map = JSON.parse(result.map.toString());\n map.file = output.split(/[\\\\/]/).at(-1);\n writeFileSync(output + '.map', JSON.stringify(map));\n } else process.stdout.write(result.code + '\\n');\n return 0;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { closeSync, constants, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
//#region src/compilation-cache.ts
|
|
8
|
+
var limit = 524288;
|
|
9
|
+
var packages = [
|
|
10
|
+
"typescript",
|
|
11
|
+
"acorn",
|
|
12
|
+
"acorn-typescript",
|
|
13
|
+
"acorn-jsx",
|
|
14
|
+
"magic-string",
|
|
15
|
+
"@ampproject/remapping",
|
|
16
|
+
"@jridgewell/trace-mapping"
|
|
17
|
+
];
|
|
18
|
+
var digest = (source) => createHash("sha256").update(source).digest("hex");
|
|
19
|
+
function observe(path, kind) {
|
|
20
|
+
try {
|
|
21
|
+
if (kind === "directory") return statSync(path).isDirectory() ? "directory" : "absent";
|
|
22
|
+
return digest(readFileSync(path));
|
|
23
|
+
} catch (error) {
|
|
24
|
+
return ["ENOENT", "ENOTDIR"].includes(error.code ?? "") ? "absent" : "unavailable";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function observationsMatch(observations) {
|
|
28
|
+
return observations.every(({ path, kind, value }) => {
|
|
29
|
+
const current = observe(path, kind);
|
|
30
|
+
return current !== "unavailable" && current === value;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/** Hash actual installed code without evaluating any compiler or dependency module. */
|
|
34
|
+
function toolchainIdentity(location = import.meta.url, names = packages) {
|
|
35
|
+
try {
|
|
36
|
+
const hash = createHash("sha256").update("twill-loader-cache-v1\0" + process.version);
|
|
37
|
+
const add = (path, bytes) => hash.update(JSON.stringify([path, digest(bytes)]));
|
|
38
|
+
const directory = dirname(fileURLToPath(location));
|
|
39
|
+
for (const file of readdirSync(directory).sort()) if (file.endsWith(".js")) add(file, readFileSync(join(directory, file)));
|
|
40
|
+
const seen = /* @__PURE__ */ new Set();
|
|
41
|
+
const visit = (name, from) => {
|
|
42
|
+
const require = createRequire(from);
|
|
43
|
+
let root = dirname(require.resolve(name));
|
|
44
|
+
for (;;) {
|
|
45
|
+
const metadata = join(root, "package.json");
|
|
46
|
+
if (existsSync(metadata) && JSON.parse(readFileSync(metadata, "utf8")).name === name) break;
|
|
47
|
+
const parent = dirname(root);
|
|
48
|
+
if (parent === root) throw new Error("Package metadata unavailable");
|
|
49
|
+
root = parent;
|
|
50
|
+
}
|
|
51
|
+
root = realpathSync(root);
|
|
52
|
+
if (seen.has(root)) return;
|
|
53
|
+
seen.add(root);
|
|
54
|
+
const metadata = join(root, "package.json");
|
|
55
|
+
const value = JSON.parse(readFileSync(metadata, "utf8"));
|
|
56
|
+
add(name, readFileSync(metadata));
|
|
57
|
+
const walk = (folder) => {
|
|
58
|
+
for (const entry of readdirSync(folder, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
59
|
+
const path = join(folder, entry.name);
|
|
60
|
+
if (entry.isSymbolicLink() && statSync(path).isDirectory()) throw new Error("Directory symlink requires uncached compilation");
|
|
61
|
+
if (entry.isDirectory() && entry.name !== "node_modules") walk(path);
|
|
62
|
+
else if (/\.(?:[cm]?js|json)$/.test(entry.name)) add(path.slice(root.length), readFileSync(path));
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
if (name === "typescript") add("typescript/lib/typescript.js", readFileSync(require.resolve(name)));
|
|
66
|
+
else walk(root);
|
|
67
|
+
for (const dependency of Object.keys(value.dependencies ?? {}).sort()) visit(dependency, metadata);
|
|
68
|
+
};
|
|
69
|
+
for (const name of names) visit(name, location);
|
|
70
|
+
return hash.digest("hex");
|
|
71
|
+
} catch {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function safeDirectory(directory) {
|
|
76
|
+
const info = lstatSync(directory);
|
|
77
|
+
if (!info.isDirectory() || info.isSymbolicLink()) return false;
|
|
78
|
+
if (process.platform === "win32") return true;
|
|
79
|
+
const uid = process.getuid();
|
|
80
|
+
if (info.uid !== uid || (info.mode & 63) !== 0) return false;
|
|
81
|
+
let path = realpathSync(directory);
|
|
82
|
+
for (;;) {
|
|
83
|
+
const parent = lstatSync(path);
|
|
84
|
+
if (parent.uid !== uid && parent.uid !== 0) return false;
|
|
85
|
+
if ((parent.mode & 18) !== 0 && (parent.mode & 512) === 0) return false;
|
|
86
|
+
const next = dirname(path);
|
|
87
|
+
if (next === path) return true;
|
|
88
|
+
path = next;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function safeFile(info) {
|
|
92
|
+
return info.isFile() && info.nlink === 1 && info.size <= limit && (process.platform === "win32" || info.uid === process.getuid() && (info.mode & 63) === 0);
|
|
93
|
+
}
|
|
94
|
+
/** Fixed slots bound completed storage; collisions are misses, never shared module results. */
|
|
95
|
+
var CompilationCache = class {
|
|
96
|
+
directory;
|
|
97
|
+
identity;
|
|
98
|
+
constructor(directory, identity) {
|
|
99
|
+
this.directory = directory;
|
|
100
|
+
this.identity = identity;
|
|
101
|
+
}
|
|
102
|
+
key(url, source, dialect) {
|
|
103
|
+
return digest(JSON.stringify([
|
|
104
|
+
this.identity,
|
|
105
|
+
url,
|
|
106
|
+
digest(source),
|
|
107
|
+
dialect,
|
|
108
|
+
"ES2022"
|
|
109
|
+
]));
|
|
110
|
+
}
|
|
111
|
+
slot(url) {
|
|
112
|
+
return join(this.directory, parseInt(digest(url).slice(0, 2), 16) % 128 + ".json");
|
|
113
|
+
}
|
|
114
|
+
read(url, source, dialect) {
|
|
115
|
+
let fd;
|
|
116
|
+
try {
|
|
117
|
+
if (!safeDirectory(this.directory)) return;
|
|
118
|
+
const file = this.slot(url);
|
|
119
|
+
if (lstatSync(file).isSymbolicLink()) return;
|
|
120
|
+
fd = openSync(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
121
|
+
if (!safeFile(fstatSync(fd))) return;
|
|
122
|
+
const value = JSON.parse(readFileSync(fd, "utf8"));
|
|
123
|
+
if (value.key !== this.key(url, source, dialect) || typeof value.source !== "string" || value.digest !== digest(value.source) || !Array.isArray(value.observations) || !value.observations.every((entry) => entry && typeof entry.path === "string" && isAbsolute(entry.path) && ["file", "directory"].includes(entry.kind) && typeof entry.value === "string") || !observationsMatch(value.observations)) return;
|
|
124
|
+
return value.source;
|
|
125
|
+
} catch {
|
|
126
|
+
return;
|
|
127
|
+
} finally {
|
|
128
|
+
if (fd !== void 0) try {
|
|
129
|
+
closeSync(fd);
|
|
130
|
+
} catch {}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
write(url, source, dialect, emitted, observations) {
|
|
134
|
+
let temporary;
|
|
135
|
+
try {
|
|
136
|
+
const value = JSON.stringify({
|
|
137
|
+
key: this.key(url, source, dialect),
|
|
138
|
+
source: emitted,
|
|
139
|
+
digest: digest(emitted),
|
|
140
|
+
observations
|
|
141
|
+
});
|
|
142
|
+
if (Buffer.byteLength(value) > limit || !safeDirectory(this.directory) || !observationsMatch(observations)) return;
|
|
143
|
+
temporary = join(this.directory, randomUUID() + ".tmp");
|
|
144
|
+
writeFileSync(temporary, value, {
|
|
145
|
+
flag: "wx",
|
|
146
|
+
mode: 384
|
|
147
|
+
});
|
|
148
|
+
renameSync(temporary, this.slot(url));
|
|
149
|
+
} catch {} finally {
|
|
150
|
+
if (temporary !== void 0) try {
|
|
151
|
+
unlinkSync(temporary);
|
|
152
|
+
} catch {}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
function openCompilationCache() {
|
|
157
|
+
try {
|
|
158
|
+
const directory = process.env.TWILL_CACHE_DIR ?? join(homedir(), ".twill", "script-cache-v1");
|
|
159
|
+
if (!isAbsolute(directory)) return;
|
|
160
|
+
mkdirSync(directory, {
|
|
161
|
+
recursive: true,
|
|
162
|
+
mode: 448
|
|
163
|
+
});
|
|
164
|
+
if (!safeDirectory(directory)) return;
|
|
165
|
+
const identity = toolchainIdentity();
|
|
166
|
+
if (identity) return new CompilationCache(resolve(directory), identity);
|
|
167
|
+
} catch {}
|
|
168
|
+
}
|
|
169
|
+
//#endregion
|
|
170
|
+
export { openCompilationCache as i, observationsMatch as n, observe as r, digest as t };
|
|
171
|
+
|
|
172
|
+
//# sourceMappingURL=compilation-cache-7SugqJ4n.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type ConfigurationObservation = {
|
|
2
|
+
path: string;
|
|
3
|
+
kind: 'file' | 'directory';
|
|
4
|
+
value: string;
|
|
5
|
+
};
|
|
6
|
+
export declare const digest: (source: string | Uint8Array) => string;
|
|
7
|
+
export declare function observe(path: string, kind: ConfigurationObservation['kind']): string;
|
|
8
|
+
export declare function observationsMatch(observations: ConfigurationObservation[]): boolean;
|
|
9
|
+
/** Hash actual installed code without evaluating any compiler or dependency module. */
|
|
10
|
+
export declare function toolchainIdentity(location?: string, names?: string[]): string | undefined;
|
|
11
|
+
/** Fixed slots bound completed storage; collisions are misses, never shared module results. */
|
|
12
|
+
export declare class CompilationCache {
|
|
13
|
+
private readonly directory;
|
|
14
|
+
private readonly identity;
|
|
15
|
+
constructor(directory: string, identity: string);
|
|
16
|
+
private key;
|
|
17
|
+
private slot;
|
|
18
|
+
read(url: string, source: string, dialect: boolean): string | undefined;
|
|
19
|
+
write(url: string, source: string, dialect: boolean, emitted: string, observations: ConfigurationObservation[]): void;
|
|
20
|
+
}
|
|
21
|
+
export declare function openCompilationCache(): CompilationCache | undefined;
|