create-astrale-domain 0.3.0-beta.2 → 0.3.0-beta.20

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.
Files changed (54) hide show
  1. package/README.md +11 -1
  2. package/dist/index.js +80 -59
  3. package/package.json +19 -14
  4. package/template/.env.example +5 -1
  5. package/template/.variants/custom/README.md +7 -6
  6. package/template/.variants/custom/__tests__/variant.test.ts +13 -0
  7. package/template/.variants/custom/application.ts +9 -0
  8. package/template/.variants/custom/schema/application/index.ts +1 -3
  9. package/template/.variants/custom/ui/application/__tests__/application.test.ts +9 -0
  10. package/template/.variants/custom/ui/application/index.ts +11 -0
  11. package/template/.variants/custom/views/application/__tests__/application.view.test.ts +15 -0
  12. package/template/.variants/custom/views/application/app/main.ts +3 -0
  13. package/template/.variants/custom/views/application/application.view.ts +7 -0
  14. package/template/.variants/custom/views/application/routes.ts +4 -5
  15. package/template/.variants/custom/vite.config.ts +9 -0
  16. package/template/.variants/none/README.md +2 -1
  17. package/template/.variants/none/__tests__/variant.test.ts +8 -0
  18. package/template/.variants/none/application.ts +8 -0
  19. package/template/.variants/none/schema/application/index.ts +0 -2
  20. package/template/.variants/react/README.md +12 -11
  21. package/template/.variants/react/__tests__/variant.test.ts +13 -0
  22. package/template/.variants/react/application.ts +9 -0
  23. package/template/.variants/react/frontend/index.html +12 -0
  24. package/template/.variants/react/frontend/package.json +5 -0
  25. package/template/.variants/react/frontend/src/main.tsx +15 -0
  26. package/template/.variants/react/frontend/src/routeTree.gen.ts +77 -0
  27. package/template/.variants/react/frontend/src/router.tsx +11 -0
  28. package/template/.variants/react/frontend/src/routes/__root.tsx +30 -0
  29. package/template/.variants/react/frontend/src/routes/about.tsx +16 -0
  30. package/template/.variants/react/frontend/src/routes/index.tsx +16 -0
  31. package/template/.variants/react/frontend/src/styles.css +19 -0
  32. package/template/.variants/react/frontend/src/vite-env.d.ts +1 -0
  33. package/template/.variants/react/schema/application/index.ts +1 -3
  34. package/template/.variants/react/ui/application/screen.tsx +6 -6
  35. package/template/.variants/react/views/application/routes.ts +5 -9
  36. package/template/.variants/react/vite.config.ts +36 -0
  37. package/template/CLAUDE.md +9 -5
  38. package/template/index.ts +2 -1
  39. package/template/package.json +45 -15
  40. package/template/pnpm-workspace.yaml +31 -25
  41. package/template/runtime.ts +13 -0
  42. package/template/schema/application/__tests__/schema.test.ts +4 -4
  43. package/template/tests/e2e/application.test.ts +6 -5
  44. package/template/tsconfig.json +13 -10
  45. package/template/tsconfig.package.json +29 -0
  46. package/template/.variants/custom/implementation.ts +0 -14
  47. package/template/.variants/custom/views/app/main.ts +0 -10
  48. package/template/.variants/none/implementation.ts +0 -14
  49. package/template/.variants/react/implementation.ts +0 -16
  50. package/template/.variants/react/ui/application/styles.css +0 -34
  51. package/template/.variants/react/ui/application/styles.d.ts +0 -1
  52. package/template/.variants/react/views/app/README.md +0 -4
  53. package/template/.variants/react/views/application/application.view.tsx +0 -19
  54. /package/template/.variants/custom/views/{app → application/app}/index.html +0 -0
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Official scaffolder for a new Astrale Domain project.
4
4
 
5
- During the SDK beta program, generate a project from the beta cohort explicitly:
5
+ During the SDK beta program, generate a project from the beta package set explicitly:
6
6
 
7
7
  ```bash
8
8
  npx -y create-astrale-domain@beta my-domain --yes
@@ -10,3 +10,13 @@ npx -y create-astrale-domain@beta my-domain --yes
10
10
 
11
11
  The generated project must resolve the SDK and deployment adapters from npm; it must not retain
12
12
  workspace links or repository-only source paths.
13
+
14
+ Generated projects have one package journey:
15
+
16
+ ```bash
17
+ pnpm pack
18
+ ```
19
+
20
+ The existing `astrale-domain package` command compiles the public Schema and independently loadable
21
+ Runtime, normalizes emitted declarations to semantic SDK facades, and verifies the archive boundary.
22
+ Deployment adapters remain development dependencies and are not part of the published Domain runtime.
package/dist/index.js CHANGED
@@ -6,7 +6,11 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
8
  var __commonJS = (cb, mod) => function __require() {
9
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
9
+ try {
10
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
+ } catch (e2) {
12
+ throw mod = 0, e2;
13
+ }
10
14
  };
11
15
  var __copyProps = (to, from, except, desc) => {
12
16
  if (from && typeof from === "object" || typeof from === "function") {
@@ -364,7 +368,7 @@ function BD(e2, u2) {
364
368
  return s;
365
369
  }
366
370
  var AD = globalThis.process.platform.startsWith("win");
367
- var S = Symbol("clack:cancel");
371
+ var S = /* @__PURE__ */ Symbol("clack:cancel");
368
372
  function pD(e2) {
369
373
  return e2 === S;
370
374
  }
@@ -780,6 +784,7 @@ import { existsSync as existsSync2 } from "node:fs";
780
784
  import { delimiter, join as join2, relative as relative2, resolve as resolve2 } from "node:path";
781
785
 
782
786
  // src/scaffold/index.ts
787
+ import { signingIdentity } from "@astrale-os/adapter-cloudflare/build";
783
788
  import { existsSync, readFileSync } from "node:fs";
784
789
  import { cp, readFile, rename, rm, writeFile } from "node:fs/promises";
785
790
  import { createRequire } from "node:module";
@@ -794,9 +799,7 @@ var INSTANCE_PLACEHOLDER = "my-instance-slug";
794
799
  var LINK_PACKAGES = /* @__PURE__ */ new Set([
795
800
  "@astrale-os/sdk",
796
801
  "@astrale-os/adapter-cloudflare",
797
- "@astrale-os/adapter-astrale",
798
- "@astrale-os/shell",
799
- "@astrale-os/shell-react"
802
+ "@astrale-os/adapter-astrale"
800
803
  ]);
801
804
  var ADAPTER_PACKAGES = {
802
805
  astrale: "@astrale-os/adapter-astrale",
@@ -928,7 +931,11 @@ async function writeProject(opts) {
928
931
  await stampOrigin(opts.dir, opts.origin);
929
932
  await rewritePackageJson(opts.dir, opts.name, opts.link);
930
933
  if (opts.link) await writeLinkedZodOverride(opts.dir);
931
- else await writeReleaseAgePolicy(opts.dir);
934
+ await signingIdentity({
935
+ projectDir: opts.dir,
936
+ origin: opts.origin,
937
+ mode: "local"
938
+ });
932
939
  await writeFile(
933
940
  join(opts.dir, ".env.dev"),
934
941
  "# Dev secrets \u2014 the ENTIRE file is injected into the local runtime by `pnpm dev`.\n# Gitignored. See .env.example.\n"
@@ -943,42 +950,41 @@ async function materializeGitignores(dir, frontend) {
943
950
  throw new Error(`Template is missing packaged Git ignore file ${packagedPath}.`);
944
951
  }
945
952
  }
946
- if (frontend === "react") {
947
- const path = join(dir, ".gitignore");
948
- const current = await readFile(path, "utf-8");
949
- await writeFile(
950
- path,
951
- `${current.trimEnd()}
952
-
953
- # SDK-managed React bootstrap (regenerated before every frontend build)
954
- /views/app/index.html
955
- /views/app/astrale.generated.tsx
956
- `
957
- );
958
- }
953
+ void frontend;
959
954
  }
960
955
  async function materializeFrontend(opts, template) {
961
956
  const variant = join(template, ".variants", opts.frontend);
962
957
  if (!existsSync(variant)) throw new Error(`Frontend template not found at ${variant}.`);
963
- await cp(variant, opts.dir, { recursive: true });
958
+ await cp(variant, opts.dir, {
959
+ recursive: true,
960
+ filter: (source) => relative(variant, source) !== "implementation.ts"
961
+ });
964
962
  const path = join(opts.dir, "package.json");
965
963
  const pkg = JSON.parse(await readFile(path, "utf-8"));
966
964
  pkg.dependencies ??= {};
967
965
  pkg.devDependencies ??= {};
966
+ pkg.scripts ??= {};
968
967
  if (opts.frontend === "react") {
969
968
  Object.assign(pkg.dependencies, {
970
- "@astrale-os/shell": ">=0.3.8-beta.2 <1.0.0",
971
- "@astrale-os/shell-react": ">=0.2.10-beta.2 <1.0.0",
969
+ "@tanstack/react-router": "1.170.17",
972
970
  react: "^19.2.0",
973
971
  "react-dom": "^19.2.0"
974
972
  });
975
973
  Object.assign(pkg.devDependencies, {
974
+ "@tailwindcss/vite": "^4.0.6",
975
+ "@tanstack/router-plugin": "1.168.19",
976
976
  "@types/react": "^19.2.0",
977
977
  "@types/react-dom": "^19.2.0",
978
- vite: "^7.1.7"
978
+ "@vitejs/plugin-react": "^6.0.5",
979
+ tailwindcss: "^4.0.6",
980
+ vite: "^8.2.1"
979
981
  });
980
982
  } else if (opts.frontend === "custom") {
981
- pkg.devDependencies.vite = "^7.1.7";
983
+ pkg.devDependencies.vite = "^8.2.1";
984
+ }
985
+ if (opts.frontend !== "none") {
986
+ pkg.scripts["frontend:dev"] = "vite";
987
+ pkg.scripts["frontend:build"] = "vite build";
982
988
  }
983
989
  pkg.dependencies = sorted(pkg.dependencies);
984
990
  pkg.devDependencies = sorted(pkg.devDependencies);
@@ -1014,16 +1020,16 @@ async function materializeAdapter(opts) {
1014
1020
  async function addManagedAdapterDep(dir) {
1015
1021
  const path = join(dir, "package.json");
1016
1022
  const pkg = JSON.parse(await readFile(path, "utf-8"));
1017
- if (!pkg.dependencies || !("@astrale-os/adapter-cloudflare" in pkg.dependencies)) {
1023
+ if (!pkg.devDependencies || !("@astrale-os/adapter-cloudflare" in pkg.devDependencies)) {
1018
1024
  throw new Error(
1019
1025
  "Could not materialize @astrale-os/adapter-astrale: the scaffold body does not declare its @astrale-os/adapter-cloudflare Worker runtime dependency."
1020
1026
  );
1021
1027
  }
1022
- pkg.dependencies["@astrale-os/adapter-astrale"] = installedPackageRange(
1028
+ pkg.devDependencies["@astrale-os/adapter-astrale"] = installedPackageRange(
1023
1029
  "@astrale-os/adapter-astrale"
1024
1030
  );
1025
- pkg.dependencies = Object.fromEntries(
1026
- Object.entries(pkg.dependencies).sort(([a], [b3]) => a.localeCompare(b3))
1031
+ pkg.devDependencies = Object.fromEntries(
1032
+ Object.entries(pkg.devDependencies).sort(([a], [b3]) => a.localeCompare(b3))
1027
1033
  );
1028
1034
  await writeFile(path, JSON.stringify(pkg, null, 2) + "\n");
1029
1035
  }
@@ -1031,9 +1037,23 @@ async function rewritePackageJson(dir, name, link) {
1031
1037
  const path = join(dir, "package.json");
1032
1038
  const pkg = JSON.parse(await readFile(path, "utf-8"));
1033
1039
  pkg.name = name;
1040
+ if (pkg.imports !== void 0) {
1041
+ pkg.publishConfig ??= {};
1042
+ pkg.publishConfig.imports = publishedImports(pkg.imports);
1043
+ }
1034
1044
  rewriteAstraleDeps(pkg, link);
1035
1045
  await writeFile(path, JSON.stringify(pkg, null, 2) + "\n");
1036
1046
  }
1047
+ function publishedImports(imports) {
1048
+ return Object.fromEntries(
1049
+ Object.entries(imports).map(([specifier, target]) => {
1050
+ if (!/^\.\/.*\.ts$/u.test(target)) {
1051
+ throw new TypeError(`Package import ${specifier} must target one project TypeScript file.`);
1052
+ }
1053
+ return [specifier, `./dist/${target.slice(2, -3)}.js`];
1054
+ })
1055
+ );
1056
+ }
1037
1057
  function rewriteAstraleDeps(pkg, link) {
1038
1058
  if (!link) return false;
1039
1059
  let changed = false;
@@ -1050,7 +1070,6 @@ function rewriteAstraleDeps(pkg, link) {
1050
1070
  }
1051
1071
  return changed;
1052
1072
  }
1053
- var FIRST_PARTY_SCOPES = ["@astrale-domains/*", "@astrale-os/*"];
1054
1073
  async function writeLinkedZodOverride(dir) {
1055
1074
  const workspace = join(dir, "pnpm-workspace.yaml");
1056
1075
  if (!existsSync(workspace)) return;
@@ -1066,21 +1085,6 @@ ${end}
1066
1085
  const pattern = new RegExp(`\\n?${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}\\n?`);
1067
1086
  await writeFile(workspace, text.replace(pattern, "\n").trimEnd() + block);
1068
1087
  }
1069
- async function writeReleaseAgePolicy(dir) {
1070
- const workspace = join(dir, "pnpm-workspace.yaml");
1071
- if (!existsSync(workspace)) return;
1072
- const start = "# create-astrale-domain: first-party scopes";
1073
- const end = "# /create-astrale-domain";
1074
- const block = `
1075
- ${start}
1076
- minimumReleaseAgeExclude:
1077
- ` + FIRST_PARTY_SCOPES.map((scope) => ` - '${scope}'`).join("\n") + `
1078
- ${end}
1079
- `;
1080
- const text = await readFile(workspace, "utf-8");
1081
- const pattern = new RegExp(`\\n?${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}\\n?`);
1082
- await writeFile(workspace, text.replace(pattern, "\n").trimEnd() + block);
1083
- }
1084
1088
  function escapeRegExp(value) {
1085
1089
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1086
1090
  }
@@ -1115,13 +1119,6 @@ function localPackageRoot(dependency) {
1115
1119
  const installed = dirname(resolved.path);
1116
1120
  if (!installed.split(sep).includes("node_modules")) return installed;
1117
1121
  }
1118
- if ((dependency === "@astrale-os/shell" || dependency === "@astrale-os/shell-react") && SDK_ROOT !== void 0) {
1119
- const packageDirectory = dependency === "@astrale-os/shell" ? "shell" : "shell-react";
1120
- for (const base of [dirname(SDK_ROOT), dirname(dirname(SDK_ROOT))]) {
1121
- const candidate = join(base, "shell", "packages", packageDirectory);
1122
- if (existsSync(join(candidate, "package.json"))) return candidate;
1123
- }
1124
- }
1125
1122
  throw new Error(`No active source package is available for ${dependency}.`);
1126
1123
  }
1127
1124
 
@@ -1175,6 +1172,10 @@ async function main() {
1175
1172
  const headless = flags.yes || !process.stdin.isTTY;
1176
1173
  Ie("Astrale \u2014 new domain");
1177
1174
  let slug = flags.positional[0];
1175
+ if (flags.positional.length > 1) {
1176
+ xe(`Unexpected positional argument "${flags.positional[1]}".`);
1177
+ return 1;
1178
+ }
1178
1179
  if (!slug) {
1179
1180
  if (headless) {
1180
1181
  xe("A domain slug is required in non-interactive mode (pass it as the first argument).");
@@ -1249,6 +1250,10 @@ async function main() {
1249
1250
  pm = answer;
1250
1251
  }
1251
1252
  }
1253
+ if (!PMS.includes(pm)) {
1254
+ xe(`Unknown package manager "${pm}" \u2014 use ${PMS.join(", ")}.`);
1255
+ return 1;
1256
+ }
1252
1257
  const dir = resolve2(flags.dir ?? slug);
1253
1258
  if (existsSync2(dir) && !flags.force) {
1254
1259
  if (headless) {
@@ -1259,8 +1264,10 @@ async function main() {
1259
1264
  if (pD(go) || !go) return bail();
1260
1265
  }
1261
1266
  const link = flags.link ?? workspaceAvailable();
1262
- if (flags.origin !== void 0 && !isValidSlug(flags.origin)) {
1263
- xe(`Invalid --origin "${flags.origin}". Use lowercase letters, digits, dots and dashes.`);
1267
+ if (flags.origin !== void 0 && !isValidOrigin(flags.origin)) {
1268
+ xe(
1269
+ `Invalid --origin "${flags.origin}". Use a lowercase dotted domain with valid DNS labels.`
1270
+ );
1264
1271
  return 1;
1265
1272
  }
1266
1273
  const origin = flags.origin ?? (slug.includes(".") ? slug : `${slug}.example.dev`);
@@ -1341,16 +1348,24 @@ function parseFlags(argv) {
1341
1348
  else if (a === "--force") flags.force = true;
1342
1349
  else if (a === "--link") flags.link = true;
1343
1350
  else if (a === "--no-link") flags.link = false;
1344
- else if (a === "--adapter") flags.adapter = argv[++i];
1345
- else if (a === "--frontend") flags.frontend = argv[++i];
1346
- else if (a === "--pm") flags.pm = argv[++i];
1347
- else if (a === "--dir") flags.dir = argv[++i];
1348
- else if (a === "--origin") flags.origin = argv[++i];
1349
- else if (a === "--instance") flags.instance = argv[++i];
1351
+ else if (a === "--adapter") flags.adapter = flagValue(argv, ++i, a);
1352
+ else if (a === "--frontend") flags.frontend = flagValue(argv, ++i, a);
1353
+ else if (a === "--pm") flags.pm = flagValue(argv, ++i, a);
1354
+ else if (a === "--dir") flags.dir = flagValue(argv, ++i, a);
1355
+ else if (a === "--origin") flags.origin = flagValue(argv, ++i, a);
1356
+ else if (a === "--instance") flags.instance = flagValue(argv, ++i, a);
1357
+ else if (a.startsWith("-")) throw new TypeError(`Unknown option "${a}".`);
1350
1358
  else if (!a.startsWith("-")) flags.positional.push(a);
1351
1359
  }
1352
1360
  return flags;
1353
1361
  }
1362
+ function flagValue(argv, index, flag) {
1363
+ const value = argv[index];
1364
+ if (value === void 0 || value.startsWith("-")) {
1365
+ throw new TypeError(`${flag} requires a value.`);
1366
+ }
1367
+ return value;
1368
+ }
1354
1369
  function printHelp() {
1355
1370
  process.stdout.write(
1356
1371
  `
@@ -1384,6 +1399,12 @@ Non-interactive (also auto-enabled without a TTY, e.g. CI):
1384
1399
  function isValidSlug(v2) {
1385
1400
  return /^[a-z][a-z0-9.-]*$/.test(v2);
1386
1401
  }
1402
+ function isValidOrigin(value) {
1403
+ if (value.length > 253 || !value.includes(".")) return false;
1404
+ return value.split(".").every(
1405
+ (label) => label.length > 0 && label.length <= 63 && /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/u.test(label)
1406
+ );
1407
+ }
1387
1408
  function bail() {
1388
1409
  xe("Cancelled.");
1389
1410
  return 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-astrale-domain",
3
- "version": "0.3.0-beta.2",
3
+ "version": "0.3.0-beta.20",
4
4
  "description": "Scaffold a standalone Astrale domain — npm create astrale-domain",
5
5
  "keywords": [
6
6
  "astrale",
@@ -10,38 +10,43 @@
10
10
  ],
11
11
  "license": "Apache-2.0",
12
12
  "author": "Astrale",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/astrale-os/sdk.git"
16
+ },
13
17
  "bin": {
14
18
  "create-astrale-domain": "./dist/index.js"
15
19
  },
16
20
  "files": [
17
21
  "dist",
22
+ "!dist/**/*.map",
18
23
  "template",
19
- "!template/node_modules",
20
- "!template/pnpm-lock.yaml"
24
+ "!template/**/node_modules",
25
+ "!template/**/node_modules/**",
26
+ "!template/**/pnpm-lock.yaml"
21
27
  ],
22
28
  "type": "module",
23
29
  "dependencies": {
24
30
  "@clack/prompts": "^0.11.0",
25
- "@astrale-os/adapter-cloudflare": "^0.5.0-beta.9",
26
- "@astrale-os/adapter-astrale": "^0.5.0-beta.2"
31
+ "@astrale-os/adapter-astrale": "^0.5.0-beta.20",
32
+ "@astrale-os/adapter-cloudflare": "^0.5.0-beta.27"
27
33
  },
28
34
  "devDependencies": {
29
35
  "@astrale-os/ox": ">=0.1.0 <1.0.0",
30
36
  "@types/node": "^22.0.0",
31
- "@typescript/native-preview": "latest",
32
- "esbuild": "^0.25.4",
33
- "oxfmt": "0.52.0",
34
- "oxlint": "latest",
35
- "typescript": "~6.0.1-rc",
36
- "vitest": "^3.0.0",
37
- "@astrale-os/sdk": "^0.5.0-beta.6"
37
+ "esbuild": "^0.28.2",
38
+ "oxfmt": "0.63.0",
39
+ "oxlint": "1.78.0",
40
+ "typescript": "7.0.2",
41
+ "vitest": "^4.1.10",
42
+ "@astrale-os/sdk": "^0.5.0-beta.24"
38
43
  },
39
44
  "engines": {
40
45
  "node": ">=22"
41
46
  },
42
47
  "scripts": {
43
- "build": "node scripts/build.mjs",
44
- "typecheck": "tsgo --noEmit",
48
+ "build": "rm -rf dist && node scripts/build.mjs && node ../scripts/verify-no-source-maps.mjs dist",
49
+ "typecheck": "tsc --noEmit",
45
50
  "test": "vitest run",
46
51
  "test:generated": "node scripts/verify-generated.mjs",
47
52
  "lint": "oxlint .",
@@ -4,6 +4,10 @@
4
4
  # prod. These files are gitignored — copy this to `.env.dev` and fill in.
5
5
  #
6
6
  # Add one line per secret required by an Integration. Declare the corresponding
7
- # binding on `ApplicationEnvironment` in `implementation.ts`. For example:
7
+ # binding returned by `runtime.ts` initialization. For example:
8
8
  #
9
9
  # EXAMPLE_API_KEY=sk-...
10
+
11
+ # The Cloudflare adapter's execution identity is separate from Integration
12
+ # secrets. The scaffold creates `.astrale/identity.json`; retain and restore
13
+ # that file for production rather than copying its value into this dotenv file.
@@ -1,17 +1,18 @@
1
1
  # Astrale Domain
2
2
 
3
- This project uses the framework-neutral frontend escape hatch. `views/routes.ts` binds declared
4
- Views to a `frontendArtifact`; `views/app/` is an ordinary Vite root you can replace with any
3
+ This project uses the framework-neutral frontend escape hatch. `views/application/routes.ts` binds
4
+ declared Views to `defineFrontend`; `vite.config.ts` owns the ordinary Vite root you can replace with any
5
5
  framework; `ui/` owns presentation assets. The starter route uses `handshake: 'none'`, so it mounts
6
6
  as a plain hosted app. Change that route to `handshake: 'shell'` only after your browser entrypoint
7
7
  boots the Shell protocol.
8
8
 
9
9
  ```text
10
10
  schema/ public Domain contract, including View declarations
11
- implementation.ts schema, callable handlers, and frontend composition
12
- views/routes.ts framework-neutral artifact and routes
13
- views/app/ fully author-owned browser application
14
- ui/ presentation tokens and styles
11
+ runtime.ts Actions, Workflows, Integrations, Providers, and initialization
12
+ application.ts Schema, Runtime, and frontend composition
13
+ views/ framework-neutral frontend declaration
14
+ views/application/ application View, route, and browser entrypoint
15
+ ui/application/ application presentation, tokens, and styles
15
16
  tests/ cross-layer executable journeys
16
17
  ```
17
18
 
@@ -0,0 +1,13 @@
1
+ import { application } from '../application.js'
2
+
3
+ describe('custom frontend variant', () => {
4
+ /** @evidence SDK-V1-SCAFFOLD-CUSTOM */
5
+ it('rejects missing authored frontend composition', () => {
6
+ expect(application.frontend).toBeDefined()
7
+ expect(application.frontend?.source.kind).toBe('vite')
8
+ expect(application.frontend?.routes.application).toMatchObject({
9
+ path: '/application',
10
+ handshake: 'none',
11
+ })
12
+ })
13
+ })
@@ -0,0 +1,9 @@
1
+ import { defineApplication } from '@astrale-os/sdk/application'
2
+
3
+ import { schema } from '#schema'
4
+ import { frontend } from '#views'
5
+
6
+ import runtime from './runtime.js'
7
+
8
+ /** Build and publication composition; provider effects live in astrale.config.ts. */
9
+ export const application = defineApplication({ schema, runtime, frontend })
@@ -1,10 +1,8 @@
1
1
  import { defineSchema, view } from '@astrale-os/sdk/schema'
2
2
 
3
3
  export const schema = defineSchema('astrale-domain.example.dev', {
4
- interfaces: {},
5
4
  classes: {},
6
- functions: {},
7
5
  views: {
8
- application: view({ target: 'domain' }),
6
+ application: view({}),
9
7
  },
10
8
  })
@@ -0,0 +1,9 @@
1
+ import { applicationMarkup, applicationTheme } from '../index.js'
2
+
3
+ describe('application UI', () => {
4
+ it('owns the framework-neutral presentation', () => {
5
+ expect(applicationTheme.accent).toBe('#e3b866')
6
+ expect(applicationMarkup()).toContain('Custom frontend')
7
+ expect(applicationMarkup()).toContain('Ready to build.')
8
+ })
9
+ })
@@ -1,2 +1,13 @@
1
+ import './styles.css'
2
+
1
3
  /** Framework-neutral presentation tokens and styles for the application. */
2
4
  export const applicationTheme = Object.freeze({ accent: '#e3b866' })
5
+
6
+ /** Pure presentation owned by the application UI feature. */
7
+ export function applicationMarkup(): string {
8
+ return `
9
+ <p class="application-eyebrow">Custom frontend</p>
10
+ <h1>Ready to build.</h1>
11
+ <p>Replace the application browser entrypoint while preserving its View route.</p>
12
+ `
13
+ }
@@ -0,0 +1,15 @@
1
+ import { mountApplication } from '../application.view.js'
2
+
3
+ describe('application View', () => {
4
+ it('mounts the UI owned by the application feature', () => {
5
+ const application = { innerHTML: '' } as HTMLElement
6
+
7
+ mountApplication(application)
8
+
9
+ expect(application.innerHTML).toContain('Ready to build.')
10
+ })
11
+
12
+ it('rejects a missing browser root', () => {
13
+ expect(() => mountApplication(null)).toThrow('Custom frontend root #application is missing.')
14
+ })
15
+ })
@@ -0,0 +1,3 @@
1
+ import { mountApplication } from '../application.view.js'
2
+
3
+ mountApplication(document.querySelector<HTMLElement>('#application'))
@@ -0,0 +1,7 @@
1
+ import { applicationMarkup } from '#ui/application'
2
+
3
+ /** Mount the application presentation into its browser-owned root. */
4
+ export function mountApplication(application: HTMLElement | null): void {
5
+ if (application === null) throw new Error('Custom frontend root #application is missing.')
6
+ application.innerHTML = applicationMarkup()
7
+ }
@@ -1,13 +1,12 @@
1
- import { frontendArtifact, frontendRoute, viteFrontend } from '@astrale-os/sdk/view'
1
+ import { defineFrontend, vite } from '@astrale-os/sdk/view'
2
2
 
3
3
  import { schema } from '#schema'
4
4
 
5
5
  /** Framework-neutral escape hatch: swap Vite or the browser implementation without changing Views. */
6
- export const frontend = frontendArtifact({
6
+ export const frontend = defineFrontend({
7
7
  schema,
8
- source: viteFrontend({ root: './views/app' }),
8
+ source: vite(),
9
9
  routes: {
10
- application: frontendRoute({ path: '/ui/application', handshake: 'none' }),
10
+ application: { handshake: 'none' },
11
11
  },
12
- entrypoint: 'application',
13
12
  })
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from 'vite'
2
+
3
+ export default defineConfig({
4
+ root: 'views/application/app',
5
+ build: {
6
+ outDir: '../../../dist/frontend',
7
+ emptyOutDir: true,
8
+ },
9
+ })
@@ -4,7 +4,8 @@ This is a callable-only Domain with no frontend artifact or browser assets.
4
4
 
5
5
  ```text
6
6
  schema/ public Domain contract
7
- implementation.ts schema and callable handler composition
7
+ runtime.ts Actions, Workflows, Integrations, Providers, and initialization
8
+ application.ts Schema and Runtime composition
8
9
  tests/ cross-layer executable journeys
9
10
  ```
10
11
 
@@ -0,0 +1,8 @@
1
+ import { application } from '../application.js'
2
+
3
+ describe('none frontend variant', () => {
4
+ /** @evidence SDK-V1-SCAFFOLD-NONE */
5
+ it('rejects an implicit browser frontend by keeping Application headless', () => {
6
+ expect(application.frontend).toBeUndefined()
7
+ })
8
+ })
@@ -0,0 +1,8 @@
1
+ import { defineApplication } from '@astrale-os/sdk/application'
2
+
3
+ import { schema } from '#schema'
4
+
5
+ import runtime from './runtime.js'
6
+
7
+ /** Build and publication composition; provider effects live in astrale.config.ts. */
8
+ export const application = defineApplication({ schema, runtime })
@@ -1,7 +1,5 @@
1
1
  import { defineSchema } from '@astrale-os/sdk/schema'
2
2
 
3
3
  export const schema = defineSchema('astrale-domain.example.dev', {
4
- interfaces: {},
5
4
  classes: {},
6
- functions: {},
7
5
  })
@@ -1,23 +1,24 @@
1
1
  # Astrale Domain
2
2
 
3
- This project uses the managed React frontend path. `views/routes.ts` is Worker-safe metadata;
4
- `views/application/application.view.tsx` owns Domain integration; `ui/` owns pure presentation.
5
- The adapter generates the shell boot module under `views/app/`, builds it, publishes the declared
6
- route, and serves the immutable static files beside the callable Worker.
3
+ This project is an ordinary Vite React application. `views/routes.ts` is the small Worker-safe
4
+ delivery artifact; `frontend/` owns TanStack Router and browser composition; `ui/` owns reusable
5
+ presentation. It has no mandatory Shell runtime: add `@astrale-os/shell-react` independently when
6
+ the application genuinely needs Shell sessions, graph context, or View-stage integration.
7
7
 
8
8
  ```text
9
9
  schema/ public Domain contract, including View declarations
10
- implementation.ts schema, callable handlers, and frontend composition
11
- views/routes.ts typed route and browser-module coordinates
12
- views/application/ Domain-aware React View
13
- views/app/ managed browser build root
10
+ runtime.ts Actions, Workflows, Integrations, Providers, and initialization
11
+ application.ts Schema, Runtime, and frontend composition
12
+ views/ schema-bound frontend declaration
13
+ frontend/ Vite, file-based TanStack Router, and Tailwind entrypoint
14
14
  ui/ styles and pure presentation components
15
15
  tests/ cross-layer executable journeys
16
16
  ```
17
17
 
18
- Run `pnpm dev` for a local Worker, `pnpm build` to pressure-test Worker and frontend output,
18
+ Run `pnpm dev` for one Worker-facing URL with Vite HMR, `pnpm build` to qualify Worker and frontend output,
19
19
  and `pnpm prod` to deploy through the selected adapter. Add semantic `rules/`, `queries/`,
20
- `mutations/`, and `functions/` modules only when the Domain gains those responsibilities.
20
+ `mutations/`, and `actions/` modules only when the Domain gains those responsibilities.
21
21
 
22
22
  Domain projects intentionally own no `.spec` trees. Import authoring contracts through semantic
23
- `@astrale-os/sdk/*` subpaths; never import Kernel Core or DSL directly.
23
+ `@astrale-os/sdk/*` subpaths; never import Kernel Core or DSL directly. Routing remains a normal
24
+ TanStack Router concern; optional Astrale hooks remain normal React hooks inside route components.
@@ -0,0 +1,13 @@
1
+ import { application } from '../application.js'
2
+
3
+ describe('react frontend variant', () => {
4
+ /** @evidence SDK-V1-SCAFFOLD-REACT */
5
+ it('rejects missing authored frontend composition', () => {
6
+ expect(application.frontend).toBeDefined()
7
+ expect(application.frontend?.source.kind).toBe('vite')
8
+ expect(application.frontend?.routes.application).toMatchObject({
9
+ path: '/application',
10
+ handshake: 'none',
11
+ })
12
+ })
13
+ })
@@ -0,0 +1,9 @@
1
+ import { defineApplication } from '@astrale-os/sdk/application'
2
+
3
+ import { schema } from '#schema'
4
+ import { frontend } from '#views'
5
+
6
+ import runtime from './runtime.js'
7
+
8
+ /** Build and publication composition; provider effects live in astrale.config.ts. */
9
+ export const application = defineApplication({ schema, runtime, frontend })
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Astrale Domain</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "astrale-frontend",
3
+ "private": true,
4
+ "type": "module"
5
+ }