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/secret-placeholder-policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@croco/auth-better-auth": patch
"create-croco-app": patch
---

Generated SaaS apps now use checked Croco secret/config placeholders in provider env examples and docs.
Auth Better Auth now resolves a patched Better Auth runtime dependency.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"setup": "pnpm install && pnpm build && pnpm typecheck && pnpm test",
"audit:prod": "pnpm audit --audit-level high --prod",
"security:audit-policy": "node --experimental-strip-types scripts/dependency-audit-policy.mts",
"check": "pnpm package-manifests:check && pnpm docs:catalog:check && pnpm provider-certification:check && pnpm docs:api-triggers:check && pnpm problem-registry:check && pnpm docs:examples:check && pnpm release-docs:check && pnpm architecture-policy:check && pnpm architecture:check:circular:allowlist && pnpm dependency-boundaries:check && pnpm security-allowlists:check && pnpm strict-contract-typecheck && pnpm static-misuse:check && pnpm public-api:check && oxlint . && oxfmt --check . --ignore-path=.gitignore --ignore-path=.prettierignore --ignore-path=.oxfmtignore",
"check": "pnpm package-manifests:check && pnpm docs:catalog:check && pnpm provider-certification:check && pnpm docs:api-triggers:check && pnpm problem-registry:check && pnpm docs:examples:check && pnpm release-docs:check && pnpm architecture-policy:check && pnpm architecture:check:circular:allowlist && pnpm dependency-boundaries:check && pnpm security-allowlists:check && pnpm generated-secret-placeholders:check && pnpm strict-contract-typecheck && pnpm static-misuse:check && pnpm public-api:check && oxlint . && oxfmt --check . --ignore-path=.gitignore --ignore-path=.prettierignore --ignore-path=.oxfmtignore",
"changeset": "changeset",
"deploy": "turbo deploy",
"dev": "turbo dev",
Expand All @@ -35,6 +35,7 @@
"alpha-release:smoke": "node --experimental-strip-types scripts/alpha-release-smoke.mts",
"changeset-required:check": "node --experimental-strip-types scripts/changeset-required-check.mts",
"create-croco-app:smoke": "node --experimental-strip-types scripts/create-croco-app-generated-smoke.mts",
"generated-secret-placeholders:check": "node --experimental-strip-types scripts/generated-secret-placeholder-policy.mts",
"docs:catalog:check": "node --experimental-strip-types scripts/package-docs-check.mts --check",
"docs:catalog:write": "node --experimental-strip-types scripts/package-docs-check.mts --write",
"dependency-boundaries:check": "node --experimental-strip-types scripts/package-quality-report.mts --boundary-check-only",
Expand Down
14 changes: 7 additions & 7 deletions packages/auth-better-auth/src/libs/BetterAuthFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as schema from "./schema";
export const DRIZZLE_TOKEN = "DRIZZLE_TOKEN";

type BetterAuthDatabase = Parameters<typeof drizzleAdapter>[0];
type BetterAuthInstance = ReturnType<typeof createBetterAuthInstance>;

/**
* Better Auth 초기화에 필요한 설정입니다.
Expand All @@ -26,8 +27,6 @@ function createBetterAuthInstance(db: BetterAuthDatabase, config: BetterAuthConf
});
}

type BetterAuthInstance = ReturnType<typeof createBetterAuthInstance>;

@Component()
/**
* Better Auth 인스턴스를 지연 생성하고 재사용하는 팩토리입니다.
Expand All @@ -41,12 +40,13 @@ export class BetterAuthFactory {
) {}

getAuth(): BetterAuthInstance {
if (this.auth) {
return this.auth;
const cachedAuth = this.auth;
if (cachedAuth) {
return cachedAuth;
}

this.auth = createBetterAuthInstance(this.db, this.config);

return this.auth;
const auth = createBetterAuthInstance(this.db, this.config);
this.auth = auth;
return auth;
}
}
8 changes: 6 additions & 2 deletions packages/create-croco-app/src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ function writeSaasProviderProfileArtifacts(targetDir: string, options: Generator
const tenantModelSchema = createTenantModelManifestSchema();
const tenantModelPlaybook = renderTenantModelPlaybook(tenantModelManifest);
const providerProfileDocs = renderSaasDeployNotes(manifest);
const providerEnvExample = renderSaasEnvExample(manifest);
const providerSecretsChecklist = renderSaasSecretsChecklist(manifest);
const docsDir = join(targetDir, "docs");
const apiServerSrcDir = join(targetDir, "apps", "api-server", "src");

Expand All @@ -233,15 +235,17 @@ function writeSaasProviderProfileArtifacts(targetDir: string, options: Generator
join(targetDir, "croco.arch.json"),
`${JSON.stringify(createArchitecturePolicyManifest(options), null, 2)}\n`,
);
writeFileSync(join(targetDir, ".env.example"), renderSaasEnvExample(manifest));
writeFileSync(join(targetDir, ".env.example"), providerEnvExample);
writeFileSync(join(docsDir, "provider-profile.md"), providerProfileDocs);
writeFileSync(join(docsDir, "tenant-model-playbook.md"), tenantModelPlaybook);
writeFileSync(join(docsDir, "secrets-checklist.md"), renderSaasSecretsChecklist(manifest));
writeFileSync(join(docsDir, "secrets-checklist.md"), providerSecretsChecklist);
writeFileSync(
join(apiServerSrcDir, "generatedSaasProviderProfile.ts"),
[
`export const generatedSaasProviderProfileManifest = ${JSON.stringify(manifest, null, 2)} as const;`,
`export const generatedSaasProviderProfileDocs = ${JSON.stringify(providerProfileDocs)} as const;`,
`export const generatedSaasProviderProfileEnvExample = ${JSON.stringify(providerEnvExample)} as const;`,
`export const generatedSaasProviderSecretsChecklist = ${JSON.stringify(providerSecretsChecklist)} as const;`,
"",
].join("\n"),
);
Expand Down
41 changes: 24 additions & 17 deletions packages/create-croco-app/src/saas-provider-profiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import {
type TenantModelCapabilityName,
type TenantModelName,
} from "@croco/tenant-core/tenant-model";
import {
renderSafeEnvExampleValue,
renderSecretPlaceholderPolicyTable,
renderSecretsChecklistPlaceholderItems,
} from "./secret-placeholder-policy.js";

export const SAAS_PROVIDER_PROFILE_CHOICES = [
"saas-node-postgres",
Expand Down Expand Up @@ -103,6 +108,7 @@ export type SaasProviderProfileManifest = {
tenantModelManifest: "croco-tenant-model.manifest.json";
tenantModelSchema: "croco-tenant-model.schema.json";
providerDocs: "docs/provider-profile.md";
secretsChecklist: "docs/secrets-checklist.md";
tenantModelPlaybook: "docs/tenant-model-playbook.md";
envExample: ".env.example";
source: "apps/api-server/src/generatedSaasProviderProfile.ts";
Expand Down Expand Up @@ -491,6 +497,7 @@ export function createSaasProviderProfileManifest(
tenantModelManifest: "croco-tenant-model.manifest.json",
tenantModelSchema: "croco-tenant-model.schema.json",
providerDocs: "docs/provider-profile.md",
secretsChecklist: "docs/secrets-checklist.md",
tenantModelPlaybook: "docs/tenant-model-playbook.md",
envExample: ".env.example",
source: "apps/api-server/src/generatedSaasProviderProfile.ts",
Expand Down Expand Up @@ -574,39 +581,32 @@ export function renderSaasEnvExample(manifest: SaasProviderProfileManifest): str
...[...manifest.env.required, ...manifest.env.optional].flatMap((entry) => [
"",
`# ${entry.description}`,
`${entry.name}=${renderEnvExampleValue(entry, manifest.profile.name)}`,
`${entry.name}=${renderSafeEnvExampleValue(entry, manifest.profile.name)}`,
]),
"",
];

return lines.join("\n");
}

function renderEnvExampleValue(
entry: SaasProviderEnvVar,
profileName: SaasProviderProfileName,
): string {
if (entry.name === "SAAS_PROVIDER_PROFILE") {
return profileName;
}

return entry.example ?? (entry.secret ? "<secret>" : `<${entry.name.toLowerCase()}>`);
}

export function renderSaasSecretsChecklist(manifest: SaasProviderProfileManifest): string {
const requiredSecrets = manifest.env.required.filter((entry) => entry.secret);
const nonSecretConfig = manifest.env.required.filter((entry) => !entry.secret);

return [
`# ${manifest.profile.displayName} Secrets Checklist`,
"",
"## Placeholder Policy",
"",
renderSecretPlaceholderPolicyTable(manifest),
"",
"## Required Secrets",
"",
...requiredSecrets.map((entry) => `- [ ] ${entry.name}: ${entry.description}`),
...renderSecretsChecklistPlaceholderItems(manifest, requiredSecrets),
"",
"## Required Non-Secret Config",
"",
...nonSecretConfig.map((entry) => `- [ ] ${entry.name}: ${entry.description}`),
...renderSecretsChecklistPlaceholderItems(manifest, nonSecretConfig),
"",
"## Real-Provider Smoke",
"",
Expand Down Expand Up @@ -641,6 +641,12 @@ export function renderSaasDeployNotes(manifest: SaasProviderProfileManifest): st
"Quality gates:",
...manifest.compatibility.qualityGates.map((gate) => `- \`${gate}\``),
"",
"## Placeholder Policy",
"",
renderSecretPlaceholderPolicyTable(manifest),
"",
"Generated `.env.example`, provider docs, and secrets checklists must use these safe values. Load real provider credentials from deployment secrets only.",
"",
`Runtime target: \`${manifest.profile.runtimeTarget}\``,
`Tenant model: \`${tenantModel.name}\` (${tenantModel.displayName})`,
"",
Expand Down Expand Up @@ -684,7 +690,8 @@ function envVar(
secret: boolean,
example?: string,
): SaasProviderEnvVar {
return { name, description, requiredForRealProvider, secret, example };
const base = { name, description, requiredForRealProvider, secret };
return example === undefined ? base : { ...base, example };
}

function capability(
Expand All @@ -694,14 +701,14 @@ function capability(
packageName: string | undefined,
env: readonly string[],
): SaasProviderCapability {
return {
const base = {
capability: capabilityName,
provider,
status,
packageName,
env,
notes: `${provider} covers ${capabilityName}.`,
};
return packageName === undefined ? base : { ...base, packageName };
}

function capabilities(
Expand Down
Loading
Loading