@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 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,7 @@
1
+ node_modules/
2
+ dist/
3
+ *.tsbuildinfo
4
+ .venv/
5
+ __pycache__/
6
+ .repos/
7
+ uv.lock
@@ -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
+ }