@elaraai/create-e3 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +51 -0
- package/dist/index.js +176 -0
- package/package.json +32 -0
- package/templates/e3/.nvmrc +1 -0
- package/templates/e3/.python-version +1 -0
- package/templates/e3/README.md +26 -0
- package/templates/e3/eslint.config.js +15 -0
- package/templates/e3/gitignore +7 -0
- package/templates/e3/package.json +39 -0
- package/templates/e3/pyproject.toml +14 -0
- package/templates/e3/src/index.spec.ts +15 -0
- package/templates/e3/src/index.ts +14 -0
- package/templates/e3/tests/test_unit.py +90 -0
- package/templates/e3/tsconfig.json +24 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Copyright (c) 2025 Elara AI Pty Ltd
|
|
2
|
+
|
|
3
|
+
# East Workspace — Multi-License Repository
|
|
4
|
+
|
|
5
|
+
This repository contains multiple components released under different
|
|
6
|
+
licenses. Each component directory under `libs/` that contains its own
|
|
7
|
+
`LICENSE.md` is governed **exclusively** by that file, which takes
|
|
8
|
+
precedence over this one. This file licenses everything not otherwise
|
|
9
|
+
covered, and summarizes the per-component split.
|
|
10
|
+
|
|
11
|
+
## License by component
|
|
12
|
+
|
|
13
|
+
- **Dual AGPL-3.0 / Commercial** — `east`, `east-node`, `east-ui`, and the
|
|
14
|
+
e3 SDK (`@elaraai/e3`, `@elaraai/e3-types`).
|
|
15
|
+
- **Business Source License 1.1** — `east-c`, `east-py` (runtime, std, io,
|
|
16
|
+
cli), and the e3 server stack (`@elaraai/e3-core`, `@elaraai/e3-cli`,
|
|
17
|
+
`@elaraai/e3-api-client`, `@elaraai/e3-api-server`, `@elaraai/e3-api-tests`).
|
|
18
|
+
- **Hybrid (TypeScript AGPL-3.0 / Python BSL 1.1)** — `east-py-datascience`.
|
|
19
|
+
|
|
20
|
+
For the full terms of any component, see its `libs/<name>/LICENSE.md` and
|
|
21
|
+
the per-package `LICENSE.md`.
|
|
22
|
+
|
|
23
|
+
## Repository-level files
|
|
24
|
+
|
|
25
|
+
All files that are **not** part of a component carrying its own
|
|
26
|
+
`LICENSE.md` — including the build tooling, `scripts/`, `docs/`,
|
|
27
|
+
`.github/`, the `Makefile`, and top-level configuration — are licensed
|
|
28
|
+
under the GNU Affero General Public License v3.0 or later
|
|
29
|
+
(AGPL-3.0-or-later).
|
|
30
|
+
|
|
31
|
+
Full text: https://www.gnu.org/licenses/agpl-3.0.html
|
|
32
|
+
|
|
33
|
+
## Commercial licensing
|
|
34
|
+
|
|
35
|
+
To use any dual-licensed component without the source-disclosure
|
|
36
|
+
requirements of AGPL-3.0, or to discuss licensing for Business Source
|
|
37
|
+
License components, contact Elara AI Pty Ltd at support@elara.ai.
|
|
38
|
+
|
|
39
|
+
## Contributions
|
|
40
|
+
|
|
41
|
+
By submitting a contribution you agree to license it under the license(s)
|
|
42
|
+
applicable to the component you are contributing to. See
|
|
43
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) and [CLA.md](CLA.md).
|
|
44
|
+
|
|
45
|
+
## Governing Law
|
|
46
|
+
|
|
47
|
+
This file is governed by the laws of New South Wales, Australia.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
*Elara AI Pty Ltd*
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ../scaffold-core/dist/scaffold.js
|
|
4
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { join, relative } from "node:path";
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
|
|
8
|
+
// ../scaffold-core/dist/names.js
|
|
9
|
+
import { basename } from "node:path";
|
|
10
|
+
function deriveNames(rawName, cwd) {
|
|
11
|
+
const source = rawName === "." ? basename(cwd) : rawName;
|
|
12
|
+
const projectName = source.trim().toLowerCase().replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
13
|
+
if (projectName === "") {
|
|
14
|
+
throw new Error(`Could not derive a valid project name from "${rawName}"`);
|
|
15
|
+
}
|
|
16
|
+
const displayName = projectName.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
17
|
+
const workspaceName = projectName.replace(/-/g, "_");
|
|
18
|
+
return { projectName, displayName, workspaceName };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ../scaffold-core/dist/scaffold.js
|
|
22
|
+
var TEXT_REPLACE_EXT = /* @__PURE__ */ new Set([
|
|
23
|
+
".ts",
|
|
24
|
+
".tsx",
|
|
25
|
+
".js",
|
|
26
|
+
".mjs",
|
|
27
|
+
".json",
|
|
28
|
+
".toml",
|
|
29
|
+
".md",
|
|
30
|
+
".txt",
|
|
31
|
+
".yml",
|
|
32
|
+
".yaml",
|
|
33
|
+
".py",
|
|
34
|
+
".nvmrc",
|
|
35
|
+
".python-version",
|
|
36
|
+
""
|
|
37
|
+
]);
|
|
38
|
+
var DOTFILE_RENAMES = {
|
|
39
|
+
gitignore: ".gitignore",
|
|
40
|
+
npmrc: ".npmrc"
|
|
41
|
+
};
|
|
42
|
+
function substituteTokens(content, names) {
|
|
43
|
+
return content.replaceAll("__PROJECT_NAME__", names.projectName).replaceAll("__DISPLAY_NAME__", names.displayName).replaceAll("__WORKSPACE_NAME__", names.workspaceName);
|
|
44
|
+
}
|
|
45
|
+
function transformPackageJson(raw, names, version) {
|
|
46
|
+
const pkg = JSON.parse(raw);
|
|
47
|
+
pkg.name = `@elaraai/${names.projectName}`;
|
|
48
|
+
pkg.description = names.displayName;
|
|
49
|
+
pkg.version = "0.0.1";
|
|
50
|
+
delete pkg.private;
|
|
51
|
+
const pin = `^${version}`;
|
|
52
|
+
for (const field of ["dependencies", "devDependencies", "peerDependencies"]) {
|
|
53
|
+
const deps = pkg[field];
|
|
54
|
+
if (!deps)
|
|
55
|
+
continue;
|
|
56
|
+
for (const [name, spec] of Object.entries(deps)) {
|
|
57
|
+
if (spec.startsWith("workspace:"))
|
|
58
|
+
deps[name] = pin;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return `${JSON.stringify(pkg, null, 2)}
|
|
62
|
+
`;
|
|
63
|
+
}
|
|
64
|
+
function walk(dir) {
|
|
65
|
+
const out = [];
|
|
66
|
+
for (const entry of readdirSync(dir)) {
|
|
67
|
+
const full = join(dir, entry);
|
|
68
|
+
if (statSync(full).isDirectory())
|
|
69
|
+
out.push(...walk(full));
|
|
70
|
+
else
|
|
71
|
+
out.push(full);
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
function extOf(path) {
|
|
76
|
+
const base = path.slice(path.lastIndexOf("/") + 1);
|
|
77
|
+
const dot = base.lastIndexOf(".");
|
|
78
|
+
return dot <= 0 ? "" : base.slice(dot);
|
|
79
|
+
}
|
|
80
|
+
function scaffold(options) {
|
|
81
|
+
const { kind, name, templateDir, version } = options;
|
|
82
|
+
const cwd = options.cwd ?? process.cwd();
|
|
83
|
+
const log = options.log ?? ((m) => console.log(m));
|
|
84
|
+
if (!existsSync(templateDir)) {
|
|
85
|
+
throw new Error(`Template directory not found: ${templateDir}`);
|
|
86
|
+
}
|
|
87
|
+
const names = deriveNames(name, cwd);
|
|
88
|
+
const inPlace = name === ".";
|
|
89
|
+
const projectDir = inPlace ? cwd : join(cwd, names.projectName);
|
|
90
|
+
if (!inPlace && existsSync(projectDir)) {
|
|
91
|
+
throw new Error(`Directory already exists: ${projectDir}`);
|
|
92
|
+
}
|
|
93
|
+
mkdirSync(projectDir, { recursive: true });
|
|
94
|
+
for (const srcPath of walk(templateDir)) {
|
|
95
|
+
const rel = relative(templateDir, srcPath);
|
|
96
|
+
const segments = rel.split(/[/\\]/);
|
|
97
|
+
const last = segments[segments.length - 1];
|
|
98
|
+
segments[segments.length - 1] = DOTFILE_RENAMES[last] ?? last;
|
|
99
|
+
const destRel = segments.join("/");
|
|
100
|
+
const destPath = join(projectDir, destRel);
|
|
101
|
+
mkdirSync(join(destPath, ".."), { recursive: true });
|
|
102
|
+
const baseName = segments[segments.length - 1];
|
|
103
|
+
if (baseName === "package.json") {
|
|
104
|
+
const manifest = transformPackageJson(readFileSync(srcPath, "utf8"), names, version);
|
|
105
|
+
writeFileSync(destPath, substituteTokens(manifest, names));
|
|
106
|
+
} else if (TEXT_REPLACE_EXT.has(extOf(srcPath)) || baseName.startsWith(".")) {
|
|
107
|
+
writeFileSync(destPath, substituteTokens(readFileSync(srcPath, "utf8"), names));
|
|
108
|
+
} else {
|
|
109
|
+
writeFileSync(destPath, readFileSync(srcPath));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
log(`Created ${names.projectName} (${kind}) at ${projectDir}`);
|
|
113
|
+
if (options.install) {
|
|
114
|
+
runInstall(kind, projectDir, log);
|
|
115
|
+
}
|
|
116
|
+
return { ...names, projectDir, inPlace };
|
|
117
|
+
}
|
|
118
|
+
function hasCommand(cmd) {
|
|
119
|
+
const probe = process.platform === "win32" ? "where" : "which";
|
|
120
|
+
return spawnSync(probe, [cmd], { stdio: "ignore" }).status === 0;
|
|
121
|
+
}
|
|
122
|
+
function runInstall(kind, projectDir, log) {
|
|
123
|
+
log("Installing Node dependencies (npm install)...");
|
|
124
|
+
const npm = spawnSync("npm", ["install"], { cwd: projectDir, stdio: "inherit", shell: process.platform === "win32" });
|
|
125
|
+
if (npm.status !== 0) {
|
|
126
|
+
log("npm install failed \u2014 fix the issue and re-run `npm install`.");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (kind === "e3") {
|
|
130
|
+
if (hasCommand("uv")) {
|
|
131
|
+
log("Installing Python dependencies (uv sync)...");
|
|
132
|
+
const uv = spawnSync("uv", ["sync"], { cwd: projectDir, stdio: "inherit", shell: process.platform === "win32" });
|
|
133
|
+
if (uv.status !== 0)
|
|
134
|
+
log("uv sync failed \u2014 fix the issue and re-run `uv sync`.");
|
|
135
|
+
} else {
|
|
136
|
+
log("uv not found \u2014 install it (https://docs.astral.sh/uv/) then run `uv sync` to set up Python.");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ../scaffold-core/dist/cli.js
|
|
142
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
143
|
+
import { dirname, join as join2 } from "node:path";
|
|
144
|
+
import { fileURLToPath } from "node:url";
|
|
145
|
+
function runCreateCli(kind, moduleUrl) {
|
|
146
|
+
const pkgRoot = join2(dirname(fileURLToPath(moduleUrl)), "..");
|
|
147
|
+
const args = process.argv.slice(2);
|
|
148
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
149
|
+
console.log(`Usage: npm create @elaraai/${kind} <project-name> [-- --install|--no-install]`);
|
|
150
|
+
console.log(` create-${kind} <project-name|.> [--install|--no-install]`);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const version = JSON.parse(readFileSync2(join2(pkgRoot, "package.json"), "utf8")).version;
|
|
154
|
+
const templateDir = join2(pkgRoot, "templates", kind);
|
|
155
|
+
const name = args.find((a) => !a.startsWith("-")) ?? ".";
|
|
156
|
+
const install = args.includes("--install") ? true : args.includes("--no-install") ? false : Boolean(process.stdout.isTTY);
|
|
157
|
+
try {
|
|
158
|
+
const result = scaffold({ kind, name, templateDir, version, install });
|
|
159
|
+
printNextSteps(kind, result.projectName, result.inPlace, install);
|
|
160
|
+
} catch (err) {
|
|
161
|
+
console.error(`error: ${err instanceof Error ? err.message : String(err)}`);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function printNextSteps(kind, projectName, inPlace, installed) {
|
|
166
|
+
console.log("");
|
|
167
|
+
console.log("Next steps:");
|
|
168
|
+
if (!inPlace)
|
|
169
|
+
console.log(` cd ${projectName}`);
|
|
170
|
+
if (!installed)
|
|
171
|
+
console.log(kind === "e3" ? " npm run setup" : " npm install");
|
|
172
|
+
console.log(kind === "e3" ? " npm run start" : " npm run test");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/index.ts
|
|
176
|
+
runCreateCli("e3", import.meta.url);
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elaraai/create-e3",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Scaffold a new e3 project (BSL-1.1, Node + Python, durable execution): npm create @elaraai/e3",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-e3": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"templates"
|
|
12
|
+
],
|
|
13
|
+
"license": "BSL-1.1",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/elaraai/east-workspace.git",
|
|
17
|
+
"directory": "libs/create/packages/create-e3"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=22"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^22",
|
|
24
|
+
"esbuild": "^0.24",
|
|
25
|
+
"typescript": "^5",
|
|
26
|
+
"@elaraai/scaffold-core": "1.0.0"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "node build.mjs",
|
|
30
|
+
"test": "node --test \"dist/**/*.spec.js\""
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
22
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# __DISPLAY_NAME__
|
|
2
|
+
|
|
3
|
+
e3 project (BSL-1.1) — Node + Python, durable dataflow execution.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run setup # npm install + uv sync (Node and Python deps)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Commands
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm run build # compile TypeScript
|
|
15
|
+
npm run test # build, export IR, run TS + Python tests
|
|
16
|
+
npm run test:ts # TypeScript tests only
|
|
17
|
+
npm run test:py # Python tests only (needs IR exported first)
|
|
18
|
+
npm run deploy # create repo (if needed) + deploy from ./src/index.ts
|
|
19
|
+
npm run start # deploy, then run the dataflow once
|
|
20
|
+
npm run watch # auto-deploy + run on every save
|
|
21
|
+
npm run lint # lint sources
|
|
22
|
+
npm run clean # remove build output, venv, repo, dependencies
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The package is defined in `src/index.ts` as the default export; `npm run
|
|
26
|
+
deploy`/`start`/`watch` deploy it straight from source via the e3 CLI.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import tseslint from "@typescript-eslint/eslint-plugin";
|
|
2
|
+
import tsparser from "@typescript-eslint/parser";
|
|
3
|
+
|
|
4
|
+
export default [
|
|
5
|
+
{ ignores: ["dist/", "node_modules/", ".venv/"] },
|
|
6
|
+
{
|
|
7
|
+
files: ["src/**/*.ts"],
|
|
8
|
+
languageOptions: {
|
|
9
|
+
parser: tsparser,
|
|
10
|
+
parserOptions: { ecmaVersion: "latest", sourceType: "module" },
|
|
11
|
+
},
|
|
12
|
+
plugins: { "@typescript-eslint": tseslint },
|
|
13
|
+
rules: {},
|
|
14
|
+
},
|
|
15
|
+
];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elaraai/template-e3",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "__DISPLAY_NAME__",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"test:ts": "node --enable-source-maps --test \"dist/**/*.spec.js\"",
|
|
10
|
+
"test:export": "cross-env EXPORT_TEST_IR=dist/test-ir node --enable-source-maps --test \"dist/**/*.spec.js\"",
|
|
11
|
+
"test:py": "uv run pytest -v",
|
|
12
|
+
"test": "npm run build && npm run test:export && npm run test:py",
|
|
13
|
+
"setup": "npm install && uv sync",
|
|
14
|
+
"deploy": "e3 repo create .repos --exist-ok && e3 workspace deploy .repos __WORKSPACE_NAME__ --from-source ./src/index.ts",
|
|
15
|
+
"start": "npm run deploy && e3 dataflow run .repos __WORKSPACE_NAME__",
|
|
16
|
+
"watch": "e3 watch ./src/index.ts .repos __WORKSPACE_NAME__ --start",
|
|
17
|
+
"lint": "eslint .",
|
|
18
|
+
"clean": "rimraf dist node_modules .venv uv.lock .repos *.tsbuildinfo"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@elaraai/east": "workspace:*",
|
|
22
|
+
"@elaraai/east-node-std": "workspace:*",
|
|
23
|
+
"@elaraai/east-node-io": "workspace:*",
|
|
24
|
+
"@elaraai/east-py-datascience": "workspace:*",
|
|
25
|
+
"@elaraai/e3": "workspace:*",
|
|
26
|
+
"@elaraai/e3-types": "workspace:*"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@elaraai/e3-cli": "workspace:*",
|
|
30
|
+
"@types/node": "^22",
|
|
31
|
+
"typescript": "^5",
|
|
32
|
+
"eslint": "^9",
|
|
33
|
+
"@typescript-eslint/eslint-plugin": "^8",
|
|
34
|
+
"@typescript-eslint/parser": "^8",
|
|
35
|
+
"cross-env": "^7",
|
|
36
|
+
"rimraf": "^6"
|
|
37
|
+
},
|
|
38
|
+
"engines": { "node": ">=22" }
|
|
39
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "__PROJECT_NAME__"
|
|
3
|
+
description = "__DISPLAY_NAME__"
|
|
4
|
+
requires-python = ">=3.11"
|
|
5
|
+
version = "0.1.0"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"elaraai-east-py",
|
|
8
|
+
"elaraai-east-py-std",
|
|
9
|
+
"elaraai-east-py-io",
|
|
10
|
+
"elaraai-east-py-datascience",
|
|
11
|
+
"elaraai-east-py-cli",
|
|
12
|
+
"pytest",
|
|
13
|
+
"pytest-subtests",
|
|
14
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { East } from "@elaraai/east";
|
|
2
|
+
import { describeEast, Assert } from "@elaraai/east-node-std";
|
|
3
|
+
import { greetFn } from "./index.js";
|
|
4
|
+
|
|
5
|
+
describeEast("__DISPLAY_NAME__", (test) => {
|
|
6
|
+
test("greet returns greeting message", ($) => {
|
|
7
|
+
const result = $.let(greetFn("World"));
|
|
8
|
+
$(Assert.equal(result, East.value("Hello, World!")));
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("greet with custom name", ($) => {
|
|
12
|
+
const result = $.let(greetFn("East"));
|
|
13
|
+
$(Assert.equal(result, East.value("Hello, East!")));
|
|
14
|
+
});
|
|
15
|
+
}, { exportOnly: true });
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import e3 from "@elaraai/e3";
|
|
2
|
+
import { East, StringType } from "@elaraai/east";
|
|
3
|
+
|
|
4
|
+
export const nameInput = e3.input("name", StringType, "World!");
|
|
5
|
+
|
|
6
|
+
export const greetFn = East.function(
|
|
7
|
+
[StringType],
|
|
8
|
+
StringType,
|
|
9
|
+
($, name) => East.str`Hello, ${name}!`,
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
export const greet = e3.task("greet", [nameInput], greetFn);
|
|
13
|
+
|
|
14
|
+
export default e3.package("__PROJECT_NAME__", "1.0.0", greet);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Run TypeScript-exported IR tests through the east-c Python bridge.
|
|
2
|
+
|
|
3
|
+
Generate IR first: npm run test:export
|
|
4
|
+
Then run: uv run pytest -v
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from east.runtime.compiler import compile_from_json
|
|
10
|
+
from east.runtime.platform import PlatformFunction
|
|
11
|
+
from east.runtime._compiler_eastc import _eastc_call
|
|
12
|
+
from east.types.types import FunctionType, NullType, StringType
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from east_py_std import platform as std_platform
|
|
16
|
+
except ImportError:
|
|
17
|
+
std_platform = []
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from east_py_io import platform as io_platform
|
|
21
|
+
except ImportError:
|
|
22
|
+
io_platform = []
|
|
23
|
+
|
|
24
|
+
TEST_IR_DIR = Path("dist/test-ir")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _get_ir_files():
|
|
28
|
+
if not TEST_IR_DIR.exists():
|
|
29
|
+
return []
|
|
30
|
+
return sorted(TEST_IR_DIR.glob("*.json"))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def run_one(ir_file: Path) -> tuple[int, int]:
|
|
34
|
+
"""Compile and run one IR test file. Returns (passed, failed)."""
|
|
35
|
+
data = ir_file.read_bytes()
|
|
36
|
+
is_async = b'"AsyncFunction"' in data[:100]
|
|
37
|
+
|
|
38
|
+
passed = 0
|
|
39
|
+
failed = 0
|
|
40
|
+
|
|
41
|
+
def describe_impl(name, test_fn):
|
|
42
|
+
if callable(test_fn):
|
|
43
|
+
try:
|
|
44
|
+
test_fn()
|
|
45
|
+
except Exception:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
def test_impl(name, test_fn):
|
|
49
|
+
nonlocal passed, failed
|
|
50
|
+
try:
|
|
51
|
+
if callable(test_fn):
|
|
52
|
+
test_fn()
|
|
53
|
+
passed += 1
|
|
54
|
+
except Exception:
|
|
55
|
+
failed += 1
|
|
56
|
+
|
|
57
|
+
def test_pass():
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
def test_fail(msg):
|
|
61
|
+
raise AssertionError(msg)
|
|
62
|
+
|
|
63
|
+
test_names = {"describe", "test", "testPass", "testFail"}
|
|
64
|
+
platform = [
|
|
65
|
+
pf for pf in std_platform if pf["name"] not in test_names
|
|
66
|
+
] + [
|
|
67
|
+
pf for pf in io_platform if pf["name"] not in test_names
|
|
68
|
+
] + [
|
|
69
|
+
PlatformFunction(name="describe", inputs=[StringType, FunctionType([], NullType)], output=NullType, type="sync", fn=describe_impl),
|
|
70
|
+
PlatformFunction(name="test", inputs=[StringType, FunctionType([], NullType)], output=NullType, type="sync", fn=test_impl),
|
|
71
|
+
PlatformFunction(name="testPass", inputs=[], output=NullType, type="sync", fn=test_pass),
|
|
72
|
+
PlatformFunction(name="testFail", inputs=[StringType], output=NullType, type="sync", fn=test_fail),
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
compiled = compile_from_json(data, platform, is_async=is_async)
|
|
76
|
+
handle = compiled._eastc_handle
|
|
77
|
+
_eastc_call(handle._compiled, handle._input_types, handle._output_type, ())
|
|
78
|
+
return passed, failed
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def pytest_generate_tests(metafunc):
|
|
82
|
+
if "ir_file" in metafunc.fixturenames:
|
|
83
|
+
files = _get_ir_files()
|
|
84
|
+
metafunc.parametrize("ir_file", files, ids=[f.stem for f in files])
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_ir(ir_file):
|
|
88
|
+
passed, failed = run_one(ir_file)
|
|
89
|
+
assert failed == 0, f"{failed} test(s) failed in {ir_file.stem}"
|
|
90
|
+
assert passed > 0, f"No tests ran in {ir_file.stem}"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"exclude": ["dist"],
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
"module": "nodenext",
|
|
6
|
+
"target": "esnext",
|
|
7
|
+
"lib": ["esnext", "es2024"],
|
|
8
|
+
"types": ["node"],
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"declarationMap": true,
|
|
12
|
+
"noUncheckedIndexedAccess": true,
|
|
13
|
+
"exactOptionalPropertyTypes": true,
|
|
14
|
+
"strict": true,
|
|
15
|
+
"jsx": "react-jsx",
|
|
16
|
+
"verbatimModuleSyntax": true,
|
|
17
|
+
"isolatedModules": true,
|
|
18
|
+
"noUncheckedSideEffectImports": true,
|
|
19
|
+
"moduleDetection": "force",
|
|
20
|
+
"skipLibCheck": true,
|
|
21
|
+
"noErrorTruncation": true,
|
|
22
|
+
"incremental": true
|
|
23
|
+
}
|
|
24
|
+
}
|