Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/astryx-vite-ui-profile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@croco/presentation-preset": patch
"@croco/ui-astryx": minor
"create-croco-app": minor
---

Generate an opt-in Astryx Vite SPA profile with Croco-aware theme, layout, Problem, and auth states, explicit UI metadata, isolated dependencies, and install/typecheck/build/render smoke evidence.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,7 @@ jobs:
- 'packages/triggers-qstash/src/**'
- 'packages/tx-core/src/**'
- 'packages/tx-drizzle/src/**'
- 'packages/ui-astryx/src/**'
- 'packages/webhooks-core/src/**'
- 'packages/workflow-core/src/**'
- 'packages/docs/astro.config.mjs'
Expand Down
26 changes: 14 additions & 12 deletions README.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion croco.arch.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@
"@croco/admin-react",
"@croco/frontend-*",
"@croco/meta-vite",
"@croco/presentation-preset"
"@croco/presentation-preset",
"@croco/ui-*"
]
},
"app": {
Expand Down
18 changes: 17 additions & 1 deletion docs/package-catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,8 @@
"frontend-react",
"frontend-vite",
"meta-vite",
"presentation-preset"
"presentation-preset",
"ui-astryx"
]
},
"Tooling": {
Expand Down Expand Up @@ -342,6 +343,7 @@
"protocols-graphql",
"protocols-trpc",
"rpc-codegen",
"ui-astryx",
"search-drizzle",
"search-meilisearch",
"storage-cloudinary",
Expand Down Expand Up @@ -577,6 +579,20 @@
"meta-vite generated build smoke"
]
},
"ui-astryx": {
"domain": "Astryx UI",
"adapter": "Croco-aware Astryx React presentation adapter",
"runtimes": ["browser", "node"],
"requiredEnv": ["none"],
"features": [
"Astryx neutral theme provider",
"application shell",
"Problem recovery display",
"auth and session states",
"prebuilt StyleX CSS consumer path",
"generated Vite SPA smoke"
]
},
"integrations-posthog": {
"domain": "PostHog",
"adapter": "Shared PostHog client",
Expand Down
10 changes: 5 additions & 5 deletions docs/package-docs-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@

| Metric | Count |
| ------------------------------ | ----: |
| Public packages | 110 |
| Public packages | 111 |
| Private packages skipped | 2 |
| Missing package README | 0 |
| Missing generated API docs | 0 |
| Missing package test directory | 0 |
| Extension matrix packages | 41 |
| Extension matrix packages | 42 |
| Certification records | 6 |
| Croco 1.0 spine packages | 18 |

Expand Down Expand Up @@ -111,13 +111,13 @@ None.
| Integration | 5 |
| Protocol | 7 |
| Transport | 3 |
| Presentation | 7 |
| Presentation | 8 |
| Tooling | 9 |

| Maturity | Packages |
| ------------------- | -------: |
| 🟢 production-ready | 24 |
| 🟡 beta | 74 |
| 🟡 beta | 75 |
| 🔴 alpha/WIP | 12 |
| ⚠️ deprecated | 0 |

Expand All @@ -130,4 +130,4 @@ Extension matrix metadata is maintained in `docs/package-catalog.json` and rende
| Provider | 26 | 0 |
| Integration | 5 | 0 |
| Transport | 3 | 0 |
| Presentation | 7 | 0 |
| Presentation | 8 | 0 |
25 changes: 25 additions & 0 deletions packages/create-croco-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,31 @@ and the zero-credential `demo:smoke` success path. Generated projects are pnpm
workspaces; `--no-install --no-git` keeps the documented setup deterministic before
the explicit install and smoke commands.

### Astryx Vite UI profile

Astryx is available as an opt-in beta UI profile for the Vite SPA frontend runtime:

```bash
npx create-croco-app@latest my-app \
--preset ddd-fullstack \
--scope @myorg \
--api graphql \
--api-hosting standalone \
--web-apps web \
--frontend-deploy vite-spa \
--ui astryx \
--no-install \
--no-git
```

The generated app imports Astryx's prebuilt CSS, so it does not add a StyleX compiler plugin.
Use `--ui none` for an explicit provider-neutral Vite starter. Omitting `--ui` preserves the
generator's existing output for compatibility. Astryx does not change `@croco/frontend-react` or
apply to meta-vite profiles in this release.

Generated projects include the package manager command and next-step instructions in
the CLI result.

## Verification

```bash
Expand Down
1 change: 1 addition & 0 deletions packages/create-croco-app/src/cli-program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export function configureCreateCrocoAppProgram(program: Command): Command {
"--frontend-deploy <deploy>",
"Frontend deploy (opennext|vercel|docker|cloudflare-meta-vite|vite-spa)",
)
.option("--ui <profile>", "UI profile (none|astryx). Astryx currently requires vite-spa")
.option("--db <dbs>", "Comma-separated DB types (postgres,mongodb,redis)")
.option("--no-agent-rules", "Skip agent rules")
.option("--no-install", "Skip pnpm dependency installation")
Expand Down
15 changes: 12 additions & 3 deletions packages/create-croco-app/src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
writeFileSync,
} from "node:fs";
import { join, resolve } from "node:path";
import { validateResolvedGoalOptions, writeGoalManifest } from "./goals.js";
import { writeGoalManifest } from "./goals.js";
import { mergeInto } from "./helpers/fs.js";
import { rewriteExternalCrocoWorkspaceRanges } from "./helpers/manifest-normalizer.js";
import {
Expand All @@ -34,10 +34,12 @@ import {
installSharedUi,
installTrpcNextjs,
installTrpcStandalone,
installUiProfile,
installWebGraphql,
installWebTrpc,
} from "./installers/index.js";
import { DirectoryNotEmptyProblem } from "./libs/problems/DirectoryNotEmptyProblem.js";
import { validateResolvedOptions } from "./options.js";
import {
DEFAULT_SAAS_PROVIDER_PROFILE,
assertSaasProviderTenantModelCompatibility,
Expand All @@ -54,7 +56,7 @@ import type { GeneratorOptions } from "./types.js";
import type { SaasProviderProfileManifest } from "./saas-provider-profiles.js";

export async function generate(targetDir: string, options: GeneratorOptions): Promise<void> {
validateResolvedGoalOptions(options);
validateResolvedOptions(options);

const vars = { projectName: options.projectName, scope: options.scope };
const isLegacyVikeFullstackPreset = options.preset === "ddd-vike-fullstack";
Expand Down Expand Up @@ -128,7 +130,9 @@ export async function generate(targetDir: string, options: GeneratorOptions): Pr
hasWebApps &&
(options.preset === "ddd-fullstack" || options.apiHosting === "nextjs")
) {
installSharedUi(resolvedTarget, vars);
if (options.ui === undefined) {
installSharedUi(resolvedTarget, vars);
}
}

// Step 5: web addon (standalone hosting + web apps)
Expand Down Expand Up @@ -177,6 +181,11 @@ export async function generate(targetDir: string, options: GeneratorOptions): Pr
preset: options.preset,
frontendDeploy: options.frontendDeploy,
});
installUiProfile(resolvedTarget, webAppName, {
...vars,
frontendDeploy: options.frontendDeploy,
...(options.ui === undefined ? {} : { ui: options.ui }),
});
}
}

Expand Down
2 changes: 2 additions & 0 deletions packages/create-croco-app/src/goals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ export function validateResolvedGoalOptions(options: GeneratorOptions): void {
if (options.apiHosting !== expectedOptions.apiHosting) mismatches.push("apiHosting");
if (options.backendDeploy !== expectedOptions.backendDeploy) mismatches.push("backendDeploy");
if (options.frontendDeploy !== expectedOptions.frontendDeploy) mismatches.push("frontendDeploy");
if (options.ui !== expectedOptions.ui) mismatches.push("ui");
if (options.tenantModel !== expectedOptions.tenantModel) mismatches.push("tenantModel");
if (!sameStringArray(options.webApps, expectedOptions.webApps)) mismatches.push("webApps");
if (!sameStringArray(options.db, expectedOptions.db)) mismatches.push("db");
Expand All @@ -265,6 +266,7 @@ function assertGoalDoesNotMixStackOptions(goal: AppGoal, options: Partial<Genera
if (options.apiHosting) unsupportedOptions.push("--api-hosting");
if (options.backendDeploy) unsupportedOptions.push("--backend-deploy");
if (options.frontendDeploy) unsupportedOptions.push("--frontend-deploy");
if (options.ui) unsupportedOptions.push("--ui");
if (options.saasProviderProfile) unsupportedOptions.push("--saas-profile");
if (options.tenantModel) unsupportedOptions.push("--tenant-model");
if (options.webApps && options.webApps.length > 0) unsupportedOptions.push("--web-apps");
Expand Down
1 change: 1 addition & 0 deletions packages/create-croco-app/src/helpers/croco-ranges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const EXTERNAL_CROCO_PACKAGE_RANGES = {
"@croco/triggers-qstash": "^0.0.4",
"@croco/tx-core": "^0.0.4",
"@croco/tx-drizzle": "^0.0.4",
"@croco/ui-astryx": "^0.1.0",
"@croco/webhooks-core": "^0.1.0",
} as const satisfies Record<string, string>;

Expand Down
1 change: 1 addition & 0 deletions packages/create-croco-app/src/installers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ export { installRedis } from "./redis.js";
export { installSharedUi } from "./shared-ui.js";
export { installTrpcNextjs } from "./trpc-nextjs.js";
export { installTrpcStandalone } from "./trpc-standalone.js";
export { installUiProfile } from "./ui-profile.js";
export { installWebGraphql } from "./web-graphql.js";
export { installWebTrpc } from "./web-trpc.js";
119 changes: 119 additions & 0 deletions packages/create-croco-app/src/installers/ui-profile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { mergeInto } from "../helpers/fs.js";
import { TEMPLATES_DIR } from "../template-path.js";
import type { GeneratorOptions } from "../types.js";

type GeneratedUiProfileMetadata = {
readonly name: "none" | "astryx";
readonly styleEngine: "none" | "stylex";
readonly requiresStylexCompile: boolean;
readonly maturity: "alpha" | "beta";
readonly generatedAppSmokeCase: string;
};

type GeneratedPresentationProfile = {
readonly webApp: string;
readonly runtimeProfile: "browser-vite-spa" | "browser-vite-spa-astryx";
readonly ui: GeneratedUiProfileMetadata;
};

type GeneratedPresentationProfileManifest = {
readonly schemaVersion: "croco.generated-presentation-profile/v1";
readonly profiles: readonly GeneratedPresentationProfile[];
};

const UI_METADATA = {
none: {
name: "none",
styleEngine: "none",
requiresStylexCompile: false,
maturity: "alpha",
generatedAppSmokeCase: "graphql-vite-spa-docker",
},
astryx: {
name: "astryx",
styleEngine: "stylex",
requiresStylexCompile: false,
maturity: "beta",
generatedAppSmokeCase: "graphql-vite-spa-astryx",
},
} as const satisfies Record<NonNullable<GeneratorOptions["ui"]>, GeneratedUiProfileMetadata>;

export function installUiProfile(
targetDir: string,
webAppName: string,
options: Pick<GeneratorOptions, "projectName" | "scope" | "frontendDeploy" | "ui">,
): void {
if (!options.ui) return;
if (options.frontendDeploy !== "vite-spa") return;

const appTargetDir = join(targetDir, "apps", webAppName);
const metadata = UI_METADATA[options.ui];
const profile: GeneratedPresentationProfile = {
webApp: webAppName,
runtimeProfile: options.ui === "astryx" ? "browser-vite-spa-astryx" : "browser-vite-spa",
ui: metadata,
};

writePresentationProfileManifest(targetDir, profile);
writeFileSync(
join(appTargetDir, "croco.presentation-profile.json"),
`${JSON.stringify(profile, null, 2)}\n`,
);

if (options.ui === "none") return;

mergeInto(join(TEMPLATES_DIR, "addons", "ui-astryx-vite-spa"), appTargetDir, {
projectName: options.projectName,
scope: options.scope,
});
addAstryxDependencies(appTargetDir);
}

function writePresentationProfileManifest(
targetDir: string,
profile: GeneratedPresentationProfile,
): void {
const manifestPath = join(targetDir, "croco-presentation-profile.manifest.json");
const existing = existsSync(manifestPath)
? (JSON.parse(readFileSync(manifestPath, "utf8")) as GeneratedPresentationProfileManifest)
: undefined;
const profiles = [
...(existing?.profiles ?? []).filter(({ webApp }) => webApp !== profile.webApp),
profile,
];
const manifest: GeneratedPresentationProfileManifest = {
schemaVersion: "croco.generated-presentation-profile/v1",
profiles,
};

writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
}

function addAstryxDependencies(appTargetDir: string): void {
const packageJsonPath = join(appTargetDir, "package.json");
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8")) as {
scripts?: Record<string, string>;
dependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
};

packageJson.scripts = {
...packageJson.scripts,
"presentation:smoke": "tsx src/presentation-smoke.tsx",
};
packageJson.dependencies = {
...packageJson.dependencies,
"@astryxdesign/core": "0.1.4",
"@astryxdesign/theme-neutral": "0.1.4",
"@croco/ui-astryx": "workspace:*",
"@stylexjs/stylex": "^0.18.3",
};
packageJson.devDependencies = {
...packageJson.devDependencies,
tsx: "^4.20.0",
};

writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
}
Loading
Loading