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

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 (45) hide show
  1. package/README.md +12 -0
  2. package/dist/index.js +199 -75
  3. package/package.json +10 -5
  4. package/template/.env.example +9 -0
  5. package/template/.gitignore.template +17 -0
  6. package/template/.variants/custom/README.md +20 -0
  7. package/template/.variants/custom/implementation.ts +14 -0
  8. package/template/.variants/custom/schema/application/index.ts +10 -0
  9. package/template/.variants/custom/ui/application/index.ts +2 -0
  10. package/template/.variants/custom/ui/application/styles.css +27 -0
  11. package/template/.variants/custom/ui/application/styles.d.ts +1 -0
  12. package/template/.variants/custom/ui/index.ts +1 -0
  13. package/template/.variants/custom/views/app/index.html +12 -0
  14. package/template/.variants/custom/views/app/main.ts +10 -0
  15. package/template/.variants/custom/views/application/__tests__/routes.test.ts +7 -0
  16. package/template/.variants/custom/views/application/routes.ts +13 -0
  17. package/template/.variants/custom/views/index.ts +1 -0
  18. package/template/.variants/none/README.md +13 -0
  19. package/template/.variants/none/implementation.ts +14 -0
  20. package/template/.variants/none/schema/application/index.ts +7 -0
  21. package/template/.variants/react/README.md +23 -0
  22. package/template/.variants/react/implementation.ts +16 -0
  23. package/template/.variants/react/schema/application/index.ts +10 -0
  24. package/template/.variants/react/ui/application/__tests__/application.test.tsx +13 -0
  25. package/template/.variants/react/ui/application/index.ts +1 -0
  26. package/template/.variants/react/ui/application/screen.tsx +18 -0
  27. package/template/.variants/react/ui/application/styles.css +34 -0
  28. package/template/.variants/react/ui/application/styles.d.ts +1 -0
  29. package/template/.variants/react/ui/index.ts +1 -0
  30. package/template/.variants/react/views/app/README.md +4 -0
  31. package/template/.variants/react/views/application/__tests__/routes.test.ts +7 -0
  32. package/template/.variants/react/views/application/application.view.tsx +19 -0
  33. package/template/.variants/react/views/application/routes.ts +16 -0
  34. package/template/.variants/react/views/index.ts +1 -0
  35. package/template/CLAUDE.md +21 -0
  36. package/template/index.ts +2 -0
  37. package/template/oxlint.config.ts +1 -0
  38. package/template/package.json +72 -0
  39. package/template/pnpm-workspace.yaml +46 -0
  40. package/template/schema/application/__tests__/schema.test.ts +11 -0
  41. package/template/schema/index.ts +1 -0
  42. package/template/tests/e2e/application.test.ts +9 -0
  43. package/template/tests/index.ts +2 -0
  44. package/template/tsconfig.json +70 -0
  45. package/template/vitest.config.ts +8 -0
package/README.md ADDED
@@ -0,0 +1,12 @@
1
+ # `create-astrale-domain`
2
+
3
+ Official scaffolder for a new Astrale Domain project.
4
+
5
+ During the SDK beta program, generate a project from the beta cohort explicitly:
6
+
7
+ ```bash
8
+ npx -y create-astrale-domain@beta my-domain --yes
9
+ ```
10
+
11
+ The generated project must resolve the SDK and deployment adapters from npm; it must not retain
12
+ workspace links or repository-only source paths.
package/dist/index.js CHANGED
@@ -779,28 +779,25 @@ var Y2 = ({ indicator: t = "dots" } = {}) => {
779
779
  import { existsSync as existsSync2 } from "node:fs";
780
780
  import { delimiter, join as join2, relative as relative2, resolve as resolve2 } from "node:path";
781
781
 
782
- // src/scaffold.ts
782
+ // src/scaffold/index.ts
783
783
  import { existsSync, readFileSync } from "node:fs";
784
784
  import { cp, readFile, rename, rm, writeFile } from "node:fs/promises";
785
785
  import { createRequire } from "node:module";
786
- import { dirname, isAbsolute, join, relative, resolve } from "node:path";
786
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
787
787
  import { fileURLToPath } from "node:url";
788
788
  var require2 = createRequire(import.meta.url);
789
789
  var HERE = dirname(fileURLToPath(import.meta.url));
790
- var WORKSPACE_ROOT = join(HERE, "..", "..", "..");
790
+ var CREATE_PACKAGE_ROOT = owningPackageRoot(HERE, "create-astrale-domain");
791
+ var SDK_ROOT = sdkCheckoutRoot(CREATE_PACKAGE_ROOT);
791
792
  var ORIGIN_PLACEHOLDER = "astrale-domain.example.dev";
792
793
  var INSTANCE_PLACEHOLDER = "my-instance-slug";
793
- var LINK_TARGETS = {
794
- "@astrale-os/sdk": "sdk",
795
- "@astrale-os/adapter-cloudflare": "sdk/adapter-cloudflare",
796
- "@astrale-os/adapter-astrale": "sdk/adapter-astrale",
797
- "@astrale-os/kernel-core": "kernel/core",
798
- "@astrale-os/kernel-dsl": "kernel/dsl",
799
- // The client SPA consumes the React shell surface directly.
800
- "@astrale-os/shell": "shell/packages/shell",
801
- "@astrale-os/shell-react": "shell/packages/shell-react"
802
- };
803
- var BODY_PACKAGE = "@astrale-os/adapter-cloudflare";
794
+ var LINK_PACKAGES = /* @__PURE__ */ new Set([
795
+ "@astrale-os/sdk",
796
+ "@astrale-os/adapter-cloudflare",
797
+ "@astrale-os/adapter-astrale",
798
+ "@astrale-os/shell",
799
+ "@astrale-os/shell-react"
800
+ ]);
804
801
  var ADAPTER_PACKAGES = {
805
802
  astrale: "@astrale-os/adapter-astrale",
806
803
  cloudflare: "@astrale-os/adapter-cloudflare"
@@ -816,46 +813,68 @@ function errorText(err) {
816
813
  if (err instanceof Error) return err.message;
817
814
  return err === void 0 ? "" : String(err);
818
815
  }
819
- function errorReport(err) {
820
- if (!(err instanceof Error)) return String(err);
821
- const cause = err.cause;
816
+ function errorReport(error) {
817
+ if (!(error instanceof Error)) return String(error);
818
+ const cause = error.cause;
822
819
  if (cause !== void 0) {
823
820
  const causeText = cause instanceof Error ? cause.message : String(cause);
824
- if (causeText && !err.message.includes(causeText)) {
825
- return `${err.message}
821
+ if (causeText && !error.message.includes(causeText)) {
822
+ return `${error.message}
826
823
  \u21B3 caused by: ${causeText}`;
827
824
  }
828
825
  }
829
- return err.message;
826
+ return error.message;
830
827
  }
831
- function packageRoot(pkg) {
832
- const direct = resolveModule(`${pkg}/package.json`);
828
+ function packageRoot(packageName) {
829
+ const workspace = workspacePackageRoot(packageName);
830
+ if (workspace !== void 0) return workspace;
831
+ const direct = resolveModule(`${packageName}/package.json`);
833
832
  if ("path" in direct) return dirname(direct.path);
834
- const entry = resolveModule(pkg);
835
- if ("path" in entry) {
836
- let dir = dirname(entry.path);
837
- for (; ; ) {
838
- const candidate = join(dir, "package.json");
839
- if (existsSync(candidate)) {
840
- try {
841
- const parsed = JSON.parse(readFileSync(candidate, "utf-8"));
842
- if (parsed.name === pkg) return dir;
843
- } catch {
844
- }
833
+ const reason = errorText(direct.error);
834
+ throw new Error(
835
+ `Could not locate ${packageName}/package.json \u2014 install a release exposing its package metadata.` + (reason ? ` The module resolver failed: ${reason}` : ""),
836
+ direct.error === void 0 ? void 0 : { cause: direct.error }
837
+ );
838
+ }
839
+ function workspacePackageRoot(packageName) {
840
+ if (SDK_ROOT === void 0) return void 0;
841
+ const candidates = {
842
+ "@astrale-os/sdk": SDK_ROOT,
843
+ "@astrale-os/adapter-astrale": join(SDK_ROOT, "adapter-astrale"),
844
+ "@astrale-os/adapter-cloudflare": join(SDK_ROOT, "adapter-cloudflare"),
845
+ "create-astrale-domain": join(SDK_ROOT, "create-astrale-domain")
846
+ };
847
+ const candidate = candidates[packageName];
848
+ if (candidate === void 0 || !existsSync(join(candidate, "package.json"))) return void 0;
849
+ return candidate;
850
+ }
851
+ function owningPackageRoot(start, name) {
852
+ let directory = start;
853
+ for (; ; ) {
854
+ const manifest = join(directory, "package.json");
855
+ if (existsSync(manifest)) {
856
+ try {
857
+ const parsed = JSON.parse(readFileSync(manifest, "utf8"));
858
+ if (parsed.name === name) return directory;
859
+ } catch {
845
860
  }
846
- const parent = dirname(dir);
847
- if (parent === dir) break;
848
- dir = parent;
849
861
  }
850
- throw new Error(
851
- `Could not locate the ${pkg} package.json (its entry resolved at ${entry.path}, but no ancestor package.json declares "name": "${pkg}").`
852
- );
862
+ const parent = dirname(directory);
863
+ if (parent === directory) break;
864
+ directory = parent;
865
+ }
866
+ throw new Error(`Could not locate the ${name} package root from ${start}.`);
867
+ }
868
+ function sdkCheckoutRoot(createRoot) {
869
+ const candidate = dirname(createRoot);
870
+ const manifest = join(candidate, "package.json");
871
+ if (!existsSync(manifest)) return void 0;
872
+ try {
873
+ const parsed = JSON.parse(readFileSync(manifest, "utf8"));
874
+ return parsed.name === "@astrale-os/sdk" ? candidate : void 0;
875
+ } catch {
876
+ return void 0;
853
877
  }
854
- const reason = errorText(entry.error);
855
- throw new Error(
856
- `Could not locate the ${pkg} package \u2014 is it installed? (run your package manager's install)` + (reason ? ` The module resolver failed: ${reason}` : ""),
857
- entry.error === void 0 ? void 0 : { cause: entry.error }
858
- );
859
878
  }
860
879
  function installedPackageRange(pkg) {
861
880
  const manifestPath = join(packageRoot(pkg), "package.json");
@@ -874,19 +893,22 @@ function installedPackageRange(pkg) {
874
893
  function templateDir(pkg) {
875
894
  return join(packageRoot(pkg), "template");
876
895
  }
877
- async function scaffold(opts) {
878
- const preExisting = existsSync(opts.dir);
896
+ function bodyTemplateDir() {
897
+ return join(CREATE_PACKAGE_ROOT, "template");
898
+ }
899
+ async function scaffold(options) {
900
+ const preExisting = existsSync(options.dir);
879
901
  try {
880
- await writeProject(opts);
902
+ await writeProject(options);
881
903
  } catch (err) {
882
- if (!preExisting && existsSync(opts.dir)) {
883
- await rm(opts.dir, { recursive: true, force: true });
904
+ if (!preExisting && existsSync(options.dir)) {
905
+ await rm(options.dir, { recursive: true, force: true });
884
906
  }
885
907
  throw err;
886
908
  }
887
909
  }
888
910
  async function writeProject(opts) {
889
- const src = templateDir(BODY_PACKAGE);
911
+ const src = bodyTemplateDir();
890
912
  if (!existsSync(src)) {
891
913
  throw new Error(`Template not found at ${src}.`);
892
914
  }
@@ -896,15 +918,15 @@ async function writeProject(opts) {
896
918
  }
897
919
  await cp(src, opts.dir, {
898
920
  recursive: true,
899
- filter: (s) => !/\/(node_modules|\.agents|\.astrale|\.dist|\.domain-studio|dist-client|\.wrangler|pnpm-lock\.yaml|\.DS_Store)(\/|$)/.test(
921
+ filter: (s) => !/\/(node_modules|client|\.agents|\.astrale|\.dist|\.domain-studio|\.variants|\.wrangler|pnpm-lock\.yaml|\.DS_Store)(\/|$)/.test(
900
922
  `/${relative(src, s)}`
901
923
  )
902
924
  });
903
- await materializeGitignores(opts.dir);
925
+ await materializeFrontend(opts, src);
926
+ await materializeGitignores(opts.dir, opts.frontend);
904
927
  await materializeAdapter(opts);
905
928
  await stampOrigin(opts.dir, opts.origin);
906
929
  await rewritePackageJson(opts.dir, opts.name, opts.link);
907
- await rewriteWorkspaceMember(join(opts.dir, "client", "package.json"), opts.link);
908
930
  if (opts.link) await writeLinkedZodOverride(opts.dir);
909
931
  else await writeReleaseAgePolicy(opts.dir);
910
932
  await writeFile(
@@ -912,8 +934,8 @@ async function writeProject(opts) {
912
934
  "# Dev secrets \u2014 the ENTIRE file is injected into the local runtime by `pnpm dev`.\n# Gitignored. See .env.example.\n"
913
935
  );
914
936
  }
915
- async function materializeGitignores(dir) {
916
- for (const path of [join(dir, ".gitignore"), join(dir, "client", ".gitignore")]) {
937
+ async function materializeGitignores(dir, frontend) {
938
+ for (const path of [join(dir, ".gitignore")]) {
917
939
  const packagedPath = `${path}.template`;
918
940
  if (existsSync(packagedPath)) {
919
941
  await rename(packagedPath, path);
@@ -921,6 +943,51 @@ async function materializeGitignores(dir) {
921
943
  throw new Error(`Template is missing packaged Git ignore file ${packagedPath}.`);
922
944
  }
923
945
  }
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
+ }
959
+ }
960
+ async function materializeFrontend(opts, template) {
961
+ const variant = join(template, ".variants", opts.frontend);
962
+ if (!existsSync(variant)) throw new Error(`Frontend template not found at ${variant}.`);
963
+ await cp(variant, opts.dir, { recursive: true });
964
+ const path = join(opts.dir, "package.json");
965
+ const pkg = JSON.parse(await readFile(path, "utf-8"));
966
+ pkg.dependencies ??= {};
967
+ pkg.devDependencies ??= {};
968
+ if (opts.frontend === "react") {
969
+ 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",
972
+ react: "^19.2.0",
973
+ "react-dom": "^19.2.0"
974
+ });
975
+ Object.assign(pkg.devDependencies, {
976
+ "@types/react": "^19.2.0",
977
+ "@types/react-dom": "^19.2.0",
978
+ vite: "^7.1.7"
979
+ });
980
+ } else if (opts.frontend === "custom") {
981
+ pkg.devDependencies.vite = "^7.1.7";
982
+ }
983
+ pkg.dependencies = sorted(pkg.dependencies);
984
+ pkg.devDependencies = sorted(pkg.devDependencies);
985
+ await writeFile(path, JSON.stringify(pkg, null, 2) + "\n");
986
+ }
987
+ function sorted(values) {
988
+ return Object.fromEntries(
989
+ Object.entries(values).sort(([left], [right]) => left.localeCompare(right))
990
+ );
924
991
  }
925
992
  function assertSafeToDelete(dir) {
926
993
  const target = resolve(dir);
@@ -932,40 +999,34 @@ function assertSafeToDelete(dir) {
932
999
  }
933
1000
  }
934
1001
  async function stampOrigin(dir, origin) {
935
- const schemaIndex = join(dir, "schema", "index.ts");
1002
+ const schemaIndex = join(dir, "schema", "application", "index.ts");
936
1003
  await replaceInFile(schemaIndex, ORIGIN_PLACEHOLDER, origin);
937
1004
  const config = join(dir, "astrale.config.ts");
938
1005
  await replaceInFile(config, ORIGIN_PLACEHOLDER, origin);
939
1006
  }
940
1007
  async function materializeAdapter(opts) {
941
- if (opts.adapter !== "astrale") return;
942
1008
  const config = join(opts.dir, "astrale.config.ts");
943
- await cp(join(templateDir(ADAPTER_PACKAGES.astrale), "astrale.config.ts"), config);
944
- await setAdapterDep(opts.dir, "@astrale-os/adapter-cloudflare", "@astrale-os/adapter-astrale");
1009
+ await cp(join(templateDir(ADAPTER_PACKAGES[opts.adapter]), "astrale.config.ts"), config);
1010
+ if (opts.adapter === "cloudflare") return;
1011
+ await addManagedAdapterDep(opts.dir);
945
1012
  if (opts.instance) await replaceInFile(config, `'${INSTANCE_PLACEHOLDER}'`, `'${opts.instance}'`);
946
1013
  }
947
- async function setAdapterDep(dir, from, to) {
1014
+ async function addManagedAdapterDep(dir) {
948
1015
  const path = join(dir, "package.json");
949
1016
  const pkg = JSON.parse(await readFile(path, "utf-8"));
950
- if (!pkg.dependencies || !(from in pkg.dependencies)) {
1017
+ if (!pkg.dependencies || !("@astrale-os/adapter-cloudflare" in pkg.dependencies)) {
951
1018
  throw new Error(
952
- `Could not materialize ${to}: the scaffold body does not declare its ${from} dependency.`
1019
+ "Could not materialize @astrale-os/adapter-astrale: the scaffold body does not declare its @astrale-os/adapter-cloudflare Worker runtime dependency."
953
1020
  );
954
1021
  }
955
- const range = installedPackageRange(to);
956
- delete pkg.dependencies[from];
957
- pkg.dependencies[to] = range;
1022
+ pkg.dependencies["@astrale-os/adapter-astrale"] = installedPackageRange(
1023
+ "@astrale-os/adapter-astrale"
1024
+ );
958
1025
  pkg.dependencies = Object.fromEntries(
959
1026
  Object.entries(pkg.dependencies).sort(([a], [b3]) => a.localeCompare(b3))
960
1027
  );
961
1028
  await writeFile(path, JSON.stringify(pkg, null, 2) + "\n");
962
1029
  }
963
- async function rewriteWorkspaceMember(pkgPath, link) {
964
- if (!existsSync(pkgPath)) return;
965
- const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
966
- const changed = rewriteAstraleDeps(pkg, link);
967
- if (changed) await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
968
- }
969
1030
  async function rewritePackageJson(dir, name, link) {
970
1031
  const path = join(dir, "package.json");
971
1032
  const pkg = JSON.parse(await readFile(path, "utf-8"));
@@ -979,8 +1040,8 @@ function rewriteAstraleDeps(pkg, link) {
979
1040
  for (const group of [pkg.dependencies, pkg.devDependencies]) {
980
1041
  if (!group) continue;
981
1042
  for (const dep of Object.keys(group)) {
982
- if (!dep.startsWith("@astrale-os/") || !LINK_TARGETS[dep]) continue;
983
- const next = `link:${join(WORKSPACE_ROOT, LINK_TARGETS[dep])}`;
1043
+ if (!LINK_PACKAGES.has(dep)) continue;
1044
+ const next = `link:${localPackageRoot(dep)}`;
984
1045
  if (group[dep] !== next) {
985
1046
  group[dep] = next;
986
1047
  changed = true;
@@ -1024,7 +1085,8 @@ function escapeRegExp(value) {
1024
1085
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1025
1086
  }
1026
1087
  function sdkZodVersion() {
1027
- const sdkRequire = createRequire(join(WORKSPACE_ROOT, "sdk", "package.json"));
1088
+ if (SDK_ROOT === void 0) throw new Error("Local SDK checkout is unavailable.");
1089
+ const sdkRequire = createRequire(join(SDK_ROOT, "package.json"));
1028
1090
  const pkg = sdkRequire("zod/package.json");
1029
1091
  return pkg.version;
1030
1092
  }
@@ -1035,7 +1097,32 @@ async function replaceInFile(path, from, to) {
1035
1097
  if (next !== text) await writeFile(path, next);
1036
1098
  }
1037
1099
  function workspaceAvailable() {
1038
- return existsSync(join(WORKSPACE_ROOT, "sdk", "package.json"));
1100
+ if (SDK_ROOT === void 0) return false;
1101
+ try {
1102
+ for (const dependency of LINK_PACKAGES) localPackageRoot(dependency);
1103
+ return true;
1104
+ } catch {
1105
+ return false;
1106
+ }
1107
+ }
1108
+ function localPackageRoot(dependency) {
1109
+ if (SDK_ROOT !== void 0) {
1110
+ const local = dependency === "@astrale-os/sdk" ? SDK_ROOT : dependency === "@astrale-os/adapter-cloudflare" ? join(SDK_ROOT, "adapter-cloudflare") : dependency === "@astrale-os/adapter-astrale" ? join(SDK_ROOT, "adapter-astrale") : void 0;
1111
+ if (local !== void 0 && existsSync(join(local, "package.json"))) return local;
1112
+ }
1113
+ const resolved = resolveModule(`${dependency}/package.json`);
1114
+ if ("path" in resolved) {
1115
+ const installed = dirname(resolved.path);
1116
+ if (!installed.split(sep).includes("node_modules")) return installed;
1117
+ }
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
+ throw new Error(`No active source package is available for ${dependency}.`);
1039
1126
  }
1040
1127
 
1041
1128
  // src/index.ts
@@ -1061,6 +1148,23 @@ var ADAPTERS = [
1061
1148
  },
1062
1149
  { value: "custom", label: "Custom", hint: "bring your own adapter (soon)", enabled: false }
1063
1150
  ];
1151
+ var FRONTENDS = [
1152
+ {
1153
+ value: "react",
1154
+ label: "React",
1155
+ hint: "managed shell integration with typed View component coordinates"
1156
+ },
1157
+ {
1158
+ value: "custom",
1159
+ label: "Custom",
1160
+ hint: "framework-neutral frontend artifact with a minimal Vite application"
1161
+ },
1162
+ {
1163
+ value: "none",
1164
+ label: "None",
1165
+ hint: "callable Domain only; no Views or browser assets"
1166
+ }
1167
+ ];
1064
1168
  async function main() {
1065
1169
  const argv = process.argv.slice(2);
1066
1170
  const flags = parseFlags(argv);
@@ -1109,6 +1213,23 @@ async function main() {
1109
1213
  xe(`The "${adapter}" adapter is not available yet \u2014 use "astrale" or "cloudflare".`);
1110
1214
  return 1;
1111
1215
  }
1216
+ let frontend = flags.frontend;
1217
+ if (!frontend) {
1218
+ if (headless) frontend = "react";
1219
+ else {
1220
+ const answer = await ve({
1221
+ message: "Frontend",
1222
+ initialValue: "react",
1223
+ options: [...FRONTENDS]
1224
+ });
1225
+ if (pD(answer)) return bail();
1226
+ frontend = answer;
1227
+ }
1228
+ }
1229
+ if (frontend !== "react" && frontend !== "custom" && frontend !== "none") {
1230
+ xe(`Unknown frontend "${frontend}" \u2014 use "react", "custom", or "none".`);
1231
+ return 1;
1232
+ }
1112
1233
  const instance = flags.instance;
1113
1234
  if (instance !== void 0 && !isValidSlug(instance)) {
1114
1235
  xe(`Invalid --instance "${instance}". Use lowercase letters, digits, dots and dashes.`);
@@ -1151,6 +1272,7 @@ async function main() {
1151
1272
  name: slug,
1152
1273
  origin,
1153
1274
  adapter,
1275
+ frontend,
1154
1276
  instance,
1155
1277
  link
1156
1278
  });
@@ -1220,6 +1342,7 @@ function parseFlags(argv) {
1220
1342
  else if (a === "--link") flags.link = true;
1221
1343
  else if (a === "--no-link") flags.link = false;
1222
1344
  else if (a === "--adapter") flags.adapter = argv[++i];
1345
+ else if (a === "--frontend") flags.frontend = argv[++i];
1223
1346
  else if (a === "--pm") flags.pm = argv[++i];
1224
1347
  else if (a === "--dir") flags.dir = argv[++i];
1225
1348
  else if (a === "--origin") flags.origin = argv[++i];
@@ -1242,6 +1365,7 @@ Arguments:
1242
1365
 
1243
1366
  Options:
1244
1367
  --adapter <id> deployment adapter \u2014 astrale (managed, default) | cloudflare
1368
+ --frontend <id> react (default) | custom | none
1245
1369
  --instance <slug> target instance for managed deploys (astrale adapter)
1246
1370
  --pm <pm> pnpm | npm | yarn | bun (default: detected, else pnpm)
1247
1371
  --dir <path> target directory (default: ./<slug>)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-astrale-domain",
3
- "version": "0.2.13",
3
+ "version": "0.3.0-beta.2",
4
4
  "description": "Scaffold a standalone Astrale domain — npm create astrale-domain",
5
5
  "keywords": [
6
6
  "astrale",
@@ -14,13 +14,16 @@
14
14
  "create-astrale-domain": "./dist/index.js"
15
15
  },
16
16
  "files": [
17
- "dist"
17
+ "dist",
18
+ "template",
19
+ "!template/node_modules",
20
+ "!template/pnpm-lock.yaml"
18
21
  ],
19
22
  "type": "module",
20
23
  "dependencies": {
21
24
  "@clack/prompts": "^0.11.0",
22
- "@astrale-os/adapter-astrale": "^0.4.1",
23
- "@astrale-os/adapter-cloudflare": "^0.4.10"
25
+ "@astrale-os/adapter-cloudflare": "^0.5.0-beta.9",
26
+ "@astrale-os/adapter-astrale": "^0.5.0-beta.2"
24
27
  },
25
28
  "devDependencies": {
26
29
  "@astrale-os/ox": ">=0.1.0 <1.0.0",
@@ -30,7 +33,8 @@
30
33
  "oxfmt": "0.52.0",
31
34
  "oxlint": "latest",
32
35
  "typescript": "~6.0.1-rc",
33
- "vitest": "^3.0.0"
36
+ "vitest": "^3.0.0",
37
+ "@astrale-os/sdk": "^0.5.0-beta.6"
34
38
  },
35
39
  "engines": {
36
40
  "node": ">=22"
@@ -39,6 +43,7 @@
39
43
  "build": "node scripts/build.mjs",
40
44
  "typecheck": "tsgo --noEmit",
41
45
  "test": "vitest run",
46
+ "test:generated": "node scripts/verify-generated.mjs",
42
47
  "lint": "oxlint .",
43
48
  "format": "pnpm exec oxfmt --write ."
44
49
  }
@@ -0,0 +1,9 @@
1
+ # Secrets for an env live in `.env.<env>` (e.g. .env.dev, .env.prod), pointed at
2
+ # by `secrets:` in astrale.config.ts. The ENTIRE file is treated as secrets:
3
+ # injected into the local runtime in dev, pushed to the Worker secret store in
4
+ # prod. These files are gitignored — copy this to `.env.dev` and fill in.
5
+ #
6
+ # Add one line per secret required by an Integration. Declare the corresponding
7
+ # binding on `ApplicationEnvironment` in `implementation.ts`. For example:
8
+ #
9
+ # EXAMPLE_API_KEY=sk-...
@@ -0,0 +1,17 @@
1
+ # Deps
2
+ node_modules/
3
+
4
+ # Generated plumbing (worker entry, wrangler config, identity, spec)
5
+ .astrale/
6
+ .domain-studio/.cache/
7
+
8
+ # Worker dev + build artifacts
9
+ .dev.vars
10
+ .wrangler/
11
+ .dist/
12
+ dist/
13
+
14
+ # Secrets — every .env.<env> is gitignored; .env.example is the only one tracked
15
+ .env
16
+ .env.*
17
+ !.env.example
@@ -0,0 +1,20 @@
1
+ # Astrale Domain
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
5
+ framework; `ui/` owns presentation assets. The starter route uses `handshake: 'none'`, so it mounts
6
+ as a plain hosted app. Change that route to `handshake: 'shell'` only after your browser entrypoint
7
+ boots the Shell protocol.
8
+
9
+ ```text
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
15
+ tests/ cross-layer executable journeys
16
+ ```
17
+
18
+ Run `pnpm dev` for local delivery and `pnpm build` to build both the Worker and browser assets.
19
+ Domain projects intentionally own no `.spec` trees and import authoring contracts only through
20
+ semantic `@astrale-os/sdk/*` subpaths.
@@ -0,0 +1,14 @@
1
+ import { defineDomain, type DomainHandlers } from '@astrale-os/sdk'
2
+
3
+ import { schema } from '#schema'
4
+ import { frontend } from '#views'
5
+
6
+ export interface ApplicationEnvironment {}
7
+
8
+ const handlers = {
9
+ functions: {},
10
+ classes: {},
11
+ interfaces: {},
12
+ } satisfies DomainHandlers<typeof schema, ApplicationEnvironment>
13
+
14
+ export const domain = defineDomain({ schema, handlers, frontend })
@@ -0,0 +1,10 @@
1
+ import { defineSchema, view } from '@astrale-os/sdk/schema'
2
+
3
+ export const schema = defineSchema('astrale-domain.example.dev', {
4
+ interfaces: {},
5
+ classes: {},
6
+ functions: {},
7
+ views: {
8
+ application: view({ target: 'domain' }),
9
+ },
10
+ })
@@ -0,0 +1,2 @@
1
+ /** Framework-neutral presentation tokens and styles for the application. */
2
+ export const applicationTheme = Object.freeze({ accent: '#e3b866' })
@@ -0,0 +1,27 @@
1
+ :root {
2
+ color: #f5f3ed;
3
+ background: #171714;
4
+ font-family: Inter, ui-sans-serif, system-ui, sans-serif;
5
+ }
6
+
7
+ body {
8
+ box-sizing: border-box;
9
+ display: grid;
10
+ min-height: 100vh;
11
+ align-content: center;
12
+ max-width: 64rem;
13
+ padding: 4rem;
14
+ margin: 0 auto;
15
+ }
16
+
17
+ h1 {
18
+ margin: 0;
19
+ font-size: clamp(3rem, 9vw, 7rem);
20
+ line-height: 0.92;
21
+ }
22
+
23
+ .application-eyebrow {
24
+ color: #e3b866;
25
+ font-family: ui-monospace, monospace;
26
+ text-transform: uppercase;
27
+ }
@@ -0,0 +1 @@
1
+ declare module '*.css' {}
@@ -0,0 +1 @@
1
+ export { applicationTheme } from './application/index.js'
@@ -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
+ <main id="application">Loading…</main>
10
+ <script type="module" src="/main.ts"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,10 @@
1
+ import '../../ui/application/styles.css'
2
+
3
+ const application = document.querySelector<HTMLElement>('#application')
4
+ if (application === null) throw new Error('Custom frontend root #application is missing.')
5
+
6
+ application.innerHTML = `
7
+ <p class="application-eyebrow">Custom frontend</p>
8
+ <h1>Ready to build.</h1>
9
+ <p>Replace views/app with any framework while preserving views/application/routes.ts.</p>
10
+ `
@@ -0,0 +1,7 @@
1
+ import { frontend } from '../routes.js'
2
+
3
+ describe('application View routes', () => {
4
+ it('owns the generated application entrypoint', () => {
5
+ expect(frontend.entrypoint).toBe('application')
6
+ })
7
+ })
@@ -0,0 +1,13 @@
1
+ import { frontendArtifact, frontendRoute, viteFrontend } from '@astrale-os/sdk/view'
2
+
3
+ import { schema } from '#schema'
4
+
5
+ /** Framework-neutral escape hatch: swap Vite or the browser implementation without changing Views. */
6
+ export const frontend = frontendArtifact({
7
+ schema,
8
+ source: viteFrontend({ root: './views/app' }),
9
+ routes: {
10
+ application: frontendRoute({ path: '/ui/application', handshake: 'none' }),
11
+ },
12
+ entrypoint: 'application',
13
+ })
@@ -0,0 +1 @@
1
+ export { frontend } from './application/routes.js'
@@ -0,0 +1,13 @@
1
+ # Astrale Domain
2
+
3
+ This is a callable-only Domain with no frontend artifact or browser assets.
4
+
5
+ ```text
6
+ schema/ public Domain contract
7
+ implementation.ts schema and callable handler composition
8
+ tests/ cross-layer executable journeys
9
+ ```
10
+
11
+ Run `pnpm dev` for a local Worker and `pnpm build` to qualify its deploy bundle. Add `views/` and
12
+ `ui/` later only when the schema gains declared Views. Domain projects intentionally own no `.spec`
13
+ trees and import authoring contracts only through semantic `@astrale-os/sdk/*` subpaths.
@@ -0,0 +1,14 @@
1
+ import { defineDomain, type DomainHandlers } from '@astrale-os/sdk'
2
+
3
+ import { schema } from '#schema'
4
+
5
+ export interface ApplicationEnvironment {}
6
+
7
+ const handlers = {
8
+ functions: {},
9
+ classes: {},
10
+ interfaces: {},
11
+ } satisfies DomainHandlers<typeof schema, ApplicationEnvironment>
12
+
13
+ /** Callable-only composition: no View declarations, frontend artifact, or browser build. */
14
+ export const domain = defineDomain({ schema, handlers })
@@ -0,0 +1,7 @@
1
+ import { defineSchema } from '@astrale-os/sdk/schema'
2
+
3
+ export const schema = defineSchema('astrale-domain.example.dev', {
4
+ interfaces: {},
5
+ classes: {},
6
+ functions: {},
7
+ })
@@ -0,0 +1,23 @@
1
+ # Astrale Domain
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.
7
+
8
+ ```text
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
14
+ ui/ styles and pure presentation components
15
+ tests/ cross-layer executable journeys
16
+ ```
17
+
18
+ Run `pnpm dev` for a local Worker, `pnpm build` to pressure-test Worker and frontend output,
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.
21
+
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.
@@ -0,0 +1,16 @@
1
+ import { defineDomain, type DomainHandlers } from '@astrale-os/sdk'
2
+
3
+ import { schema } from '#schema'
4
+ import { frontend } from '#views'
5
+
6
+ /** External bindings are added here only when an Integration requires them. */
7
+ export interface ApplicationEnvironment {}
8
+
9
+ const handlers = {
10
+ functions: {},
11
+ classes: {},
12
+ interfaces: {},
13
+ } satisfies DomainHandlers<typeof schema, ApplicationEnvironment>
14
+
15
+ /** Worker-safe composition: schema, callables, and serializable frontend metadata. */
16
+ export const domain = defineDomain({ schema, handlers, frontend })
@@ -0,0 +1,10 @@
1
+ import { defineSchema, view } from '@astrale-os/sdk/schema'
2
+
3
+ export const schema = defineSchema('astrale-domain.example.dev', {
4
+ interfaces: {},
5
+ classes: {},
6
+ functions: {},
7
+ views: {
8
+ application: view({ target: 'domain' }),
9
+ },
10
+ })
@@ -0,0 +1,13 @@
1
+ import { renderToStaticMarkup } from 'react-dom/server'
2
+
3
+ import { ApplicationScreen } from '../index.js'
4
+
5
+ describe('application UI', () => {
6
+ it('renders presentation props without a Domain dependency', () => {
7
+ const html = renderToStaticMarkup(
8
+ <ApplicationScreen eyebrow="crm.example.dev" title="Ready" detail="Mounted" />,
9
+ )
10
+ expect(html).toContain('crm.example.dev')
11
+ expect(html).toContain('Ready')
12
+ })
13
+ })
@@ -0,0 +1 @@
1
+ export { ApplicationScreen, type ApplicationScreenProps } from './screen.js'
@@ -0,0 +1,18 @@
1
+ import './styles.css'
2
+
3
+ export interface ApplicationScreenProps {
4
+ readonly eyebrow: string
5
+ readonly title: string
6
+ readonly detail: string
7
+ }
8
+
9
+ /** Pure presentation: no graph, routing, credentials, or Domain imports. */
10
+ export function ApplicationScreen({ eyebrow, title, detail }: ApplicationScreenProps) {
11
+ return (
12
+ <main className="application-shell">
13
+ <p className="application-eyebrow">{eyebrow}</p>
14
+ <h1>{title}</h1>
15
+ <p>{detail}</p>
16
+ </main>
17
+ )
18
+ }
@@ -0,0 +1,34 @@
1
+ :root {
2
+ color: #f5f3ed;
3
+ background: #171714;
4
+ font-family: Inter, ui-sans-serif, system-ui, sans-serif;
5
+ }
6
+
7
+ body {
8
+ margin: 0;
9
+ }
10
+
11
+ .application-shell {
12
+ box-sizing: border-box;
13
+ display: grid;
14
+ min-height: 100vh;
15
+ align-content: center;
16
+ gap: 1rem;
17
+ max-width: 64rem;
18
+ padding: 4rem;
19
+ margin: 0 auto;
20
+ }
21
+
22
+ .application-shell h1 {
23
+ margin: 0;
24
+ font-size: clamp(3rem, 9vw, 7rem);
25
+ line-height: 0.92;
26
+ letter-spacing: -0.06em;
27
+ }
28
+
29
+ .application-eyebrow {
30
+ color: #e3b866;
31
+ font-family: ui-monospace, monospace;
32
+ text-transform: uppercase;
33
+ letter-spacing: 0.12em;
34
+ }
@@ -0,0 +1 @@
1
+ declare module '*.css' {}
@@ -0,0 +1 @@
1
+ export { ApplicationScreen, type ApplicationScreenProps } from './application/index.js'
@@ -0,0 +1,4 @@
1
+ # Managed React entrypoint
2
+
3
+ The adapter generates `index.html` and `astrale.generated.tsx` here from `views/routes.ts` before
4
+ Vite builds the frontend. Keep browser build configuration here only when the application needs it.
@@ -0,0 +1,7 @@
1
+ import { frontend } from '../routes.js'
2
+
3
+ describe('application View routes', () => {
4
+ it('owns the generated application entrypoint', () => {
5
+ expect(frontend.entrypoint).toBe('application')
6
+ })
7
+ })
@@ -0,0 +1,19 @@
1
+ import type { ReactViewProps } from '@astrale-os/sdk/react'
2
+
3
+ import { Domain } from '@astrale-os/sdk/domain'
4
+
5
+ import { schema } from '#schema'
6
+ import { ApplicationScreen } from '#ui/application'
7
+
8
+ const application = Domain.fromSchema(schema).application
9
+
10
+ /** Domain-aware View integration; Queries, Rules, and named actions belong here. */
11
+ export default function ApplicationView({ target, route }: ReactViewProps<typeof application>) {
12
+ return (
13
+ <ApplicationScreen
14
+ eyebrow={target.origin}
15
+ title="Ready to build."
16
+ detail={`Mounted as ${route.name} at ${route.href}`}
17
+ />
18
+ )
19
+ }
@@ -0,0 +1,16 @@
1
+ import { reactFrontend, reactRoute } from '@astrale-os/sdk/react'
2
+
3
+ import { schema } from '#schema'
4
+
5
+ /** Worker-safe route metadata. Browser modules are coordinates, never Worker imports. */
6
+ export const frontend = reactFrontend({
7
+ schema,
8
+ schemaModule: { module: './schema/index.ts', export: 'schema' },
9
+ routes: {
10
+ application: reactRoute({
11
+ path: '/ui/application',
12
+ component: { module: './views/application/application.view.tsx' },
13
+ }),
14
+ },
15
+ entrypoint: 'application',
16
+ })
@@ -0,0 +1 @@
1
+ export { frontend } from './application/routes.js'
@@ -0,0 +1,21 @@
1
+ # Astrale domain
2
+
3
+ Load the `astrale-domain` skill before changing Schema, Rules, Queries, Mutations, Functions,
4
+ Workflows, Capabilities, Integrations, Views, UI, migrations, or security. Install it user-level if
5
+ needed:
6
+
7
+ ```bash
8
+ npx skills add astrale-os/cli -g
9
+ ```
10
+
11
+ This scaffold is intentionally minimal. React and custom projects include one schema-declared
12
+ application View to prove frontend delivery; `--frontend none` is callable-only. Start each real
13
+ behavior in its semantic submodule, keep owners explicit, put focused tests in `__tests__`, use
14
+ registered package `#` imports across owners, and keep `implementation.ts` limited to composition.
15
+ Functions perform one asynchronous operation or bind one Workflow; Workflows name every durable
16
+ Step.
17
+
18
+ Domain projects do not contain `.spec` directories. Do not add one: public SDK types, package
19
+ facades, the Domain knowledge rules, and executable tests are the contract guardrails. Import Core
20
+ and DSL authoring values only from the matching `@astrale-os/sdk/*` semantic subpath; never import
21
+ `@astrale-os/kernel-core` or `@astrale-os/kernel-dsl` directly.
@@ -0,0 +1,2 @@
1
+ export { domain } from './implementation.js'
2
+ export { schema } from '#schema'
@@ -0,0 +1 @@
1
+ export { default } from '@astrale-os/sdk/linter/oxlint-config'
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "astrale-domain",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "imports": {
7
+ "#capabilities": "./capabilities/index.ts",
8
+ "#capabilities/*": "./capabilities/*/index.ts",
9
+ "#functions": "./functions/index.ts",
10
+ "#functions/*": "./functions/*/index.ts",
11
+ "#integrations": "./integrations/index.ts",
12
+ "#integrations/*": "./integrations/*/index.ts",
13
+ "#migrations": "./migrations/index.ts",
14
+ "#migrations/*": "./migrations/*/index.ts",
15
+ "#mutations": "./mutations/index.ts",
16
+ "#mutations/*": "./mutations/*/index.ts",
17
+ "#queries": "./queries/index.ts",
18
+ "#queries/*": "./queries/*/index.ts",
19
+ "#rules": "./rules/index.ts",
20
+ "#rules/*": "./rules/*/index.ts",
21
+ "#schema": "./schema/index.ts",
22
+ "#schema/*": "./schema/*/index.ts",
23
+ "#scripts": "./scripts/index.ts",
24
+ "#scripts/*": "./scripts/*/index.ts",
25
+ "#states": "./states/index.ts",
26
+ "#states/*": "./states/*/index.ts",
27
+ "#tests": "./tests/index.ts",
28
+ "#tests/*": "./tests/*/index.ts",
29
+ "#ui": "./ui/index.ts",
30
+ "#ui/*": "./ui/*/index.ts",
31
+ "#utils": "./utils/index.ts",
32
+ "#utils/*": "./utils/*/index.ts",
33
+ "#views": "./views/index.ts",
34
+ "#views/*": "./views/*/index.ts",
35
+ "#workflows": "./workflows/index.ts",
36
+ "#workflows/*": "./workflows/*/index.ts"
37
+ },
38
+ "scripts": {
39
+ "dev": "astrale-domain dev",
40
+ "prod": "astrale-domain prod",
41
+ "deploy": "astrale-domain deploy",
42
+ "build": "astrale-domain build",
43
+ "lint": "astrale-domain lint",
44
+ "lint:fix": "astrale-domain lint --fix",
45
+ "typecheck": "tsgo --noEmit",
46
+ "test": "vitest run --config vitest.config.ts"
47
+ },
48
+ "dependencies": {
49
+ "@astrale-os/adapter-cloudflare": ">=0.5.0-beta.7 <1.0.0",
50
+ "@astrale-os/sdk": ">=0.5.0-beta.4 <1.0.0",
51
+ "@hono/node-server": "^1.19.0",
52
+ "zod": "^4.3.6"
53
+ },
54
+ "devDependencies": {
55
+ "@astrale-os/ox": ">=0.1.3 <1.0.0",
56
+ "@types/node": "^22.0.0",
57
+ "@typescript/native-preview": "latest",
58
+ "oxlint": ">=1.72.0 <2.0.0",
59
+ "typescript": "~6.0.1-rc",
60
+ "vitest": "^3.2.4",
61
+ "wrangler": "4.121.0"
62
+ },
63
+ "engines": {
64
+ "node": ">=22.13"
65
+ },
66
+ "packageManager": "pnpm@11.13.1",
67
+ "trustedDependencies": [
68
+ "esbuild",
69
+ "sharp",
70
+ "workerd"
71
+ ]
72
+ }
@@ -0,0 +1,46 @@
1
+ # pnpm blocks dependency build scripts by default; these need their postinstall
2
+ # to run (workerd is wrangler's runtime — without it `pnpm dev`/`pnpm prod`
3
+ # fail). Internal guarded preinstalls are explicitly denied because they are
4
+ # no-ops outside the umbrella workspace.
5
+ allowBuilds:
6
+ esbuild: true
7
+ sharp: true
8
+ workerd: true
9
+ # Explicit `false` for the @astrale-os no-op preinstalls — without these,
10
+ # `pnpm add` of a new version re-prompts and writes placeholder entries here.
11
+ '@astrale-os/kernel-api': false
12
+ '@astrale-os/kernel-client': false
13
+ '@astrale-os/kernel-core': false
14
+ '@astrale-os/kernel-dsl': false
15
+ '@astrale-os/kernel-ports': false
16
+ '@astrale-os/kernel-protocol': false
17
+ '@astrale-os/kernel-runtime': false
18
+ '@astrale-os/kernel-server': false
19
+ '@astrale-os/sdk': false
20
+ '@astrale-os/shell': false
21
+ '@astrale-os/shell-react': false
22
+ # Optional native msgpack acceleration; the JS fallback is the portable
23
+ # scaffold baseline and does not require executing a transitive build script.
24
+ msgpackr-extract: false
25
+
26
+ # @astrale-os packages ship a guarded no-op preinstall (a workspace-dev guard
27
+ # that exits instantly outside the monorepo); declare them intentionally
28
+ # ignored so pnpm's build gate stays quiet.
29
+ ignoredBuiltDependencies:
30
+ - '@astrale-os/kernel-api'
31
+ - '@astrale-os/kernel-client'
32
+ - '@astrale-os/kernel-core'
33
+ - '@astrale-os/kernel-dsl'
34
+ - '@astrale-os/kernel-ports'
35
+ - '@astrale-os/kernel-protocol'
36
+ - '@astrale-os/kernel-runtime'
37
+ - '@astrale-os/kernel-server'
38
+ - '@astrale-os/sdk'
39
+ - '@astrale-os/shell'
40
+ - '@astrale-os/shell-react'
41
+
42
+ # Keep pnpm 11's one-day default non-strict. Never install implicitly before
43
+ # `pnpm run` or `pnpm exec`; the ignored preinstalls above can otherwise make
44
+ # its dependency-status check misfire.
45
+ minimumReleaseAgeStrict: false
46
+ verifyDepsBeforeRun: false
@@ -0,0 +1,11 @@
1
+ import { Domain } from '@astrale-os/sdk/domain'
2
+
3
+ import { schema } from '../index.js'
4
+
5
+ describe('application schema', () => {
6
+ it('owns one canonical Domain schema', () => {
7
+ const domain = Domain.fromSchema(schema)
8
+ expect(domain.$.schema).toBe(schema)
9
+ expect(domain.$.origin).toBe(schema.origin)
10
+ })
11
+ })
@@ -0,0 +1 @@
1
+ export { schema } from './application/index.js'
@@ -0,0 +1,9 @@
1
+ import { domain, schema } from '../../index.js'
2
+
3
+ describe('application composition', () => {
4
+ it('assembles the canonical Domain implementation', () => {
5
+ expect(domain.schema).toBe(schema)
6
+ expect(domain.domain.$.schema).toBe(schema)
7
+ expect(domain.handlers).toEqual({ functions: {}, classes: {}, interfaces: {} })
8
+ })
9
+ })
@@ -0,0 +1,2 @@
1
+ /** Public facade for reusable system-test environments and scenarios. */
2
+ export {}
@@ -0,0 +1,70 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "jsx": "react-jsx",
8
+ "types": ["node", "vitest/globals"],
9
+ "paths": {
10
+ "#capabilities": ["./capabilities/index.ts"],
11
+ "#capabilities/*": ["./capabilities/*/index.ts"],
12
+ "#functions": ["./functions/index.ts"],
13
+ "#functions/*": ["./functions/*/index.ts"],
14
+ "#integrations": ["./integrations/index.ts"],
15
+ "#integrations/*": ["./integrations/*/index.ts"],
16
+ "#migrations": ["./migrations/index.ts"],
17
+ "#migrations/*": ["./migrations/*/index.ts"],
18
+ "#mutations": ["./mutations/index.ts"],
19
+ "#mutations/*": ["./mutations/*/index.ts"],
20
+ "#queries": ["./queries/index.ts"],
21
+ "#queries/*": ["./queries/*/index.ts"],
22
+ "#rules": ["./rules/index.ts"],
23
+ "#rules/*": ["./rules/*/index.ts"],
24
+ "#schema": ["./schema/index.ts"],
25
+ "#schema/*": ["./schema/*/index.ts"],
26
+ "#scripts": ["./scripts/index.ts"],
27
+ "#scripts/*": ["./scripts/*/index.ts"],
28
+ "#states": ["./states/index.ts"],
29
+ "#states/*": ["./states/*/index.ts"],
30
+ "#tests": ["./tests/index.ts"],
31
+ "#tests/*": ["./tests/*/index.ts"],
32
+ "#ui": ["./ui/index.ts"],
33
+ "#ui/*": ["./ui/*/index.ts"],
34
+ "#utils": ["./utils/index.ts"],
35
+ "#utils/*": ["./utils/*/index.ts"],
36
+ "#views": ["./views/index.ts"],
37
+ "#views/*": ["./views/*/index.ts"],
38
+ "#workflows": ["./workflows/index.ts"],
39
+ "#workflows/*": ["./workflows/*/index.ts"]
40
+ },
41
+ "strict": true,
42
+ "noEmit": true,
43
+ "allowImportingTsExtensions": true,
44
+ "skipLibCheck": true,
45
+ "verbatimModuleSyntax": true,
46
+ "esModuleInterop": true,
47
+ "resolveJsonModule": true
48
+ },
49
+ "include": [
50
+ "schema",
51
+ "states",
52
+ "rules",
53
+ "queries",
54
+ "mutations",
55
+ "workflows",
56
+ "migrations",
57
+ "capabilities",
58
+ "functions",
59
+ "integrations",
60
+ "views",
61
+ "ui",
62
+ "utils",
63
+ "scripts",
64
+ "tests",
65
+ "implementation.ts",
66
+ "index.ts",
67
+ "astrale.config.ts"
68
+ ],
69
+ "exclude": ["node_modules", ".dist", ".wrangler", "**/__tests__/**"]
70
+ }
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'vitest/config'
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ include: ['**/__tests__/**/*.test.{ts,tsx}', 'tests/**/*.test.{ts,tsx}'],
7
+ },
8
+ })