@elaraai/create-e3 1.0.18 → 1.0.19

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/dist/index.js CHANGED
@@ -43,6 +43,18 @@ var MANIFEST_FILE = "template.json";
43
43
  function substituteTokens(content, names) {
44
44
  return content.replaceAll("__PROJECT_NAME__", names.projectName).replaceAll("__DISPLAY_NAME__", names.displayName).replaceAll("__WORKSPACE_NAME__", names.workspaceName);
45
45
  }
46
+ function isPlainObject(v) {
47
+ return typeof v === "object" && v !== null && !Array.isArray(v);
48
+ }
49
+ function mergeInto(target, source) {
50
+ for (const [k, v] of Object.entries(source)) {
51
+ const existing = target[k];
52
+ if (isPlainObject(existing) && isPlainObject(v))
53
+ mergeInto(existing, v);
54
+ else
55
+ target[k] = v;
56
+ }
57
+ }
46
58
  function transformPackageJson(raw, names, version, manifest, enabled) {
47
59
  const pkg = JSON.parse(raw);
48
60
  pkg.name = `@elaraai/${names.projectName}`;
@@ -70,6 +82,10 @@ function transformPackageJson(raw, names, version, manifest, enabled) {
70
82
  else
71
83
  delete scripts?.[name];
72
84
  }
85
+ for (const [feature, spec] of Object.entries(manifest.features)) {
86
+ if (enabled(feature) && spec.packageJson)
87
+ mergeInto(pkg, spec.packageJson);
88
+ }
73
89
  }
74
90
  const pin = `^${version}`;
75
91
  for (const field of ["dependencies", "devDependencies", "peerDependencies"]) {
@@ -114,7 +130,12 @@ function scaffold(options) {
114
130
  throw new Error(`Template directory not found: ${templateDir}`);
115
131
  }
116
132
  const manifest = loadManifest(templateDir);
117
- const enabled = (feature) => options.features?.[feature] ?? manifest?.features[feature]?.default ?? true;
133
+ const enabled = (feature) => {
134
+ const spec = manifest?.features[feature];
135
+ if (spec?.allOf)
136
+ return spec.allOf.every(enabled);
137
+ return options.features?.[feature] ?? spec?.default ?? true;
138
+ };
118
139
  const skip = /* @__PURE__ */ new Set();
119
140
  const renames = {};
120
141
  if (manifest) {
@@ -129,6 +150,17 @@ function scaffold(options) {
129
150
  skip.add(f);
130
151
  }
131
152
  }
153
+ const variants = manifest.indexVariants ?? [];
154
+ if (variants.length > 0) {
155
+ const dest = (variants.find((v) => v.when.length === 0) ?? variants[variants.length - 1]).source;
156
+ const winner = variants.find((v) => v.when.every(enabled)) ?? variants[variants.length - 1];
157
+ for (const v of variants) {
158
+ if (v.source !== winner.source)
159
+ skip.add(v.source);
160
+ }
161
+ if (winner.source !== dest)
162
+ renames[winner.source] = dest;
163
+ }
132
164
  }
133
165
  const names = deriveNames(name, cwd);
134
166
  const inPlace = name === ".";
@@ -219,12 +251,15 @@ async function resolveFeatures(templateDir, args) {
219
251
  return {};
220
252
  const manifest = JSON.parse(readFileSync2(manifestPath, "utf8"));
221
253
  const features = {};
222
- for (const [key, spec] of Object.entries(manifest.features))
254
+ for (const [key, spec] of Object.entries(manifest.features)) {
255
+ if (spec.allOf)
256
+ continue;
223
257
  features[key] = spec.default ?? true;
258
+ }
224
259
  const runnerKeys = Object.keys(manifest.features).filter((k) => k.startsWith("runner:"));
225
260
  const runnerName = (key) => key.slice("runner:".length);
226
261
  const runnersFlag = args.find((a) => a.startsWith("--runners="));
227
- const selectionFlags = ["--tests", "--no-tests", "--ui", "--no-ui", "--eslint", "--no-eslint"].some((f) => args.includes(f)) || Boolean(runnersFlag);
262
+ const selectionFlags = ["--tests", "--no-tests", "--ui", "--no-ui", "--platform", "--no-platform", "--eslint", "--no-eslint"].some((f) => args.includes(f)) || Boolean(runnersFlag);
228
263
  if (args.includes("--tests"))
229
264
  features["tests"] = true;
230
265
  if (args.includes("--no-tests"))
@@ -233,6 +268,10 @@ async function resolveFeatures(templateDir, args) {
233
268
  features["ui"] = true;
234
269
  if (args.includes("--no-ui"))
235
270
  features["ui"] = false;
271
+ if (args.includes("--platform"))
272
+ features["platform"] = true;
273
+ if (args.includes("--no-platform"))
274
+ features["platform"] = false;
236
275
  if (args.includes("--eslint"))
237
276
  features["eslint"] = true;
238
277
  if (args.includes("--no-eslint"))
@@ -264,6 +303,9 @@ async function resolveFeatures(templateDir, args) {
264
303
  for (const key of runnerKeys)
265
304
  features[key] = picks.has(runnerName(key));
266
305
  }
306
+ if ("platform" in manifest.features) {
307
+ features["platform"] = await askYesNo(rl, "Include a project-owned platform module (custom TS-East functions, plus Python when east-py is on)?", features["platform"]);
308
+ }
267
309
  if ("eslint" in manifest.features) {
268
310
  features["eslint"] = await askYesNo(rl, "Include ESLint with the East lint rules?", features["eslint"]);
269
311
  }
@@ -292,6 +334,7 @@ function printHelp(kind) {
292
334
  if (kind === "e3") {
293
335
  console.log(" --tests | --no-tests include test files (default: yes)");
294
336
  console.log(" --ui | --no-ui include east-ui + e3-ui UI components (default: no)");
337
+ console.log(" --platform | --no-platform include a project-owned platform module (TS-East; +Python when east-py is on) (default: no)");
295
338
  console.log(" --runners=east-node,east-c,east-py East runtimes to include (default: all)");
296
339
  }
297
340
  console.log("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elaraai/create-e3",
3
- "version": "1.0.18",
3
+ "version": "1.0.19",
4
4
  "description": "Scaffold a new e3 project (BSL-1.1, Node + Python, durable execution): npm create @elaraai/e3",
5
5
  "type": "module",
6
6
  "bin": {
@@ -40,5 +40,9 @@ npm run lint # lint sources
40
40
  npm run clean # remove build output, dependencies, and the local repo
41
41
  ```
42
42
 
43
- If you scaffolded with UI, `src/surface.tsx` holds a decision surface — a `ui()` task an operator
43
+ If you scaffolded with UI, `src/ui/index.tsx` holds a decision surface — a `ui()` task an operator
44
44
  uses to observe and act on the recommendation, registered in the package next to the decision.
45
+
46
+ If you scaffolded with `--platform`, `src/platform/` holds project-owned TS-East platform functions
47
+ (exported via `./platform`) and `platform_module/` holds the Python ones — replace the generated
48
+ `example` functions with your own native code, called from tasks like any East function.
@@ -3,5 +3,7 @@ dist/
3
3
  *.tsbuildinfo
4
4
  .venv/
5
5
  __pycache__/
6
+ *.egg-info/
7
+ .pytest_cache/
6
8
  .repos/
7
9
  uv.lock
@@ -0,0 +1,16 @@
1
+ """Project-owned Python platform functions, aggregated for the east-py runner.
2
+
3
+ `east-py run -p platform_module` imports this package and reads the top-level
4
+ ``platform`` list below. This mirrors how the first-party east-py-std /
5
+ east-py-datascience packages are built: each submodule ends with
6
+ ``<name>_impl = platform_functions(__name__)``, and this file spreads them all
7
+ into ``platform``.
8
+
9
+ To add a function: create a module beside this file (e.g. ``pricing.py``) ending
10
+ with ``pricing_impl = platform_functions(__name__)``, then add an import and
11
+ spread it into ``platform`` below.
12
+ """
13
+
14
+ from .example import example_impl
15
+
16
+ platform = [*example_impl]
@@ -0,0 +1,31 @@
1
+ """An example project-owned Python platform function — replace it with your own.
2
+
3
+ Platform functions let East call NATIVE Python (numpy, pandas, scikit-learn, …)
4
+ that East itself can't express. The ``@platform_function`` is bound to East by
5
+ its dotted ``"<project>.<fn>"`` name, which the TypeScript declaration in
6
+ ``src/platform_module.ts`` mirrors exactly — keep the two in lockstep. Add
7
+ native dependencies to ``pyproject.toml`` and import them inside the body.
8
+
9
+ This module ends by collecting its functions into ``example_impl`` (the
10
+ canonical east-py idiom — ``platform_functions`` keys the registry on each
11
+ function's ``__module__``), which ``__init__.py`` spreads into the package's
12
+ top-level ``platform`` list.
13
+ """
14
+
15
+ from east.runtime.platform import platform_function, platform_functions
16
+ from east.types.types import ArrayType, FloatType
17
+
18
+
19
+ @platform_function(
20
+ inputs=[ArrayType(FloatType)],
21
+ output=FloatType,
22
+ name="__PROJECT_NAME__.example_python",
23
+ )
24
+ def example_python(values):
25
+ """Example: the mean of a list of floats. Replace with your own logic."""
26
+ values = list(values)
27
+ return sum(values) / len(values) if values else 0.0
28
+
29
+
30
+ # The platform functions defined in THIS module, in definition order.
31
+ example_impl = platform_functions(__name__)
@@ -0,0 +1,28 @@
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
+ ]
15
+
16
+ # Packaging block — without it `uv sync` would not install this project's own
17
+ # code, so `east-py run -p platform_module` could not import it. Matches the
18
+ # repo's pure-Python packages (east-py-std/io/cli). This variant of
19
+ # pyproject.toml ships only with the `--platform` scaffold feature (which
20
+ # requires the east-py runner); the plain pyproject.toml has no build-system, so
21
+ # `uv sync` installs dependencies without trying to build a package that isn't
22
+ # there.
23
+ [build-system]
24
+ requires = ["setuptools>=61"]
25
+ build-backend = "setuptools.build_meta"
26
+
27
+ [tool.setuptools]
28
+ packages = ["platform_module"]
@@ -0,0 +1,58 @@
1
+ import e3 from "@elaraai/e3";
2
+ import { East, ArrayType, FloatType, IntegerType } from "@elaraai/east";
3
+
4
+ import { examplePython } from "./platform_module.js";
5
+ import { exampleNode } from "./platform/index.js";
6
+
7
+ // In East a decision is a typed task over inputs. This one recommends how many
8
+ // units to reorder to bring stock up to its target level — never negative.
9
+ export const onHandInput = e3.input("on_hand", IntegerType, 12n);
10
+ export const targetInput = e3.input("reorder_to", IntegerType, 50n);
11
+
12
+ export const reorderFn = East.function(
13
+ [IntegerType, IntegerType],
14
+ IntegerType,
15
+ ($, onHand, target) => {
16
+ const gap = $.let(target.subtract(onHand));
17
+ $.return(East.greater(gap, 0n).ifElse(() => gap, () => 0n));
18
+ },
19
+ );
20
+
21
+ export const reorderQty = e3.task("reorder_qty", [onHandInput, targetInput], reorderFn);
22
+
23
+ // An example project-owned PYTHON platform function on the east-py runtime.
24
+ // `{ custom: "platform_module" }` loads the project's own package, resolved from
25
+ // <project>/.venv after `uv sync`. Replace `example_python`
26
+ // (platform_module/example.py) with your own.
27
+ export const exampleValuesInput = e3.input("example_values", ArrayType(FloatType), [1.0, 2.0, 3.0, 4.0, 5.0]);
28
+
29
+ export const examplePythonFn = East.function(
30
+ [ArrayType(FloatType)],
31
+ FloatType,
32
+ ($, values) => {
33
+ $.return(examplePython(values));
34
+ },
35
+ );
36
+
37
+ export const examplePythonTask = e3.task("example_python", [exampleValuesInput], examplePythonFn, {
38
+ runner: { runtime: "east-py", platforms: [{ custom: "platform_module" }, "east-py-std"] },
39
+ });
40
+
41
+ // An example project-owned TS-East platform function on the east-node runtime.
42
+ // `{ custom: "@elaraai/__PROJECT_NAME__" }` loads this package's own `./platform`
43
+ // export. Replace `exampleNode` (src/platform/example.ts) with your own.
44
+ export const exampleFactorInput = e3.input("example_factor", FloatType, 1.5);
45
+
46
+ export const exampleNodeFn = East.function(
47
+ [IntegerType, FloatType],
48
+ IntegerType,
49
+ ($, value, factor) => {
50
+ $.return(exampleNode(value, factor));
51
+ },
52
+ );
53
+
54
+ export const exampleNodeTask = e3.task("example_node", [reorderQty.output, exampleFactorInput], exampleNodeFn, {
55
+ runner: { runtime: "east-node", platforms: [{ custom: "@elaraai/__PROJECT_NAME__" }] },
56
+ });
57
+
58
+ export default e3.package("__PROJECT_NAME__", "1.0.0", reorderQty, examplePythonTask, exampleNodeTask);
@@ -0,0 +1,39 @@
1
+ import e3 from "@elaraai/e3";
2
+ import { East, FloatType, IntegerType } from "@elaraai/east";
3
+
4
+ import { exampleNode } from "./platform/index.js";
5
+
6
+ // In East a decision is a typed task over inputs. This one recommends how many
7
+ // units to reorder to bring stock up to its target level — never negative.
8
+ export const onHandInput = e3.input("on_hand", IntegerType, 12n);
9
+ export const targetInput = e3.input("reorder_to", IntegerType, 50n);
10
+
11
+ export const reorderFn = East.function(
12
+ [IntegerType, IntegerType],
13
+ IntegerType,
14
+ ($, onHand, target) => {
15
+ const gap = $.let(target.subtract(onHand));
16
+ $.return(East.greater(gap, 0n).ifElse(() => gap, () => 0n));
17
+ },
18
+ );
19
+
20
+ export const reorderQty = e3.task("reorder_qty", [onHandInput, targetInput], reorderFn);
21
+
22
+ // An example project-owned TS-East platform function on the east-node runtime.
23
+ // `{ custom: "@elaraai/__PROJECT_NAME__" }` loads this package's own `./platform`
24
+ // export. Replace `exampleNode` (src/platform/example.ts) with your own.
25
+ export const exampleFactorInput = e3.input("example_factor", FloatType, 1.5);
26
+
27
+ export const exampleNodeFn = East.function(
28
+ [IntegerType, FloatType],
29
+ IntegerType,
30
+ ($, value, factor) => {
31
+ $.return(exampleNode(value, factor));
32
+ },
33
+ );
34
+
35
+ export const exampleNodeTask = e3.task("example_node", [reorderQty.output, exampleFactorInput], exampleNodeFn, {
36
+ runner: { runtime: "east-node", platforms: [{ custom: "@elaraai/__PROJECT_NAME__" }] },
37
+ });
38
+
39
+ export default e3.package("__PROJECT_NAME__", "1.0.0", reorderQty, exampleNodeTask);
@@ -0,0 +1,59 @@
1
+ import e3 from "@elaraai/e3";
2
+ import { East, ArrayType, FloatType, IntegerType } from "@elaraai/east";
3
+
4
+ import { surface } from "./ui/index.js";
5
+ import { examplePython } from "./platform_module.js";
6
+ import { exampleNode } from "./platform/index.js";
7
+
8
+ // In East a decision is a typed task over inputs. This one recommends how many
9
+ // units to reorder to bring stock up to its target level — never negative.
10
+ export const onHandInput = e3.input("on_hand", IntegerType, 12n);
11
+ export const targetInput = e3.input("reorder_to", IntegerType, 50n);
12
+
13
+ export const reorderFn = East.function(
14
+ [IntegerType, IntegerType],
15
+ IntegerType,
16
+ ($, onHand, target) => {
17
+ const gap = $.let(target.subtract(onHand));
18
+ $.return(East.greater(gap, 0n).ifElse(() => gap, () => 0n));
19
+ },
20
+ );
21
+
22
+ export const reorderQty = e3.task("reorder_qty", [onHandInput, targetInput], reorderFn);
23
+
24
+ // An example project-owned PYTHON platform function on the east-py runtime.
25
+ // `{ custom: "platform_module" }` loads the project's own package, resolved from
26
+ // <project>/.venv after `uv sync`. Replace `example_python`
27
+ // (platform_module/example.py) with your own.
28
+ export const exampleValuesInput = e3.input("example_values", ArrayType(FloatType), [1.0, 2.0, 3.0, 4.0, 5.0]);
29
+
30
+ export const examplePythonFn = East.function(
31
+ [ArrayType(FloatType)],
32
+ FloatType,
33
+ ($, values) => {
34
+ $.return(examplePython(values));
35
+ },
36
+ );
37
+
38
+ export const examplePythonTask = e3.task("example_python", [exampleValuesInput], examplePythonFn, {
39
+ runner: { runtime: "east-py", platforms: [{ custom: "platform_module" }, "east-py-std"] },
40
+ });
41
+
42
+ // An example project-owned TS-East platform function on the east-node runtime.
43
+ // `{ custom: "@elaraai/__PROJECT_NAME__" }` loads this package's own `./platform`
44
+ // export. Replace `exampleNode` (src/platform/example.ts) with your own.
45
+ export const exampleFactorInput = e3.input("example_factor", FloatType, 1.5);
46
+
47
+ export const exampleNodeFn = East.function(
48
+ [IntegerType, FloatType],
49
+ IntegerType,
50
+ ($, value, factor) => {
51
+ $.return(exampleNode(value, factor));
52
+ },
53
+ );
54
+
55
+ export const exampleNodeTask = e3.task("example_node", [reorderQty.output, exampleFactorInput], exampleNodeFn, {
56
+ runner: { runtime: "east-node", platforms: [{ custom: "@elaraai/__PROJECT_NAME__" }] },
57
+ });
58
+
59
+ export default e3.package("__PROJECT_NAME__", "1.0.0", reorderQty, examplePythonTask, exampleNodeTask, surface);
@@ -0,0 +1,40 @@
1
+ import e3 from "@elaraai/e3";
2
+ import { East, FloatType, IntegerType } from "@elaraai/east";
3
+
4
+ import { surface } from "./ui/index.js";
5
+ import { exampleNode } from "./platform/index.js";
6
+
7
+ // In East a decision is a typed task over inputs. This one recommends how many
8
+ // units to reorder to bring stock up to its target level — never negative.
9
+ export const onHandInput = e3.input("on_hand", IntegerType, 12n);
10
+ export const targetInput = e3.input("reorder_to", IntegerType, 50n);
11
+
12
+ export const reorderFn = East.function(
13
+ [IntegerType, IntegerType],
14
+ IntegerType,
15
+ ($, onHand, target) => {
16
+ const gap = $.let(target.subtract(onHand));
17
+ $.return(East.greater(gap, 0n).ifElse(() => gap, () => 0n));
18
+ },
19
+ );
20
+
21
+ export const reorderQty = e3.task("reorder_qty", [onHandInput, targetInput], reorderFn);
22
+
23
+ // An example project-owned TS-East platform function on the east-node runtime.
24
+ // `{ custom: "@elaraai/__PROJECT_NAME__" }` loads this package's own `./platform`
25
+ // export. Replace `exampleNode` (src/platform/example.ts) with your own.
26
+ export const exampleFactorInput = e3.input("example_factor", FloatType, 1.5);
27
+
28
+ export const exampleNodeFn = East.function(
29
+ [IntegerType, FloatType],
30
+ IntegerType,
31
+ ($, value, factor) => {
32
+ $.return(exampleNode(value, factor));
33
+ },
34
+ );
35
+
36
+ export const exampleNodeTask = e3.task("example_node", [reorderQty.output, exampleFactorInput], exampleNodeFn, {
37
+ runner: { runtime: "east-node", platforms: [{ custom: "@elaraai/__PROJECT_NAME__" }] },
38
+ });
39
+
40
+ export default e3.package("__PROJECT_NAME__", "1.0.0", reorderQty, exampleNodeTask, surface);
@@ -1,7 +1,7 @@
1
1
  import e3 from "@elaraai/e3";
2
2
  import { East, IntegerType } from "@elaraai/east";
3
3
 
4
- import { surface } from "./surface.js";
4
+ import { surface } from "./ui/index.js";
5
5
 
6
6
  // In East a decision is a typed task over inputs. This one recommends how many
7
7
  // units to reorder to bring stock up to its target level — never negative.
@@ -0,0 +1,16 @@
1
+ import { East, IntegerType, FloatType } from "@elaraai/east";
2
+
3
+ // An example project-owned TS-East platform function — replace it with your own.
4
+ // Platform functions let East call native TS/Node code East can't express. The
5
+ // declaration (`exampleNode`) is what East code calls in a task; the
6
+ // implementation runs on the east-node runtime. To add another, create a sibling
7
+ // file in this folder and wire it into ./index.ts.
8
+ export const exampleNode = East.platform(
9
+ "__PROJECT_NAME__.example_node",
10
+ [IntegerType, FloatType],
11
+ IntegerType,
12
+ );
13
+
14
+ export const exampleNodeImpl = exampleNode.implement(
15
+ (value: bigint, factor: number): bigint => BigInt(Math.ceil(Number(value) * factor)),
16
+ );
@@ -0,0 +1,14 @@
1
+ // Barrel for the project's TS-East platform functions (run on the east-node
2
+ // runtime). This module IS the package's `./platform` subpath export — its
3
+ // default export is the PlatformFunction[] that east-node-cli loads.
4
+ //
5
+ // To add a function: create a sibling file (e.g. ./discount.ts) exporting its
6
+ // declaration + `<name>Impl`, then add one re-export line and one array entry
7
+ // below.
8
+ import { exampleNodeImpl } from "./example.js";
9
+
10
+ // Re-export each declaration so e3 tasks (src/index.ts) can call them.
11
+ export { exampleNode } from "./example.js";
12
+
13
+ // The PlatformFunction[] the east-node runner loads via `./platform`.
14
+ export default [exampleNodeImpl];
@@ -0,0 +1,13 @@
1
+ import { East, ArrayType, FloatType } from "@elaraai/east";
2
+
3
+ // Type-safe declaration mirroring `platform_module/example.py`'s
4
+ // `example_python` — same dotted "<project>.<fn>" name, same signature.
5
+ // Hand-written (no codegen): keep it in lockstep with the Python
6
+ // @platform_function. East code calls this; the impl runs on the east-py
7
+ // runtime, resolved from the project's own `.venv` (see the `example_python`
8
+ // task in the index).
9
+ export const examplePython = East.platform(
10
+ "__PROJECT_NAME__.example_python",
11
+ [ArrayType(FloatType)],
12
+ FloatType,
13
+ );
@@ -4,7 +4,9 @@ import { Box, Text, UIComponentType } from "@elaraai/east-ui";
4
4
 
5
5
  // A decision surface for an operator to observe and act on the recommendation.
6
6
  // east-ui / e3-ui JSX authoring is still being finalised — build this out: bind
7
- // reorderQty and the inputs with Data.bind, then add the Observe / Decide controls.
7
+ // the tasks and inputs with Data.bind, then add the Observe / Decide controls.
8
+ // To add another surface, define it beside this file and register it in the
9
+ // package (src/index.ts).
8
10
  export const surface = ui(
9
11
  "surface",
10
12
  [],
@@ -7,11 +7,31 @@
7
7
  },
8
8
  "ui": {
9
9
  "default": false,
10
- "files": ["src/surface.tsx", "src/index.ui.ts"],
11
- "disable": ["src/index.ts"],
12
- "rename": { "src/index.ui.ts": "src/index.ts" },
10
+ "files": ["src/ui/index.tsx"],
13
11
  "dependencies": ["@elaraai/east-ui", "@elaraai/e3-ui"]
14
12
  },
13
+ "platform": {
14
+ "default": false,
15
+ "files": ["src/platform/index.ts", "src/platform/example.ts"],
16
+ "packageJson": {
17
+ "exports": {
18
+ ".": "./dist/index.js",
19
+ "./platform": "./dist/platform/index.js",
20
+ "./package.json": "./package.json"
21
+ }
22
+ }
23
+ },
24
+ "platform-py": {
25
+ "allOf": ["platform", "runner:east-py"],
26
+ "files": [
27
+ "platform_module/__init__.py",
28
+ "platform_module/example.py",
29
+ "src/platform_module.ts",
30
+ "pyproject.platform.toml"
31
+ ],
32
+ "disable": ["pyproject.toml"],
33
+ "rename": { "pyproject.platform.toml": "pyproject.toml" }
34
+ },
15
35
  "runner:east-node": {
16
36
  "default": true,
17
37
  "devDependencies": ["@elaraai/east-node-cli"]
@@ -42,6 +62,14 @@
42
62
  "devDependencies": ["@elaraai/tsserver-plugin-east"]
43
63
  }
44
64
  },
65
+ "indexVariants": [
66
+ { "when": ["ui", "platform", "runner:east-py"], "source": "src/index.ui.platform.py.ts" },
67
+ { "when": ["ui", "platform"], "source": "src/index.ui.platform.ts" },
68
+ { "when": ["ui"], "source": "src/index.ui.ts" },
69
+ { "when": ["platform", "runner:east-py"], "source": "src/index.platform.py.ts" },
70
+ { "when": ["platform"], "source": "src/index.platform.ts" },
71
+ { "when": [], "source": "src/index.ts" }
72
+ ],
45
73
  "scriptVariants": {
46
74
  "test": [
47
75
  { "when": ["tests", "runner:east-py"], "value": "npm run build && npm run test:export && npm run test:py" },
@@ -50,6 +78,10 @@
50
78
  "setup": [
51
79
  { "when": ["runner:east-py"], "value": "npm install && uv sync" },
52
80
  { "when": [], "value": "npm install" }
81
+ ],
82
+ "start": [
83
+ { "when": ["platform"], "value": "npm run build && npm run deploy && e3 dataflow run .repos __WORKSPACE_NAME__" },
84
+ { "when": [], "value": "npm run deploy && e3 dataflow run .repos __WORKSPACE_NAME__" }
53
85
  ]
54
86
  }
55
87
  }
@@ -21,6 +21,13 @@ try:
21
21
  except ImportError:
22
22
  io_platform = []
23
23
 
24
+ # A project-owned platform module (scaffolded by `--platform`). Absent unless
25
+ # that feature is on, so this import is best-effort.
26
+ try:
27
+ from platform_module import platform as project_platform
28
+ except ImportError:
29
+ project_platform = []
30
+
24
31
  TEST_IR_DIR = Path("dist/test-ir")
25
32
 
26
33
 
@@ -65,6 +72,8 @@ def run_one(ir_file: Path) -> tuple[int, int]:
65
72
  pf for pf in std_platform if pf["name"] not in test_names
66
73
  ] + [
67
74
  pf for pf in io_platform if pf["name"] not in test_names
75
+ ] + [
76
+ pf for pf in project_platform if pf["name"] not in test_names
68
77
  ] + [
69
78
  PlatformFunction(name="describe", inputs=[StringType, FunctionType([], NullType)], output=NullType, type="sync", fn=describe_impl),
70
79
  PlatformFunction(name="test", inputs=[StringType, FunctionType([], NullType)], output=NullType, type="sync", fn=test_impl),