create-astrale-domain 0.3.0-beta.4 → 0.3.0-beta.41

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 (55) hide show
  1. package/README.md +12 -1
  2. package/dist/index.js +94 -101
  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/{app → application/app}/index.html +1 -0
  13. package/template/.variants/custom/views/application/app/main.ts +3 -0
  14. package/template/.variants/custom/views/application/app/public/favicon.svg +4 -0
  15. package/template/.variants/custom/views/application/application.view.ts +7 -0
  16. package/template/.variants/custom/views/application/routes.ts +4 -5
  17. package/template/.variants/custom/vite.config.ts +9 -0
  18. package/template/.variants/none/README.md +2 -1
  19. package/template/.variants/none/__tests__/variant.test.ts +8 -0
  20. package/template/.variants/none/application.ts +8 -0
  21. package/template/.variants/none/schema/application/index.ts +0 -2
  22. package/template/.variants/react/README.md +13 -12
  23. package/template/.variants/react/__tests__/variant.test.ts +13 -0
  24. package/template/.variants/react/application.ts +9 -0
  25. package/template/.variants/react/frontend/index.html +13 -0
  26. package/template/.variants/react/frontend/package.json +5 -0
  27. package/template/.variants/react/frontend/public/favicon.svg +4 -0
  28. package/template/.variants/react/frontend/src/main.tsx +15 -0
  29. package/template/.variants/react/frontend/src/routeTree.gen.ts +77 -0
  30. package/template/.variants/react/frontend/src/router.tsx +11 -0
  31. package/template/.variants/react/frontend/src/routes/__root.tsx +30 -0
  32. package/template/.variants/react/frontend/src/routes/about.tsx +16 -0
  33. package/template/.variants/react/frontend/src/routes/index.tsx +16 -0
  34. package/template/.variants/react/frontend/src/styles.css +19 -0
  35. package/template/.variants/react/frontend/src/vite-env.d.ts +1 -0
  36. package/template/.variants/react/schema/application/index.ts +1 -3
  37. package/template/.variants/react/ui/application/screen.tsx +6 -6
  38. package/template/.variants/react/views/application/routes.ts +5 -9
  39. package/template/.variants/react/vite.config.ts +36 -0
  40. package/template/CLAUDE.md +10 -5
  41. package/template/index.ts +2 -1
  42. package/template/package.json +37 -16
  43. package/template/pnpm-workspace.yaml +39 -35
  44. package/template/runtime.ts +13 -0
  45. package/template/schema/application/__tests__/schema.test.ts +4 -4
  46. package/template/tests/e2e/application.test.ts +6 -5
  47. package/template/tsconfig.json +13 -10
  48. package/template/.variants/custom/implementation.ts +0 -14
  49. package/template/.variants/custom/views/app/main.ts +0 -10
  50. package/template/.variants/none/implementation.ts +0 -14
  51. package/template/.variants/react/implementation.ts +0 -16
  52. package/template/.variants/react/ui/application/styles.css +0 -34
  53. package/template/.variants/react/ui/application/styles.d.ts +0 -1
  54. package/template/.variants/react/views/app/README.md +0 -4
  55. package/template/.variants/react/views/application/application.view.tsx +0 -19
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,14 @@ 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 only the public Schema contract, normalizes
21
+ emitted declarations to semantic SDK facades, and verifies the package boundary. Runtime, handlers,
22
+ Providers, frontend code, and deployment adapters remain implementation inputs and are not part of
23
+ the published Domain contract package.
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",
@@ -927,8 +930,11 @@ async function writeProject(opts) {
927
930
  await materializeAdapter(opts);
928
931
  await stampOrigin(opts.dir, opts.origin);
929
932
  await rewritePackageJson(opts.dir, opts.name, opts.link);
930
- if (opts.link) await writeLinkedZodOverride(opts.dir);
931
- else await writeReleaseAgePolicy(opts.dir);
933
+ await signingIdentity({
934
+ projectDir: opts.dir,
935
+ origin: opts.origin,
936
+ mode: "local"
937
+ });
932
938
  await writeFile(
933
939
  join(opts.dir, ".env.dev"),
934
940
  "# Dev secrets \u2014 the ENTIRE file is injected into the local runtime by `pnpm dev`.\n# Gitignored. See .env.example.\n"
@@ -943,42 +949,41 @@ async function materializeGitignores(dir, frontend) {
943
949
  throw new Error(`Template is missing packaged Git ignore file ${packagedPath}.`);
944
950
  }
945
951
  }
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
- }
952
+ void frontend;
959
953
  }
960
954
  async function materializeFrontend(opts, template) {
961
955
  const variant = join(template, ".variants", opts.frontend);
962
956
  if (!existsSync(variant)) throw new Error(`Frontend template not found at ${variant}.`);
963
- await cp(variant, opts.dir, { recursive: true });
957
+ await cp(variant, opts.dir, {
958
+ recursive: true,
959
+ filter: (source) => relative(variant, source) !== "implementation.ts"
960
+ });
964
961
  const path = join(opts.dir, "package.json");
965
962
  const pkg = JSON.parse(await readFile(path, "utf-8"));
966
963
  pkg.dependencies ??= {};
967
964
  pkg.devDependencies ??= {};
965
+ pkg.scripts ??= {};
968
966
  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",
967
+ Object.assign(pkg.devDependencies, {
968
+ "@tanstack/react-router": "1.170.17",
972
969
  react: "^19.2.0",
973
970
  "react-dom": "^19.2.0"
974
971
  });
975
972
  Object.assign(pkg.devDependencies, {
973
+ "@tailwindcss/vite": "^4.0.6",
974
+ "@tanstack/router-plugin": "1.168.19",
976
975
  "@types/react": "^19.2.0",
977
976
  "@types/react-dom": "^19.2.0",
978
- vite: "^7.1.7"
977
+ "@vitejs/plugin-react": "^6.0.5",
978
+ tailwindcss: "^4.0.6",
979
+ vite: "^8.2.1"
979
980
  });
980
981
  } else if (opts.frontend === "custom") {
981
- pkg.devDependencies.vite = "^7.1.7";
982
+ pkg.devDependencies.vite = "^8.2.1";
983
+ }
984
+ if (opts.frontend !== "none") {
985
+ pkg.scripts["frontend:dev"] = "vite";
986
+ pkg.scripts["frontend:build"] = "vite build";
982
987
  }
983
988
  pkg.dependencies = sorted(pkg.dependencies);
984
989
  pkg.devDependencies = sorted(pkg.devDependencies);
@@ -1007,23 +1012,18 @@ async function stampOrigin(dir, origin) {
1007
1012
  async function materializeAdapter(opts) {
1008
1013
  const config = join(opts.dir, "astrale.config.ts");
1009
1014
  await cp(join(templateDir(ADAPTER_PACKAGES[opts.adapter]), "astrale.config.ts"), config);
1010
- if (opts.adapter === "cloudflare") return;
1011
- await addManagedAdapterDep(opts.dir);
1012
- if (opts.instance) await replaceInFile(config, `'${INSTANCE_PLACEHOLDER}'`, `'${opts.instance}'`);
1015
+ await addAdapterDependency(opts.dir, ADAPTER_PACKAGES[opts.adapter]);
1016
+ if (opts.adapter === "astrale" && opts.instance) {
1017
+ await replaceInFile(config, `'${INSTANCE_PLACEHOLDER}'`, `'${opts.instance}'`);
1018
+ }
1013
1019
  }
1014
- async function addManagedAdapterDep(dir) {
1020
+ async function addAdapterDependency(dir, adapter) {
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)) {
1018
- throw new Error(
1019
- "Could not materialize @astrale-os/adapter-astrale: the scaffold body does not declare its @astrale-os/adapter-cloudflare Worker runtime dependency."
1020
- );
1021
- }
1022
- pkg.dependencies["@astrale-os/adapter-astrale"] = installedPackageRange(
1023
- "@astrale-os/adapter-astrale"
1024
- );
1025
- pkg.dependencies = Object.fromEntries(
1026
- Object.entries(pkg.dependencies).sort(([a], [b3]) => a.localeCompare(b3))
1023
+ pkg.devDependencies ??= {};
1024
+ pkg.devDependencies[adapter] = installedPackageRange(adapter);
1025
+ pkg.devDependencies = Object.fromEntries(
1026
+ Object.entries(pkg.devDependencies).sort(([a], [b3]) => a.localeCompare(b3))
1027
1027
  );
1028
1028
  await writeFile(path, JSON.stringify(pkg, null, 2) + "\n");
1029
1029
  }
@@ -1031,9 +1031,25 @@ async function rewritePackageJson(dir, name, link) {
1031
1031
  const path = join(dir, "package.json");
1032
1032
  const pkg = JSON.parse(await readFile(path, "utf-8"));
1033
1033
  pkg.name = name;
1034
+ if (pkg.imports !== void 0) {
1035
+ pkg.publishConfig ??= {};
1036
+ pkg.publishConfig.imports = publishedImports(pkg.imports);
1037
+ }
1034
1038
  rewriteAstraleDeps(pkg, link);
1035
1039
  await writeFile(path, JSON.stringify(pkg, null, 2) + "\n");
1036
1040
  }
1041
+ function publishedImports(imports) {
1042
+ return Object.fromEntries(
1043
+ Object.entries(imports).filter(([specifier]) => specifier === "#schema" || specifier === "#schema/*").map(([specifier, target]) => {
1044
+ if (!/^\.\/.*\.ts$/u.test(target)) {
1045
+ throw new TypeError(
1046
+ `Package import ${specifier} must target one project TypeScript file.`
1047
+ );
1048
+ }
1049
+ return [specifier, `./dist/${target.slice(2, -3)}.js`];
1050
+ })
1051
+ );
1052
+ }
1037
1053
  function rewriteAstraleDeps(pkg, link) {
1038
1054
  if (!link) return false;
1039
1055
  let changed = false;
@@ -1050,46 +1066,6 @@ function rewriteAstraleDeps(pkg, link) {
1050
1066
  }
1051
1067
  return changed;
1052
1068
  }
1053
- var FIRST_PARTY_SCOPES = ["@astrale-domains/*", "@astrale-os/*"];
1054
- async function writeLinkedZodOverride(dir) {
1055
- const workspace = join(dir, "pnpm-workspace.yaml");
1056
- if (!existsSync(workspace)) return;
1057
- const start = "# create-astrale-domain: linked overrides";
1058
- const end = "# /create-astrale-domain";
1059
- const block = `
1060
- ${start}
1061
- overrides:
1062
- zod: ${sdkZodVersion()}
1063
- ${end}
1064
- `;
1065
- const text = await readFile(workspace, "utf-8");
1066
- const pattern = new RegExp(`\\n?${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}\\n?`);
1067
- await writeFile(workspace, text.replace(pattern, "\n").trimEnd() + block);
1068
- }
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
- function escapeRegExp(value) {
1085
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1086
- }
1087
- function sdkZodVersion() {
1088
- if (SDK_ROOT === void 0) throw new Error("Local SDK checkout is unavailable.");
1089
- const sdkRequire = createRequire(join(SDK_ROOT, "package.json"));
1090
- const pkg = sdkRequire("zod/package.json");
1091
- return pkg.version;
1092
- }
1093
1069
  async function replaceInFile(path, from, to) {
1094
1070
  if (!existsSync(path)) return;
1095
1071
  const text = await readFile(path, "utf-8");
@@ -1115,13 +1091,6 @@ function localPackageRoot(dependency) {
1115
1091
  const installed = dirname(resolved.path);
1116
1092
  if (!installed.split(sep).includes("node_modules")) return installed;
1117
1093
  }
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
1094
  throw new Error(`No active source package is available for ${dependency}.`);
1126
1095
  }
1127
1096
 
@@ -1175,6 +1144,10 @@ async function main() {
1175
1144
  const headless = flags.yes || !process.stdin.isTTY;
1176
1145
  Ie("Astrale \u2014 new domain");
1177
1146
  let slug = flags.positional[0];
1147
+ if (flags.positional.length > 1) {
1148
+ xe(`Unexpected positional argument "${flags.positional[1]}".`);
1149
+ return 1;
1150
+ }
1178
1151
  if (!slug) {
1179
1152
  if (headless) {
1180
1153
  xe("A domain slug is required in non-interactive mode (pass it as the first argument).");
@@ -1249,6 +1222,10 @@ async function main() {
1249
1222
  pm = answer;
1250
1223
  }
1251
1224
  }
1225
+ if (!PMS.includes(pm)) {
1226
+ xe(`Unknown package manager "${pm}" \u2014 use ${PMS.join(", ")}.`);
1227
+ return 1;
1228
+ }
1252
1229
  const dir = resolve2(flags.dir ?? slug);
1253
1230
  if (existsSync2(dir) && !flags.force) {
1254
1231
  if (headless) {
@@ -1259,8 +1236,10 @@ async function main() {
1259
1236
  if (pD(go) || !go) return bail();
1260
1237
  }
1261
1238
  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.`);
1239
+ if (flags.origin !== void 0 && !isValidOrigin(flags.origin)) {
1240
+ xe(
1241
+ `Invalid --origin "${flags.origin}". Use a lowercase dotted domain with valid DNS labels.`
1242
+ );
1264
1243
  return 1;
1265
1244
  }
1266
1245
  const origin = flags.origin ?? (slug.includes(".") ? slug : `${slug}.example.dev`);
@@ -1293,13 +1272,13 @@ async function main() {
1293
1272
  `${pm} install`,
1294
1273
  devStep(pm),
1295
1274
  installStep(pm),
1296
- prodStep(pm, adapter, instance)
1275
+ deployStep(pm, adapter, instance)
1297
1276
  ].join("\n"),
1298
1277
  "Next"
1299
1278
  );
1300
1279
  if (adapter === "astrale" && !instance) {
1301
1280
  M2.warn(
1302
- `No target instance set \u2014 put your slug in astrale.config.ts (prod.instance) before \`${pm === "npm" ? "npm run prod" : `${pm} prod`}\`. Create one: astrale instance create <slug>`
1281
+ `No target instance set \u2014 put your slug in astrale.config.ts (prod.instance) before \`${deployCommand(pm)}\`. Create one: astrale instance create <slug>`
1303
1282
  );
1304
1283
  }
1305
1284
  M2.info(
@@ -1309,11 +1288,11 @@ async function main() {
1309
1288
  return 0;
1310
1289
  }
1311
1290
  var devStep = (pm) => `${pm === "npm" ? "npm run dev" : `${pm} dev`} # wrangler dev \u2192 prints a local URL`;
1312
- var prodStep = (pm, adapter, instance) => {
1313
- const cmd = pm === "npm" ? "npm run prod" : `${pm} prod`;
1314
- const hint = adapter === "astrale" ? `# managed deploy \u2192 installs on ${instance ? `"${instance}"` : "your instance"}` : "# deploy prod \u2192 prints a URL";
1315
- return `${cmd} ${hint}`;
1291
+ var deployStep = (pm, adapter, instance) => {
1292
+ const hint = adapter === "astrale" ? `# managed deploy \u2192 installs on ${instance ? `"${instance}"` : "your instance"}` : "# deploy production \u2192 prints a URL";
1293
+ return `${deployCommand(pm)} ${hint}`;
1316
1294
  };
1295
+ var deployCommand = (pm) => pm === "npm" ? "npm run deploy -- prod" : `${pm} deploy prod`;
1317
1296
  function installStep(pm) {
1318
1297
  if (astraleOnPath()) return "astrale domain install <url> --direct # mount it on an instance";
1319
1298
  return [
@@ -1341,16 +1320,24 @@ function parseFlags(argv) {
1341
1320
  else if (a === "--force") flags.force = true;
1342
1321
  else if (a === "--link") flags.link = true;
1343
1322
  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];
1323
+ else if (a === "--adapter") flags.adapter = flagValue(argv, ++i, a);
1324
+ else if (a === "--frontend") flags.frontend = flagValue(argv, ++i, a);
1325
+ else if (a === "--pm") flags.pm = flagValue(argv, ++i, a);
1326
+ else if (a === "--dir") flags.dir = flagValue(argv, ++i, a);
1327
+ else if (a === "--origin") flags.origin = flagValue(argv, ++i, a);
1328
+ else if (a === "--instance") flags.instance = flagValue(argv, ++i, a);
1329
+ else if (a.startsWith("-")) throw new TypeError(`Unknown option "${a}".`);
1350
1330
  else if (!a.startsWith("-")) flags.positional.push(a);
1351
1331
  }
1352
1332
  return flags;
1353
1333
  }
1334
+ function flagValue(argv, index, flag) {
1335
+ const value = argv[index];
1336
+ if (value === void 0 || value.startsWith("-")) {
1337
+ throw new TypeError(`${flag} requires a value.`);
1338
+ }
1339
+ return value;
1340
+ }
1354
1341
  function printHelp() {
1355
1342
  process.stdout.write(
1356
1343
  `
@@ -1384,6 +1371,12 @@ Non-interactive (also auto-enabled without a TTY, e.g. CI):
1384
1371
  function isValidSlug(v2) {
1385
1372
  return /^[a-z][a-z0-9.-]*$/.test(v2);
1386
1373
  }
1374
+ function isValidOrigin(value) {
1375
+ if (value.length > 253 || !value.includes(".")) return false;
1376
+ return value.split(".").every(
1377
+ (label) => label.length > 0 && label.length <= 63 && /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/u.test(label)
1378
+ );
1379
+ }
1387
1380
  function bail() {
1388
1381
  xe("Cancelled.");
1389
1382
  return 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-astrale-domain",
3
- "version": "0.3.0-beta.4",
3
+ "version": "0.3.0-beta.41",
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-astrale": "^0.5.0-beta.4",
26
- "@astrale-os/adapter-cloudflare": "^0.5.0-beta.11"
31
+ "@astrale-os/adapter-astrale": "^0.5.0-beta.41",
32
+ "@astrale-os/adapter-cloudflare": "^0.5.0-beta.48"
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.8"
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.45"
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
+ })
@@ -3,6 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
6
7
  <title>Astrale Domain</title>
7
8
  </head>
8
9
  <body>
@@ -0,0 +1,3 @@
1
+ import { mountApplication } from '../application.view.js'
2
+
3
+ mountApplication(document.querySelector<HTMLElement>('#application'))
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <rect width="32" height="32" rx="8" fill="#0a0a0a" />
3
+ <path d="M16 5 27 26h-5.4l-2.1-4.4h-7L10.4 26H5Zm0 7.2-2.2 4.7h4.4Z" fill="#f4cf52" />
4
+ </svg>
@@ -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
  })