@dbx-tools/projen 0.9.43 → 0.9.46
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 +28 -34
- package/index.ts +6 -7
- package/package.json +9 -10
- package/shims/python-node/bootstrap.ts +129 -0
- package/shims/python-node/child-process.ts +96 -0
- package/shims/python-node/crypto.ts +60 -0
- package/shims/python-node/fs-promises.ts +179 -0
- package/shims/python-node/fs.ts +173 -0
- package/shims/python-node/headers.ts +75 -0
- package/shims/python-node/host.ts +341 -0
- package/shims/python-node/os.ts +20 -0
- package/shims/python-node/process.ts +13 -0
- package/shims/python-node/readline.ts +37 -0
- package/shims/python-node/tsconfig.json +24 -0
- package/src/_lazy-require.ts +7 -25
- package/src/bun-app.ts +2 -2
- package/src/bun-workflow.ts +1 -9
- package/src/codegen.ts +20 -17
- package/src/dbx-tools-config.ts +52 -9
- package/src/generated.ts +4 -17
- package/src/mixin.ts +1 -5
- package/src/packages.ts +18 -15
- package/src/pnpm-workspace.ts +1 -4
- package/src/project-js.ts +65 -157
- package/src/project-predicate.ts +7 -3
- package/src/project-py.ts +120 -43
- package/src/publish.ts +13 -10
- package/src/python-node-bindings.ts +2 -0
- package/src/python-node-bundle.ts +6 -0
- package/src/release-options.ts +17 -17
- package/src/release.ts +30 -9
- package/src/scaffold.ts +4 -7
- package/src/tags.ts +6 -16
- package/src/vscode.ts +1 -1
- package/src/watch.ts +5 -5
- package/src/workspace-lock.ts +1 -0
- package/src/workspace-version.ts +4 -4
- package/tasks/barrels.ts +3 -5
- package/tasks/clean.ts +1 -0
- package/tasks/lib/compile-workspace.ts +41 -19
- package/tasks/lib/local-publish.ts +0 -28
- package/tasks/lib/publish-npm.ts +8 -0
- package/tasks/lib/publish-python.ts +4 -0
- package/tasks/lib/python-node-bindings.ts +77 -21
- package/tasks/lib/python-registry.ts +4 -0
- package/tasks/lib/python-release.ts +2 -0
- package/tasks/lib/release.ts +1 -1
- package/tasks/projenrc.ts +1 -0
- package/tasks/sync.ts +4 -4
- package/src/openapi.ts +0 -293
- package/tasks/local-publish.ts +0 -6
- package/tasks/openapi.ts +0 -35
package/README.md
CHANGED
|
@@ -4,21 +4,20 @@ Projen engine for Bun-first dbx-tools workspaces.
|
|
|
4
4
|
|
|
5
5
|
Import this package from `.projenrc.ts` when a repository should discover
|
|
6
6
|
packages from the filesystem and generate manifests, tsconfigs,
|
|
7
|
-
barrels,
|
|
7
|
+
barrels, codegen outputs, and release tasks.
|
|
8
8
|
|
|
9
9
|
Key features:
|
|
10
10
|
|
|
11
11
|
- Filesystem package discovery: every `src`-bearing folder under configured
|
|
12
12
|
workspace roots becomes a TypeScript package.
|
|
13
13
|
- Tag-driven runtime defaults for shared libraries, Node packages, CLIs,
|
|
14
|
-
servers,
|
|
14
|
+
servers, and React/browser UI packages.
|
|
15
15
|
- Generated package manifests, tsconfigs, package-root barrels, Bun app configs,
|
|
16
16
|
VS Code settings, and a committed `pnpm-workspace.yaml` retained for
|
|
17
17
|
Databricks Apps deployment.
|
|
18
18
|
- Extensible mixin system so repositories can add deps, tasks, or generated
|
|
19
19
|
files based on package predicates.
|
|
20
|
-
-
|
|
21
|
-
`.d.ts` inputs.
|
|
20
|
+
- Zod schema generation from `.d.ts` inputs.
|
|
22
21
|
- Read-only generated-file ownership, cleanup, and watch-loop helpers.
|
|
23
22
|
|
|
24
23
|
## Define A Workspace Root
|
|
@@ -66,8 +65,8 @@ Repository policy stays in the consuming `.projenrc.ts`:
|
|
|
66
65
|
before release artifacts are built and published.
|
|
67
66
|
- `releaseSetupSteps` installs any extra validation/build prerequisites in the
|
|
68
67
|
shared release job before the validation tasks run.
|
|
69
|
-
- `
|
|
70
|
-
|
|
68
|
+
- `releaseSynthesisCommands` runs repository-specific synth owners before the
|
|
69
|
+
root synthesis and clean-diff release check.
|
|
71
70
|
- `extraWorkspaceMembers` declares self-synthesizing tooling packages outside
|
|
72
71
|
`packageRoots`. Their version, generated entrypoint, formatting, linting, and
|
|
73
72
|
workspace membership are derived from that declaration.
|
|
@@ -226,7 +225,7 @@ parameter to `Project`). Drop to `mixin.create(predicate, fn)` +
|
|
|
226
225
|
`project.with(...)` only when you need a predicate the filters cannot express.
|
|
227
226
|
|
|
228
227
|
Built-in tag mixins set runtime defaults for `shared`, `node`, `cli`, `server`,
|
|
229
|
-
|
|
228
|
+
and `ui`. Repo-specific mixins layer package-specific dependencies,
|
|
230
229
|
scripts, and generated files on top.
|
|
231
230
|
|
|
232
231
|
AppKit 0.81's `appkitServerConfig()` is the preferred tsdown preset for a
|
|
@@ -270,12 +269,19 @@ codegen.generateCodegen();
|
|
|
270
269
|
barrels.generateBarrels();
|
|
271
270
|
```
|
|
272
271
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
272
|
+
Add declaration inputs through the owning package's typed config:
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
pkg.dbxToolsConfig.codegenInputs.push("node_modules/example/model.d.ts");
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
`generateCodegen()` reads the serialized `dbxToolsConfig.codegenInputs` and
|
|
279
|
+
writes generated schema modules. They are written read-only, and the root
|
|
280
|
+
ESLint task runs with an explicit separate fix task, so each generated module is
|
|
281
|
+
added to `ignorePatterns` at synth - named individually via
|
|
282
|
+
`codegen.codegenModulePaths()`, never as a blanket `<package>/src/**`. A codegen
|
|
283
|
+
package may hold hand-written modules beside its generated ones, and those must
|
|
284
|
+
stay linted.
|
|
279
285
|
|
|
280
286
|
`generateBarrels()` writes package-root `index.ts` barrels with module
|
|
281
287
|
namespaces, flat unique type exports, and `PACKAGE_IDENTIFIER`, returning the
|
|
@@ -288,17 +294,6 @@ package is attempted even if one fails; the failures are re-thrown together as a
|
|
|
288
294
|
`AggregateError` naming each package, rather than the first one abandoning the
|
|
289
295
|
rest of the sweep.
|
|
290
296
|
|
|
291
|
-
## Generate OpenAPI Clients
|
|
292
|
-
|
|
293
|
-
```ts
|
|
294
|
-
import { openapi } from "@dbx-tools/projen";
|
|
295
|
-
|
|
296
|
-
const packages = await openapi.generateOpenapi();
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
OpenAPI generation scans packages for tsoa controllers, emits `openapi.json`,
|
|
300
|
-
generates TypeScript schemas, and adds an `openapi-fetch` client.
|
|
301
|
-
|
|
302
297
|
## Configure pnpm Catalogs
|
|
303
298
|
|
|
304
299
|
```ts
|
|
@@ -349,7 +344,6 @@ file contract as the CLI.
|
|
|
349
344
|
- `pnpmWorkspace` - generated pnpm workspace file and catalog model.
|
|
350
345
|
- `barrels` / `moduleExports` - public entrypoint generation.
|
|
351
346
|
- `codegen` - `.d.ts` to zod schema generation.
|
|
352
|
-
- `openapi` - tsoa/OpenAPI package generation.
|
|
353
347
|
- `bunApp` / `tsconfig` / `vscode` - generated support files/components.
|
|
354
348
|
- `generated` / `clean` / `watch` / `scaffold` - read-only file ownership,
|
|
355
349
|
cleanup, watchers, and synth orchestration.
|
|
@@ -357,17 +351,17 @@ file contract as the CLI.
|
|
|
357
351
|
- `engineRoot` - engine package root resolution for bootstrapped repos.
|
|
358
352
|
|
|
359
353
|
The engine registers its commands as projen tasks on the workspace root, so run
|
|
360
|
-
them with `bun run <task>` - `sync` (add `--watch`), `barrels`, `
|
|
361
|
-
`clean`.
|
|
354
|
+
them with `bun run <task>` - `sync` (add `--watch`), `barrels`, and `clean`.
|
|
362
355
|
[`@dbx-tools/cli`](../packages/js/cli/dbx-tools) is only needed to
|
|
363
356
|
bootstrap a folder that has no `.projenrc.ts` or toolchain yet.
|
|
364
357
|
|
|
365
358
|
## Run Tasks From The ROOT
|
|
366
359
|
|
|
367
|
-
Every repo-wide task lives on the root. `compile` batches
|
|
368
|
-
|
|
369
|
-
`test` delegates with `bun run --filter '*'`. Both read the
|
|
370
|
-
list, so a new package is covered without a re-synth. Work
|
|
360
|
+
Every repo-wide task lives on the root. `compile` batches package tsconfigs in
|
|
361
|
+
up to four `tsc --build` processes and runs package-owned compile lifecycles
|
|
362
|
+
alongside them; `test` delegates with `bun run --filter '*'`. Both read the
|
|
363
|
+
current workspace list, so a new package is covered without a re-synth. Work
|
|
364
|
+
from the root:
|
|
371
365
|
|
|
372
366
|
| Task | What it does |
|
|
373
367
|
| ----------------------- | --------------------------------------------------- |
|
|
@@ -411,7 +405,7 @@ bun run release --local-registry false --local-pypi auto
|
|
|
411
405
|
```
|
|
412
406
|
|
|
413
407
|
`--install auto` keeps Projen's normal local dependency-install behavior;
|
|
414
|
-
`always` performs a
|
|
408
|
+
`always` performs a manifest-resolved install first, and `never` uses dependencies already
|
|
415
409
|
installed while still regenerating versioned sources. `--no-validation` skips
|
|
416
410
|
optional task checks locally and in CI, not version or immutable-source checks.
|
|
417
411
|
`--no-docs` skips building and deploying the site, not package README validation.
|
|
@@ -424,8 +418,8 @@ invokes, so there is no second place to run the same thing:
|
|
|
424
418
|
|
|
425
419
|
- `compile` / `test` - what the root's `--filter '*'` delegation calls.
|
|
426
420
|
- `prepack` - standalone-publish safety that compiles one package before packing
|
|
427
|
-
|
|
428
|
-
and publishes with lifecycle scripts disabled
|
|
421
|
+
when that package has a compiled publication surface; the workspace release
|
|
422
|
+
driver compiles from the root and publishes with lifecycle scripts disabled.
|
|
429
423
|
- `watch` - a single-package `tsc --build -w`, for narrowing a long
|
|
430
424
|
edit/compile loop to one package.
|
|
431
425
|
- `build` / `package` - a complete compile/test/pack lifecycle when invoked in
|
package/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Hand edits are overwritten on the next watch; this file is read-only.
|
|
4
4
|
|
|
5
5
|
export const PACKAGE_IDENTIFIER = "@dbx-tools/projen";
|
|
6
|
-
export const PACKAGE_VERSION = "0.9.
|
|
6
|
+
export const PACKAGE_VERSION = "0.9.46";
|
|
7
7
|
export * as barrels from "./src/barrels.ts";
|
|
8
8
|
export * as bunApp from "./src/bun-app.ts";
|
|
9
9
|
export * as bunWorkflow from "./src/bun-workflow.ts";
|
|
@@ -14,7 +14,6 @@ export * as engineRoot from "./src/engine-root.ts";
|
|
|
14
14
|
export * as generated from "./src/generated.ts";
|
|
15
15
|
export * as mixin from "./src/mixin.ts";
|
|
16
16
|
export * as moduleExports from "./src/module-exports.ts";
|
|
17
|
-
export * as openapi from "./src/openapi.ts";
|
|
18
17
|
export * as packages from "./src/packages.ts";
|
|
19
18
|
export * as pnpmWorkspace from "./src/pnpm-workspace.ts";
|
|
20
19
|
export * as project from "./src/project.ts";
|
|
@@ -37,8 +36,8 @@ export * as workspaceLock from "./src/workspace-lock.ts";
|
|
|
37
36
|
export * as workspaceVersion from "./src/workspace-version.ts";
|
|
38
37
|
export { BUN_DEV_OVERRIDE, BUN_BUILD_OVERRIDE, BUN_APP_OVERRIDES, RootBunfigFile, BunfigFile, BunDevServerFile, BunBuildFile } from "./src/bun-app.ts";
|
|
39
38
|
export { BUN_VERSION } from "./src/bun-workflow.ts";
|
|
40
|
-
export { DBXToolsConfig } from "./src/dbx-tools-config.ts";
|
|
41
|
-
export type { DBXToolsConfigOptions } from "./src/dbx-tools-config.ts";
|
|
39
|
+
export { DBXToolsConfigDataSchema, DBXToolsConfig } from "./src/dbx-tools-config.ts";
|
|
40
|
+
export type { DBXToolsConfigOptions, DBXToolsConfigData } from "./src/dbx-tools-config.ts";
|
|
42
41
|
export { resolvePkgRoot } from "./src/engine-root.ts";
|
|
43
42
|
export type { HeaderOpts } from "./src/generated.ts";
|
|
44
43
|
export type { ConstructsMixin } from "./src/mixin.ts";
|
|
@@ -48,8 +47,8 @@ export type { RecordedPackage } from "./src/packages.ts";
|
|
|
48
47
|
export { PnpmWorkspaceState } from "./src/pnpm-workspace.ts";
|
|
49
48
|
export type { Catalog, AllowBuilds, DBXToolsPNPMWorkspaceOptions } from "./src/pnpm-workspace.ts";
|
|
50
49
|
export type { DBXToolsProjectLanguage, DBXToolsProjectOptions, DBXToolsProject, ApplyToProjectsOptions } from "./src/project.ts";
|
|
51
|
-
export { PackageIdentifier,
|
|
52
|
-
export type { DBXToolsJavaScriptProject, DBXToolsReleaseMode, DBXToolsJavaScriptProjectOptions, DBXToolsTypeScriptProjectOptions } from "./src/project-js.ts";
|
|
50
|
+
export { PackageIdentifier, LICENSE, DBXToolsNodeProject, ROOT_INSTALL_ONLY_MIXIN, DBXToolsTypeScriptProject } from "./src/project-js.ts";
|
|
51
|
+
export type { DBXToolsJavaScriptProject, DBXToolsReleaseMode, DiscoveredTypeScriptPackageOptions, DBXToolsJavaScriptProjectOptions, DBXToolsTypeScriptProjectOptions } from "./src/project-js.ts";
|
|
53
52
|
export { DBXToolsPythonProject, DBXToolsPythonWorkspace } from "./src/project-py.ts";
|
|
54
53
|
export type { PythonRepositoryOptions, PythonPackageOptions, DBXToolsPythonProjectOptions, PythonReleaseOptions, DBXToolsPythonWorkspaceOptions } from "./src/project-py.ts";
|
|
55
54
|
export { PROJEN_VERSION } from "./src/projen-version.ts";
|
|
@@ -61,7 +60,7 @@ export type { PythonNodeFunctionOverride, PythonNodeBindingsOptions, PythonNodeB
|
|
|
61
60
|
export { DBXToolsRelease } from "./src/release.ts";
|
|
62
61
|
export type { ReleaseDocsOptions, DBXToolsReleaseOptions } from "./src/release.ts";
|
|
63
62
|
export { RELEASE_VERSION } from "./src/release-context.ts";
|
|
64
|
-
export { RELEASE_PUBLISH_TARGETS, RELEASE_INSTALL_MODES } from "./src/release-options.ts";
|
|
63
|
+
export { RELEASE_PUBLISH_TARGETS, RELEASE_INSTALL_MODES, ReleaseStepSelectionSchema } from "./src/release-options.ts";
|
|
65
64
|
export type { ReleasePublishTarget, ReleaseInstallMode, ReleaseStepSelection, ReleaseSelectionOptions } from "./src/release-options.ts";
|
|
66
65
|
export { AGNOSTIC_COMPILER_OPTIONS, PACKAGE_TAG_MIXINS } from "./src/tags.ts";
|
|
67
66
|
export type { PackageTag } from "./src/tags.ts";
|
package/package.json
CHANGED
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
"post-upgrade": "projen post-upgrade",
|
|
17
17
|
"pre-compile": "projen pre-compile",
|
|
18
18
|
"test": "projen test",
|
|
19
|
-
"test:external-consumer": "projen test:external-consumer",
|
|
20
19
|
"upgrade": "projen upgrade",
|
|
21
20
|
"watch": "projen watch",
|
|
22
21
|
"projen": "projen"
|
|
@@ -24,29 +23,28 @@
|
|
|
24
23
|
"devDependencies": {
|
|
25
24
|
"@types/node": "^24.6.0",
|
|
26
25
|
"@types/semver": "^7.7.1",
|
|
27
|
-
"projen": "0.103.27"
|
|
28
|
-
"tsoa": "^6.6.0"
|
|
26
|
+
"projen": "0.103.27"
|
|
29
27
|
},
|
|
30
28
|
"peerDependencies": {
|
|
31
29
|
"projen": "^0.103.27"
|
|
32
30
|
},
|
|
33
31
|
"dependencies": {
|
|
34
32
|
"@clack/prompts": "^1.7.0",
|
|
35
|
-
"@dbx-tools/core": "^0.9.
|
|
36
|
-
"@dbx-tools/path": "^0.9.
|
|
37
|
-
"@dbx-tools/shared-core": "^0.9.
|
|
33
|
+
"@dbx-tools/core": "^0.9.46",
|
|
34
|
+
"@dbx-tools/path": "^0.9.46",
|
|
35
|
+
"@dbx-tools/shared-core": "^0.9.46",
|
|
38
36
|
"commander": "^15.0.0",
|
|
39
37
|
"concurrently": "^10.0.3",
|
|
40
38
|
"constructs": "^10.6.0",
|
|
41
39
|
"is-identifier": "^1",
|
|
42
40
|
"node-stdlib-browser": "^1.3.1",
|
|
43
|
-
"openapi-typescript": "^7.13.0",
|
|
44
41
|
"oxc-parser": "^0.90.0",
|
|
45
42
|
"semver": "^7.7.3",
|
|
46
43
|
"smol-toml": "1.8.0",
|
|
47
44
|
"ts-to-zod": "^5.1.0",
|
|
48
45
|
"typescript": "^5.9.3",
|
|
49
|
-
"yaml": "^2.9.0"
|
|
46
|
+
"yaml": "^2.9.0",
|
|
47
|
+
"zod": "^4.3.6"
|
|
50
48
|
},
|
|
51
49
|
"devEngines": {
|
|
52
50
|
"packageManager": {
|
|
@@ -56,7 +54,7 @@
|
|
|
56
54
|
},
|
|
57
55
|
"main": "index.ts",
|
|
58
56
|
"license": "Apache-2.0",
|
|
59
|
-
"version": "0.9.
|
|
57
|
+
"version": "0.9.46",
|
|
60
58
|
"types": "index.ts",
|
|
61
59
|
"type": "module",
|
|
62
60
|
"exports": {
|
|
@@ -67,7 +65,8 @@
|
|
|
67
65
|
"files": [
|
|
68
66
|
"index.ts",
|
|
69
67
|
"src",
|
|
70
|
-
"tasks"
|
|
68
|
+
"tasks",
|
|
69
|
+
"shims/python-node"
|
|
71
70
|
],
|
|
72
71
|
"//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"bunx projen\"."
|
|
73
72
|
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
|
|
3
|
+
import { PythonHeaders } from "./headers.ts";
|
|
4
|
+
import { installPythonGlobals, pythonHost } from "./host.ts";
|
|
5
|
+
|
|
6
|
+
installPythonGlobals();
|
|
7
|
+
|
|
8
|
+
(globalThis as typeof globalThis & { Buffer?: typeof Buffer }).Buffer = Buffer;
|
|
9
|
+
|
|
10
|
+
const globals = globalThis as typeof globalThis & {
|
|
11
|
+
AbortController?: typeof AbortController;
|
|
12
|
+
Headers?: typeof Headers;
|
|
13
|
+
Request?: typeof Request;
|
|
14
|
+
Response?: typeof Response;
|
|
15
|
+
structuredClone?: typeof structuredClone;
|
|
16
|
+
TextDecoder?: typeof TextDecoder;
|
|
17
|
+
TextEncoder?: typeof TextEncoder;
|
|
18
|
+
fetch?: typeof fetch;
|
|
19
|
+
};
|
|
20
|
+
globals.AbortController ??= class AbortController {
|
|
21
|
+
readonly signal: AbortSignal;
|
|
22
|
+
readonly #listeners = new Set<() => void>();
|
|
23
|
+
|
|
24
|
+
constructor() {
|
|
25
|
+
this.signal = {
|
|
26
|
+
aborted: false,
|
|
27
|
+
addEventListener: (name: string, listener: EventListenerOrEventListenerObject) => {
|
|
28
|
+
if (name === "abort") {
|
|
29
|
+
this.#listeners.add(
|
|
30
|
+
typeof listener === "function"
|
|
31
|
+
? () => listener(new Event("abort"))
|
|
32
|
+
: () => listener.handleEvent(new Event("abort")),
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
removeEventListener: () => {},
|
|
37
|
+
} as AbortSignal;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
abort(): void {
|
|
41
|
+
if (this.signal.aborted) return;
|
|
42
|
+
Object.defineProperty(this.signal, "aborted", { value: true });
|
|
43
|
+
for (const listener of this.#listeners) listener();
|
|
44
|
+
this.#listeners.clear();
|
|
45
|
+
}
|
|
46
|
+
} as typeof AbortController;
|
|
47
|
+
|
|
48
|
+
globals.Headers ??= PythonHeaders as unknown as typeof Headers;
|
|
49
|
+
|
|
50
|
+
class PythonResponse {
|
|
51
|
+
readonly headers: Headers;
|
|
52
|
+
readonly ok: boolean;
|
|
53
|
+
readonly status: number;
|
|
54
|
+
readonly #body: Uint8Array;
|
|
55
|
+
|
|
56
|
+
constructor(body: Uint8Array, init: ResponseInit = {}) {
|
|
57
|
+
this.#body = body;
|
|
58
|
+
this.status = init.status ?? 200;
|
|
59
|
+
this.ok = this.status >= 200 && this.status < 300;
|
|
60
|
+
this.headers = new Headers(init.headers);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async arrayBuffer(): Promise<ArrayBuffer> {
|
|
64
|
+
return this.#body.slice().buffer;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async text(): Promise<string> {
|
|
68
|
+
return new TextDecoder().decode(this.#body);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async json(): Promise<unknown> {
|
|
72
|
+
return JSON.parse(await this.text());
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
globals.Response ??= PythonResponse as unknown as typeof Response;
|
|
77
|
+
globals.structuredClone ??= ((value: unknown) => cloneStructured(value)) as typeof structuredClone;
|
|
78
|
+
globals.TextEncoder ??= class TextEncoder {
|
|
79
|
+
encode(value: string): Uint8Array {
|
|
80
|
+
const encoded = unescape(encodeURIComponent(String(value)));
|
|
81
|
+
return Uint8Array.from(encoded, (character) => character.charCodeAt(0));
|
|
82
|
+
}
|
|
83
|
+
} as typeof TextEncoder;
|
|
84
|
+
globals.TextDecoder ??= class TextDecoder {
|
|
85
|
+
decode(value?: ArrayBuffer | ArrayBufferView): string {
|
|
86
|
+
const bytes =
|
|
87
|
+
value instanceof ArrayBuffer
|
|
88
|
+
? new Uint8Array(value)
|
|
89
|
+
: value
|
|
90
|
+
? new Uint8Array(value.buffer, value.byteOffset, value.byteLength)
|
|
91
|
+
: new Uint8Array();
|
|
92
|
+
const encoded = Array.from(bytes, (byte) => String.fromCharCode(byte)).join("");
|
|
93
|
+
return decodeURIComponent(escape(encoded));
|
|
94
|
+
}
|
|
95
|
+
} as typeof TextDecoder;
|
|
96
|
+
globals.fetch ??= (async (input: string | URL | Request, init: RequestInit = {}) => {
|
|
97
|
+
const request = typeof input === "object" && "url" in input ? input : undefined;
|
|
98
|
+
const headers = init.headers ?? request?.headers;
|
|
99
|
+
const response = await pythonHost().http.fetch(
|
|
100
|
+
request?.url ?? String(input),
|
|
101
|
+
init.method ?? request?.method,
|
|
102
|
+
headers ? Object.fromEntries(new Headers(headers).entries()) : undefined,
|
|
103
|
+
typeof init.body === "string" ? init.body : undefined,
|
|
104
|
+
);
|
|
105
|
+
return new Response(Uint8Array.from(response.body), {
|
|
106
|
+
status: response.status,
|
|
107
|
+
headers: response.headers,
|
|
108
|
+
});
|
|
109
|
+
}) as typeof fetch;
|
|
110
|
+
|
|
111
|
+
function cloneStructured(value: unknown, seen = new Map<object, unknown>()): unknown {
|
|
112
|
+
if (value === null || typeof value !== "object") return value;
|
|
113
|
+
const cached = seen.get(value);
|
|
114
|
+
if (cached !== undefined) return cached;
|
|
115
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
116
|
+
if (Array.isArray(value)) {
|
|
117
|
+
const clone: unknown[] = [];
|
|
118
|
+
seen.set(value, clone);
|
|
119
|
+
for (const item of value) clone.push(cloneStructured(item, seen));
|
|
120
|
+
return clone;
|
|
121
|
+
}
|
|
122
|
+
if (Object.getPrototypeOf(value) === Object.prototype) {
|
|
123
|
+
const clone: Record<string, unknown> = {};
|
|
124
|
+
seen.set(value, clone);
|
|
125
|
+
for (const [key, item] of Object.entries(value)) clone[key] = cloneStructured(item, seen);
|
|
126
|
+
return clone;
|
|
127
|
+
}
|
|
128
|
+
throw new TypeError(`PythonMonkey structuredClone does not support ${value.constructor.name}`);
|
|
129
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { Readable, Writable } from "node:stream";
|
|
3
|
+
|
|
4
|
+
import { pythonHost, type PythonProcessHandle } from "./host.ts";
|
|
5
|
+
|
|
6
|
+
interface SpawnOptions {
|
|
7
|
+
env?: Record<string, string>;
|
|
8
|
+
signal?: AbortSignal;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class PythonChildProcess extends EventEmitter {
|
|
12
|
+
readonly pid = undefined;
|
|
13
|
+
readonly stdin = new Writable({ write: (_chunk, _encoding, callback) => callback() });
|
|
14
|
+
readonly stdout = new Readable({ read() {} });
|
|
15
|
+
readonly stderr = new Readable({ read() {} });
|
|
16
|
+
private active = true;
|
|
17
|
+
private readonly handle: PythonProcessHandle;
|
|
18
|
+
|
|
19
|
+
constructor(command: string, args: string[], options: SpawnOptions) {
|
|
20
|
+
super();
|
|
21
|
+
this.handle = pythonHost().process.start(command, args, options.env, undefined);
|
|
22
|
+
options.signal?.addEventListener("abort", () => {
|
|
23
|
+
if (!this.active) return;
|
|
24
|
+
this.active = false;
|
|
25
|
+
// Actually terminate the Python child so an aborted call leaks nothing.
|
|
26
|
+
this.handle.kill();
|
|
27
|
+
this.stdout.push(null);
|
|
28
|
+
this.stderr.push(null);
|
|
29
|
+
this.emit(
|
|
30
|
+
"error",
|
|
31
|
+
Object.assign(new Error("Process execution was aborted"), { code: "ABORT_ERR" }),
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
void this.handle.wait().then(
|
|
35
|
+
(result) => {
|
|
36
|
+
if (!this.active) return;
|
|
37
|
+
this.active = false;
|
|
38
|
+
if (result.stdout) this.stdout.push(result.stdout);
|
|
39
|
+
if (result.stderr) this.stderr.push(result.stderr);
|
|
40
|
+
this.stdout.push(null);
|
|
41
|
+
this.stderr.push(null);
|
|
42
|
+
this.emit("close", result.exitCode);
|
|
43
|
+
this.emit("exit", result.exitCode);
|
|
44
|
+
},
|
|
45
|
+
(error) => {
|
|
46
|
+
if (!this.active) return;
|
|
47
|
+
this.active = false;
|
|
48
|
+
this.stdout.push(null);
|
|
49
|
+
this.stderr.push(null);
|
|
50
|
+
this.emit("error", error);
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
kill(): boolean {
|
|
56
|
+
this.handle.kill();
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function spawn(command: string, args: string[] = [], options: SpawnOptions = {}) {
|
|
62
|
+
return new PythonChildProcess(command, args, options);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function execFile(
|
|
66
|
+
command: string,
|
|
67
|
+
args: string[],
|
|
68
|
+
_options: object,
|
|
69
|
+
callback: (error: Error | null, result?: { stdout: string; stderr: string }) => void,
|
|
70
|
+
): void {
|
|
71
|
+
void pythonHost()
|
|
72
|
+
.process.start(command, args, undefined, undefined)
|
|
73
|
+
.wait()
|
|
74
|
+
.then(
|
|
75
|
+
(result) => {
|
|
76
|
+
if (result.exitCode === 0) {
|
|
77
|
+
callback(null, { stdout: result.stdout ?? "", stderr: result.stderr ?? "" });
|
|
78
|
+
} else {
|
|
79
|
+
callback(
|
|
80
|
+
Object.assign(new Error(result.stderr || `Command exited ${result.exitCode}`), {
|
|
81
|
+
code: result.exitCode,
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
(error) => callback(error instanceof Error ? error : new Error(String(error))),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function spawnSync(): never {
|
|
91
|
+
throw Object.assign(new Error("Synchronous subprocesses are unavailable in PythonMonkey"), {
|
|
92
|
+
code: "ENOSYS",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export default { execFile, spawn, spawnSync };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { pythonHost } from "./host.ts";
|
|
2
|
+
|
|
3
|
+
class PythonHash {
|
|
4
|
+
private readonly content: number[] = [];
|
|
5
|
+
|
|
6
|
+
constructor(private readonly algorithm: string) {}
|
|
7
|
+
|
|
8
|
+
update(value: string | ArrayBuffer | ArrayBufferView): this {
|
|
9
|
+
const bytes =
|
|
10
|
+
typeof value === "string"
|
|
11
|
+
? new TextEncoder().encode(value)
|
|
12
|
+
: value instanceof ArrayBuffer
|
|
13
|
+
? new Uint8Array(value)
|
|
14
|
+
: new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
15
|
+
this.content.push(...bytes);
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
digest(encoding?: "hex" | "base64"): string | Uint8Array {
|
|
20
|
+
const digest = pythonHost().crypto.digest(this.algorithm, this.content, encoding);
|
|
21
|
+
if (typeof digest === "string") return digest;
|
|
22
|
+
const bytes = Uint8Array.from(digest) as Uint8Array & {
|
|
23
|
+
readBigInt64BE(offset: number): bigint;
|
|
24
|
+
};
|
|
25
|
+
bytes.readBigInt64BE = (offset) => {
|
|
26
|
+
let value = 0n;
|
|
27
|
+
for (const byte of bytes.slice(offset, offset + 8)) {
|
|
28
|
+
value = (value << 8n) | BigInt(byte);
|
|
29
|
+
}
|
|
30
|
+
return BigInt.asIntN(64, value);
|
|
31
|
+
};
|
|
32
|
+
return bytes;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function createHash(algorithm: string): PythonHash {
|
|
37
|
+
const normalized = algorithm.toLowerCase();
|
|
38
|
+
if (!getHashes().includes(normalized)) {
|
|
39
|
+
throw new Error(`Unsupported hash algorithm: ${algorithm}`);
|
|
40
|
+
}
|
|
41
|
+
return new PythonHash(normalized);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getHashes(): string[] {
|
|
45
|
+
return pythonHost().crypto.hashes();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function randomBytes(length: number): Uint8Array {
|
|
49
|
+
return Uint8Array.from(pythonHost().crypto.randomBytes(length));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function randomUUID(): string {
|
|
53
|
+
const bytes = randomBytes(16);
|
|
54
|
+
bytes[6] = (bytes[6]! & 0x0f) | 0x40;
|
|
55
|
+
bytes[8] = (bytes[8]! & 0x3f) | 0x80;
|
|
56
|
+
const hex = Array.from(bytes, (value) => value.toString(16).padStart(2, "0")).join("");
|
|
57
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default { createHash, getHashes, randomBytes, randomUUID };
|