create-astrale-domain 0.2.12 → 0.3.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/index.js +225 -83
- package/package.json +10 -5
- package/template/.env.example +9 -0
- package/template/.gitignore.template +17 -0
- package/template/.variants/custom/README.md +20 -0
- package/template/.variants/custom/implementation.ts +14 -0
- package/template/.variants/custom/schema/application/index.ts +10 -0
- package/template/.variants/custom/ui/application/index.ts +2 -0
- package/template/.variants/custom/ui/application/styles.css +27 -0
- package/template/.variants/custom/ui/application/styles.d.ts +1 -0
- package/template/.variants/custom/ui/index.ts +1 -0
- package/template/.variants/custom/views/app/index.html +12 -0
- package/template/.variants/custom/views/app/main.ts +10 -0
- package/template/.variants/custom/views/application/__tests__/routes.test.ts +7 -0
- package/template/.variants/custom/views/application/routes.ts +13 -0
- package/template/.variants/custom/views/index.ts +1 -0
- package/template/.variants/none/README.md +13 -0
- package/template/.variants/none/implementation.ts +14 -0
- package/template/.variants/none/schema/application/index.ts +7 -0
- package/template/.variants/react/README.md +23 -0
- package/template/.variants/react/implementation.ts +16 -0
- package/template/.variants/react/schema/application/index.ts +10 -0
- package/template/.variants/react/ui/application/__tests__/application.test.tsx +13 -0
- package/template/.variants/react/ui/application/index.ts +1 -0
- package/template/.variants/react/ui/application/screen.tsx +18 -0
- package/template/.variants/react/ui/application/styles.css +34 -0
- package/template/.variants/react/ui/application/styles.d.ts +1 -0
- package/template/.variants/react/ui/index.ts +1 -0
- package/template/.variants/react/views/app/README.md +4 -0
- package/template/.variants/react/views/application/__tests__/routes.test.ts +7 -0
- package/template/.variants/react/views/application/application.view.tsx +19 -0
- package/template/.variants/react/views/application/routes.ts +16 -0
- package/template/.variants/react/views/index.ts +1 -0
- package/template/CLAUDE.md +21 -0
- package/template/index.ts +2 -0
- package/template/oxlint.config.ts +1 -0
- package/template/package.json +72 -0
- package/template/pnpm-workspace.yaml +46 -0
- package/template/schema/application/__tests__/schema.test.ts +11 -0
- package/template/schema/index.ts +1 -0
- package/template/tests/e2e/application.test.ts +9 -0
- package/template/tests/index.ts +2 -0
- package/template/tsconfig.json +70 -0
- 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
|
-
import { cp, readFile, rm, writeFile } from "node:fs/promises";
|
|
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
|
|
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
|
|
794
|
-
"@astrale-os/sdk"
|
|
795
|
-
"@astrale-os/adapter-cloudflare"
|
|
796
|
-
"@astrale-os/adapter-astrale"
|
|
797
|
-
"@astrale-os/
|
|
798
|
-
"@astrale-os/
|
|
799
|
-
|
|
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(
|
|
820
|
-
if (!(
|
|
821
|
-
const 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 && !
|
|
825
|
-
return `${
|
|
821
|
+
if (causeText && !error.message.includes(causeText)) {
|
|
822
|
+
return `${error.message}
|
|
826
823
|
\u21B3 caused by: ${causeText}`;
|
|
827
824
|
}
|
|
828
825
|
}
|
|
829
|
-
return
|
|
826
|
+
return error.message;
|
|
830
827
|
}
|
|
831
|
-
function packageRoot(
|
|
832
|
-
const
|
|
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
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
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
|
-
|
|
851
|
-
|
|
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
|
-
|
|
878
|
-
|
|
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(
|
|
902
|
+
await writeProject(options);
|
|
881
903
|
} catch (err) {
|
|
882
|
-
if (!preExisting && existsSync(
|
|
883
|
-
await rm(
|
|
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 =
|
|
911
|
+
const src = bodyTemplateDir();
|
|
890
912
|
if (!existsSync(src)) {
|
|
891
913
|
throw new Error(`Template not found at ${src}.`);
|
|
892
914
|
}
|
|
@@ -896,20 +918,77 @@ 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
|
|
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
|
});
|
|
925
|
+
await materializeFrontend(opts, src);
|
|
926
|
+
await materializeGitignores(opts.dir, opts.frontend);
|
|
903
927
|
await materializeAdapter(opts);
|
|
904
928
|
await stampOrigin(opts.dir, opts.origin);
|
|
905
929
|
await rewritePackageJson(opts.dir, opts.name, opts.link);
|
|
906
|
-
|
|
907
|
-
|
|
930
|
+
if (opts.link) await writeLinkedZodOverride(opts.dir);
|
|
931
|
+
else await writeReleaseAgePolicy(opts.dir);
|
|
908
932
|
await writeFile(
|
|
909
933
|
join(opts.dir, ".env.dev"),
|
|
910
934
|
"# Dev secrets \u2014 the ENTIRE file is injected into the local runtime by `pnpm dev`.\n# Gitignored. See .env.example.\n"
|
|
911
935
|
);
|
|
912
936
|
}
|
|
937
|
+
async function materializeGitignores(dir, frontend) {
|
|
938
|
+
for (const path of [join(dir, ".gitignore")]) {
|
|
939
|
+
const packagedPath = `${path}.template`;
|
|
940
|
+
if (existsSync(packagedPath)) {
|
|
941
|
+
await rename(packagedPath, path);
|
|
942
|
+
} else if (!existsSync(path)) {
|
|
943
|
+
throw new Error(`Template is missing packaged Git ignore file ${packagedPath}.`);
|
|
944
|
+
}
|
|
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.7 <0.3.8 || >=0.3.8-beta.2 <1.0.0",
|
|
971
|
+
"@astrale-os/shell-react": ">=0.2.9 <0.2.10 || >=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
|
+
);
|
|
991
|
+
}
|
|
913
992
|
function assertSafeToDelete(dir) {
|
|
914
993
|
const target = resolve(dir);
|
|
915
994
|
const rel = relative(target, resolve(process.cwd()));
|
|
@@ -920,54 +999,39 @@ function assertSafeToDelete(dir) {
|
|
|
920
999
|
}
|
|
921
1000
|
}
|
|
922
1001
|
async function stampOrigin(dir, origin) {
|
|
923
|
-
const schemaIndex = join(dir, "schema", "index.ts");
|
|
1002
|
+
const schemaIndex = join(dir, "schema", "application", "index.ts");
|
|
924
1003
|
await replaceInFile(schemaIndex, ORIGIN_PLACEHOLDER, origin);
|
|
925
1004
|
const config = join(dir, "astrale.config.ts");
|
|
926
1005
|
await replaceInFile(config, ORIGIN_PLACEHOLDER, origin);
|
|
927
1006
|
}
|
|
928
1007
|
async function materializeAdapter(opts) {
|
|
929
|
-
if (opts.adapter !== "astrale") return;
|
|
930
1008
|
const config = join(opts.dir, "astrale.config.ts");
|
|
931
|
-
await cp(join(templateDir(ADAPTER_PACKAGES.
|
|
932
|
-
|
|
1009
|
+
await cp(join(templateDir(ADAPTER_PACKAGES[opts.adapter]), "astrale.config.ts"), config);
|
|
1010
|
+
if (opts.adapter === "cloudflare") return;
|
|
1011
|
+
await addManagedAdapterDep(opts.dir);
|
|
933
1012
|
if (opts.instance) await replaceInFile(config, `'${INSTANCE_PLACEHOLDER}'`, `'${opts.instance}'`);
|
|
934
1013
|
}
|
|
935
|
-
async function
|
|
1014
|
+
async function addManagedAdapterDep(dir) {
|
|
936
1015
|
const path = join(dir, "package.json");
|
|
937
1016
|
const pkg = JSON.parse(await readFile(path, "utf-8"));
|
|
938
|
-
if (!pkg.dependencies || !(
|
|
1017
|
+
if (!pkg.dependencies || !("@astrale-os/adapter-cloudflare" in pkg.dependencies)) {
|
|
939
1018
|
throw new Error(
|
|
940
|
-
|
|
1019
|
+
"Could not materialize @astrale-os/adapter-astrale: the scaffold body does not declare its @astrale-os/adapter-cloudflare Worker runtime dependency."
|
|
941
1020
|
);
|
|
942
1021
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1022
|
+
pkg.dependencies["@astrale-os/adapter-astrale"] = installedPackageRange(
|
|
1023
|
+
"@astrale-os/adapter-astrale"
|
|
1024
|
+
);
|
|
946
1025
|
pkg.dependencies = Object.fromEntries(
|
|
947
1026
|
Object.entries(pkg.dependencies).sort(([a], [b3]) => a.localeCompare(b3))
|
|
948
1027
|
);
|
|
949
1028
|
await writeFile(path, JSON.stringify(pkg, null, 2) + "\n");
|
|
950
1029
|
}
|
|
951
|
-
async function rewriteWorkspaceMember(pkgPath, link) {
|
|
952
|
-
if (!existsSync(pkgPath)) return;
|
|
953
|
-
const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
|
|
954
|
-
const changed = rewriteAstraleDeps(pkg, link);
|
|
955
|
-
if (changed) await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
956
|
-
}
|
|
957
1030
|
async function rewritePackageJson(dir, name, link) {
|
|
958
1031
|
const path = join(dir, "package.json");
|
|
959
1032
|
const pkg = JSON.parse(await readFile(path, "utf-8"));
|
|
960
1033
|
pkg.name = name;
|
|
961
1034
|
rewriteAstraleDeps(pkg, link);
|
|
962
|
-
if (link) {
|
|
963
|
-
pkg.pnpm = {
|
|
964
|
-
...pkg.pnpm,
|
|
965
|
-
overrides: {
|
|
966
|
-
...pkg.pnpm?.overrides,
|
|
967
|
-
zod: sdkZodVersion()
|
|
968
|
-
}
|
|
969
|
-
};
|
|
970
|
-
}
|
|
971
1035
|
await writeFile(path, JSON.stringify(pkg, null, 2) + "\n");
|
|
972
1036
|
}
|
|
973
1037
|
function rewriteAstraleDeps(pkg, link) {
|
|
@@ -976,8 +1040,8 @@ function rewriteAstraleDeps(pkg, link) {
|
|
|
976
1040
|
for (const group of [pkg.dependencies, pkg.devDependencies]) {
|
|
977
1041
|
if (!group) continue;
|
|
978
1042
|
for (const dep of Object.keys(group)) {
|
|
979
|
-
if (!
|
|
980
|
-
const next = `link:${
|
|
1043
|
+
if (!LINK_PACKAGES.has(dep)) continue;
|
|
1044
|
+
const next = `link:${localPackageRoot(dep)}`;
|
|
981
1045
|
if (group[dep] !== next) {
|
|
982
1046
|
group[dep] = next;
|
|
983
1047
|
changed = true;
|
|
@@ -987,6 +1051,21 @@ function rewriteAstraleDeps(pkg, link) {
|
|
|
987
1051
|
return changed;
|
|
988
1052
|
}
|
|
989
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
|
+
}
|
|
990
1069
|
async function writeReleaseAgePolicy(dir) {
|
|
991
1070
|
const workspace = join(dir, "pnpm-workspace.yaml");
|
|
992
1071
|
if (!existsSync(workspace)) return;
|
|
@@ -1006,7 +1085,8 @@ function escapeRegExp(value) {
|
|
|
1006
1085
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1007
1086
|
}
|
|
1008
1087
|
function sdkZodVersion() {
|
|
1009
|
-
|
|
1088
|
+
if (SDK_ROOT === void 0) throw new Error("Local SDK checkout is unavailable.");
|
|
1089
|
+
const sdkRequire = createRequire(join(SDK_ROOT, "package.json"));
|
|
1010
1090
|
const pkg = sdkRequire("zod/package.json");
|
|
1011
1091
|
return pkg.version;
|
|
1012
1092
|
}
|
|
@@ -1017,7 +1097,32 @@ async function replaceInFile(path, from, to) {
|
|
|
1017
1097
|
if (next !== text) await writeFile(path, next);
|
|
1018
1098
|
}
|
|
1019
1099
|
function workspaceAvailable() {
|
|
1020
|
-
|
|
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}.`);
|
|
1021
1126
|
}
|
|
1022
1127
|
|
|
1023
1128
|
// src/index.ts
|
|
@@ -1043,6 +1148,23 @@ var ADAPTERS = [
|
|
|
1043
1148
|
},
|
|
1044
1149
|
{ value: "custom", label: "Custom", hint: "bring your own adapter (soon)", enabled: false }
|
|
1045
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
|
+
];
|
|
1046
1168
|
async function main() {
|
|
1047
1169
|
const argv = process.argv.slice(2);
|
|
1048
1170
|
const flags = parseFlags(argv);
|
|
@@ -1091,6 +1213,23 @@ async function main() {
|
|
|
1091
1213
|
xe(`The "${adapter}" adapter is not available yet \u2014 use "astrale" or "cloudflare".`);
|
|
1092
1214
|
return 1;
|
|
1093
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
|
+
}
|
|
1094
1233
|
const instance = flags.instance;
|
|
1095
1234
|
if (instance !== void 0 && !isValidSlug(instance)) {
|
|
1096
1235
|
xe(`Invalid --instance "${instance}". Use lowercase letters, digits, dots and dashes.`);
|
|
@@ -1133,6 +1272,7 @@ async function main() {
|
|
|
1133
1272
|
name: slug,
|
|
1134
1273
|
origin,
|
|
1135
1274
|
adapter,
|
|
1275
|
+
frontend,
|
|
1136
1276
|
instance,
|
|
1137
1277
|
link
|
|
1138
1278
|
});
|
|
@@ -1202,6 +1342,7 @@ function parseFlags(argv) {
|
|
|
1202
1342
|
else if (a === "--link") flags.link = true;
|
|
1203
1343
|
else if (a === "--no-link") flags.link = false;
|
|
1204
1344
|
else if (a === "--adapter") flags.adapter = argv[++i];
|
|
1345
|
+
else if (a === "--frontend") flags.frontend = argv[++i];
|
|
1205
1346
|
else if (a === "--pm") flags.pm = argv[++i];
|
|
1206
1347
|
else if (a === "--dir") flags.dir = argv[++i];
|
|
1207
1348
|
else if (a === "--origin") flags.origin = argv[++i];
|
|
@@ -1224,6 +1365,7 @@ Arguments:
|
|
|
1224
1365
|
|
|
1225
1366
|
Options:
|
|
1226
1367
|
--adapter <id> deployment adapter \u2014 astrale (managed, default) | cloudflare
|
|
1368
|
+
--frontend <id> react (default) | custom | none
|
|
1227
1369
|
--instance <slug> target instance for managed deploys (astrale adapter)
|
|
1228
1370
|
--pm <pm> pnpm | npm | yarn | bun (default: detected, else pnpm)
|
|
1229
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.
|
|
3
|
+
"version": "0.3.0-beta.1",
|
|
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-cloudflare": "^0.
|
|
23
|
-
"@astrale-os/adapter-astrale": "^0.
|
|
25
|
+
"@astrale-os/adapter-cloudflare": "^0.5.0-beta.8",
|
|
26
|
+
"@astrale-os/adapter-astrale": "^0.5.0-beta.1"
|
|
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.5"
|
|
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,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,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,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,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,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 @@
|
|
|
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,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
|
+
}
|