handoff-app 2.0.0-12 → 2.0.0-13

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 (136) hide show
  1. package/{Changelog.md → CHANGELOG.md} +84 -0
  2. package/{License.md → LICENSE.md} +1 -1
  3. package/README.md +760 -801
  4. package/RECIPES.md +975 -0
  5. package/components.json +20 -20
  6. package/config/docs/foundations/colors/guidelines.md +0 -1
  7. package/config/docs/foundations/colors.md +0 -1
  8. package/config/docs/foundations/effects/guidelines.md +0 -1
  9. package/config/docs/foundations/effects/resources.md +0 -1
  10. package/config/docs/foundations/effects.md +0 -1
  11. package/config/docs/foundations/fonts.md +0 -1
  12. package/config/docs/foundations/grid.md +0 -1
  13. package/config/docs/foundations/icon.md +0 -1
  14. package/config/docs/foundations/icons/guidelines.md +0 -1
  15. package/config/docs/foundations/icons.md +0 -1
  16. package/config/docs/foundations/images/guidelines.md +0 -1
  17. package/config/docs/foundations/images/resources.md +0 -1
  18. package/config/docs/foundations/images.md +0 -1
  19. package/config/docs/foundations/logo/resources.md +0 -1
  20. package/config/docs/foundations/logo.md +0 -1
  21. package/config/docs/foundations/typography/guidelines.md +0 -1
  22. package/config/docs/foundations/typography/resources.md +0 -1
  23. package/config/docs/foundations/typography.md +0 -1
  24. package/config/docs/foundations.md +0 -1
  25. package/config/docs/guidelines.md +0 -1
  26. package/config/docs/index.md +0 -1
  27. package/config/docs/system/tokens/foundations/colors.md +0 -4
  28. package/config/docs/system/tokens/foundations/effects.md +0 -4
  29. package/config/docs/system/tokens/foundations/typography.md +0 -2
  30. package/config/docs/system.md +0 -2
  31. package/config/templates/page.md +2 -1
  32. package/config/templates/page.mdx +2 -1
  33. package/dist/ai/define.d.ts +1 -1
  34. package/dist/ai/define.js +1 -1
  35. package/dist/ai/types.d.ts +1 -1
  36. package/dist/ai/types.js +1 -1
  37. package/dist/app-builder/build.js +14 -14
  38. package/dist/app-builder/client-config.js +1 -1
  39. package/dist/config/loader.js +1 -2
  40. package/dist/index.d.ts +2 -2
  41. package/dist/index.js +2 -2
  42. package/dist/nav/index.d.ts +2 -2
  43. package/dist/nav/registry.d.ts +3 -3
  44. package/dist/nav/registry.js +1 -1
  45. package/dist/nav/workspace.d.ts +3 -3
  46. package/dist/nav/workspace.js +1 -1
  47. package/dist/providers.d.ts +10 -0
  48. package/dist/{define.js → providers.js} +4 -4
  49. package/dist/registry/asset-storage/define.d.ts +5 -5
  50. package/dist/registry/asset-storage/define.js +4 -4
  51. package/dist/registry/asset-storage/resolve.d.ts +8 -11
  52. package/dist/registry/asset-storage/resolve.js +12 -13
  53. package/dist/registry/asset-storage/types.d.ts +12 -12
  54. package/dist/registry/asset-storage/types.js +2 -2
  55. package/dist/registry/db/schema.d.ts +2 -2
  56. package/dist/registry/db/schema.js +2 -2
  57. package/dist/registry/email/define.d.ts +1 -1
  58. package/dist/registry/email/define.js +1 -1
  59. package/dist/registry/email/resolve.d.ts +7 -8
  60. package/dist/registry/email/resolve.js +21 -23
  61. package/dist/registry/email/types.d.ts +1 -1
  62. package/dist/registry/email/types.js +1 -1
  63. package/dist/store/registry.d.ts +3 -3
  64. package/dist/store/registry.js +4 -4
  65. package/dist/types/config.d.ts +4 -13
  66. package/docs/cli.md +275 -54
  67. package/package.json +7 -7
  68. package/src/ai/define.ts +1 -1
  69. package/src/ai/types.ts +1 -1
  70. package/src/app/components/Auth/AccountLayout.tsx +1 -1
  71. package/src/app/components/Auth/AuthShell.tsx +10 -4
  72. package/src/app/components/Foundations/ColorGrid.tsx +13 -14
  73. package/src/app/components/Layout/Main.tsx +2 -2
  74. package/src/app/components/McpIntegration/McpConfigDialog.tsx +12 -51
  75. package/src/app/components/Navigation/MobileNav.tsx +0 -2
  76. package/src/app/components/util/index.ts +0 -11
  77. package/src/app/lib/asset-storage.ts +25 -25
  78. package/src/app/lib/auth/email.ts +4 -5
  79. package/src/app/lib/docs-api/page-rendering.ts +1 -1
  80. package/src/app/lib/docs-api/registry.ts +2 -3
  81. package/src/app/lib/docs-api/runtime-config.ts +8 -8
  82. package/src/app/lib/registry-api/asset-transfer.ts +25 -25
  83. package/src/app/lib/server-module.ts +1 -1
  84. package/src/app/lib/utils.ts +7 -0
  85. package/src/app/next.config.mjs +2 -2
  86. package/src/app/pages/[...slug]/index.tsx +2 -1
  87. package/src/app/pages/account/ai.tsx +1 -1
  88. package/src/app/pages/account/index.tsx +1 -1
  89. package/src/app/pages/account/tokens.tsx +1 -1
  90. package/src/app/pages/account/users.tsx +1 -1
  91. package/src/app/pages/api/docs/nav.json.ts +1 -1
  92. package/src/app/pages/api/mcp.ts +6 -0
  93. package/src/app/pages/api/registry/transfer/assets/blobs/[hash].ts +1 -1
  94. package/src/app/pages/cli/device.tsx +1 -1
  95. package/src/app/pages/foundations/colors.tsx +9 -8
  96. package/src/app/pages/foundations/index.tsx +3 -3
  97. package/src/app/pages/index.tsx +2 -2
  98. package/src/app/pages/login.tsx +10 -5
  99. package/src/app/pages/reset-password.tsx +7 -2
  100. package/src/app/pages/system/component/[component]/index.tsx +2 -5
  101. package/src/app/pages/system/index.tsx +11 -11
  102. package/src/app/pages/system/pattern/[pattern]/index.tsx +2 -2
  103. package/src/app/pages/system/tokens/components/[component]/index.tsx +0 -4
  104. package/src/app-builder/build.ts +10 -10
  105. package/src/app-builder/client-config.ts +1 -1
  106. package/src/config/loader.ts +1 -2
  107. package/src/index.ts +6 -6
  108. package/src/nav/index.ts +2 -2
  109. package/src/nav/registry.ts +3 -3
  110. package/src/nav/workspace.ts +3 -3
  111. package/src/{define.ts → providers.ts} +8 -8
  112. package/src/registry/asset-storage/define.ts +6 -6
  113. package/src/registry/asset-storage/resolve.ts +16 -20
  114. package/src/registry/asset-storage/types.ts +12 -12
  115. package/src/registry/db/schema.ts +3 -3
  116. package/src/registry/email/define.ts +1 -1
  117. package/src/registry/email/resolve.ts +23 -23
  118. package/src/registry/email/types.ts +1 -1
  119. package/src/store/registry.ts +7 -7
  120. package/src/types/config.ts +4 -14
  121. package/.editorconfig +0 -19
  122. package/.eslintrc.json +0 -4
  123. package/.prettierignore +0 -0
  124. package/.prettierrc.json +0 -8
  125. package/.vscode/settings.json +0 -17
  126. package/dist/define.d.ts +0 -10
  127. package/dist/registry/email/adapters/resend.d.ts +0 -7
  128. package/dist/registry/email/adapters/resend.js +0 -35
  129. package/src/app/components/old/Header.tsx +0 -89
  130. package/src/app/pages/assets/fonts.tsx +0 -129
  131. package/src/app/pages/assets/index.tsx +0 -91
  132. package/src/app/pages/assets/logos.tsx +0 -75
  133. package/src/registry/email/adapters/resend.ts +0 -22
  134. /package/dist/registry/email/{adapters → providers}/smtp.d.ts +0 -0
  135. /package/dist/registry/email/{adapters → providers}/smtp.js +0 -0
  136. /package/src/registry/email/{adapters → providers}/smtp.ts +0 -0
@@ -1,13 +1,13 @@
1
1
  /**
2
- * Server-only asset storage contract for custom {@link defineAssetStorage} object storage modules.
2
+ * Server-only asset storage contract for custom {@link defineStorageProvider} object storage modules.
3
3
  * The built-in `database` provider is the inline default: bytes live in the `asset_blobs.content`
4
- * bytea column, read/written directly by the registry, so it needs no adapter. Object providers
4
+ * bytea column, read/written directly by the registry, so it needs no module. Object providers
5
5
  * record a `storageRef` + provider id so reads resolve back through whichever provider stored the
6
6
  * blob.
7
7
  */
8
8
 
9
9
  /** Input for storing one content-addressed blob. */
10
- export interface AssetStorageInput {
10
+ export interface StorageInput {
11
11
  /** SHA-256 of the bytes (hex), the content identity, usable as a stable object key. */
12
12
  hash: string;
13
13
  bytes: Buffer;
@@ -19,7 +19,7 @@ export interface AssetStorageInput {
19
19
  * How a provider returns stored content: raw bytes, a readable stream, or a signed/redirect URL the
20
20
  * client can fetch directly (used to avoid routing large payloads through the serverless function).
21
21
  */
22
- export type AssetStorageReadResult =
22
+ export type StorageReadResult =
23
23
  | { kind: 'bytes'; bytes: Buffer; contentType?: string }
24
24
  | { kind: 'stream'; stream: NodeJS.ReadableStream; contentType?: string }
25
25
  | { kind: 'redirect'; url: string };
@@ -40,10 +40,10 @@ export interface AssetUpload {
40
40
  storageRef: string;
41
41
  }
42
42
 
43
- /** The pluggable storage adapter contract for object-backed asset providers. */
44
- export interface AssetStorage {
43
+ /** The pluggable storage contract for object-backed asset providers. */
44
+ export interface StorageProvider {
45
45
  /** Store one blob and return the reference to persist on its `asset_blobs` row. */
46
- put(input: AssetStorageInput): Promise<{ storageRef: string }>;
46
+ put(input: StorageInput): Promise<{ storageRef: string }>;
47
47
  /**
48
48
  * Return a signed URL so the CLI uploads the blob directly to storage instead of through the
49
49
  * registry, which avoids serverless request size limits. The URL must make storage reject bytes
@@ -51,18 +51,18 @@ export interface AssetStorage {
51
51
  */
52
52
  createUpload?(input: AssetUploadInput): Promise<AssetUpload>;
53
53
  /** Resolve stored content by its `storageRef`. */
54
- get(storageRef: string): Promise<AssetStorageReadResult>;
54
+ get(storageRef: string): Promise<StorageReadResult>;
55
55
  /** Delete stored content by its `storageRef`. Should be retryable/idempotent. */
56
56
  delete(storageRef: string): Promise<void>;
57
57
  }
58
58
 
59
- /** Context passed to a custom adapter factory: its config options and the process env. */
60
- export interface AssetStorageContext {
59
+ /** Context passed to a custom provider factory: its config options and the process env. */
60
+ export interface StorageProviderContext {
61
61
  /** `runtime.registry.assetStorage.options`, with each `fromEnv()` reference replaced by its value. */
62
62
  options: Record<string, unknown>;
63
63
  /** The process environment. */
64
64
  env: NodeJS.ProcessEnv;
65
65
  }
66
66
 
67
- /** A custom adapter may export the adapter directly or a factory that builds it from its context. */
68
- export type AssetStorageFactory = (context: AssetStorageContext) => AssetStorage | Promise<AssetStorage>;
67
+ /** A custom provider module may export the provider directly or a factory that builds it from its context. */
68
+ export type StorageProviderFactory = (context: StorageProviderContext) => StorageProvider | Promise<StorageProvider>;
@@ -30,7 +30,7 @@ import type { TokenSetKind } from '../tokens/sets';
30
30
 
31
31
  /**
32
32
  * PostgreSQL `bytea` column. Drizzle pg-core has no built-in binary type, so declare it once here.
33
- * Reads/writes are Node `Buffer`s. The default asset storage adapter keeps small/ordinary asset
33
+ * Reads/writes are Node `Buffer`s. The default asset storage provider keeps small/ordinary asset
34
34
  * bytes inline in this column; larger assets are offloaded to an object-storage `storageRef` instead.
35
35
  */
36
36
  const bytea = customType<{ data: Buffer; driverData: Buffer }>({
@@ -39,7 +39,7 @@ const bytea = customType<{ data: Buffer; driverData: Buffer }>({
39
39
  },
40
40
  });
41
41
 
42
- /** Where an asset blob's bytes live: inline in the database (`database`) or in the custom adapter (`custom`). */
42
+ /** Where an asset blob's bytes live: inline in the database (`database`) or in a custom provider (`custom`). */
43
43
  export type AssetStorageProvider = 'database' | 'custom';
44
44
  import type { ComponentListObject, PageListObject, PatternComponentEntry, PatternListObject } from '../../transformers/preview/types';
45
45
 
@@ -432,7 +432,7 @@ export const assets = pgTable(
432
432
  /**
433
433
  * Content-addressed binary blob store, shared across assets/collections (the `hash` is the blob
434
434
  * identity, so an unchanged file re-references the same row). Exactly one location is active per row:
435
- * inline `content` (bytea, default DB adapter) or an external `storageRef` resolved through the
435
+ * inline `content` (bytea, default `database` provider) or an external `storageRef` resolved through the
436
436
  * recorded `storageProvider`. Both nullable so DB- and object-backed blobs coexist.
437
437
  */
438
438
  export const assetBlobs = pgTable('asset_blobs', {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Typed identity helper for custom email providers (mirrors `defineAssetStorage`): a consumer
2
+ * Typed identity helper for custom email providers (mirrors `defineStorageProvider`): a consumer
3
3
  * default-exports its result from a **server-only** module and points `runtime.registry.email.module`
4
4
  * at it. It must be its own module (not an inline `defineConfig` function) so the build can trace it
5
5
  * into the deployed registry. The default export may be the provider object, or a factory
@@ -1,33 +1,29 @@
1
1
  /**
2
2
  * Registry email settings resolution.
3
3
  *
4
- * `runtime.registry.email.provider` selects the built-in `resend` or `smtp` provider, or a `custom`
5
- * module. The build bakes the provider, the module location, and the options. An option literal is
6
- * baked as it is. An environment reference stays `{ $env }` and is resolved at request time.
4
+ * `runtime.registry.email.provider` selects the built-in `smtp` provider or a `custom` module. Without
5
+ * an email block, email delivery is off. The build bakes the provider, the module location, and the
6
+ * options. An option literal is baked as it is. An environment reference stays `{ $env }` and is
7
+ * resolved at request time.
7
8
  */
8
9
 
9
10
  import { HandoffConfigError } from '../../config/errors';
10
11
  import type { ResolvedConfig } from '../../types/config';
11
12
 
12
- export type EmailProviderKind = 'resend' | 'smtp' | 'custom';
13
+ export type EmailProviderKind = 'smtp' | 'custom';
13
14
 
14
- export const DEFAULT_EMAIL_PROVIDER: EmailProviderKind = 'resend';
15
-
16
- /** Default env-var name holding the Resend API key. */
17
- export const DEFAULT_RESEND_API_KEY_ENV = 'RESEND_API_KEY';
18
-
19
- const PROVIDERS: readonly EmailProviderKind[] = ['resend', 'smtp', 'custom'];
15
+ const PROVIDERS: readonly EmailProviderKind[] = ['smtp', 'custom'];
20
16
 
21
17
  /** The options each built-in provider accepts. A custom provider accepts any. */
22
18
  const BUILT_IN_OPTIONS: Record<Exclude<EmailProviderKind, 'custom'>, readonly string[]> = {
23
- resend: ['apiKey'],
24
19
  smtp: ['host', 'port', 'secure', 'user', 'password'],
25
20
  };
26
21
 
27
22
  /** Email settings as they are baked: literals and `{ $env }` references, never secret values. */
28
23
  export interface EmailSettings {
29
24
  from?: string;
30
- provider: EmailProviderKind;
25
+ /** Unset when the config has no email block. */
26
+ provider?: EmailProviderKind;
31
27
  /** For `custom`: the server-only provider module path. */
32
28
  module?: string;
33
29
  options: Record<string, unknown>;
@@ -38,25 +34,30 @@ const isPlainRecord = (value: unknown): value is Record<string, unknown> =>
38
34
 
39
35
  /** Resolve and validate the email block from a loaded config (CLI/build side). An unusable block fails the build. */
40
36
  export const resolveEmailFromConfig = (config: ResolvedConfig | null | undefined): EmailSettings => {
41
- const email = config?.runtime?.registry?.email as Record<string, unknown> | undefined;
37
+ const email = config?.runtime?.registry?.email as unknown as Record<string, unknown> | undefined;
42
38
  const fail = (problem: string): never => {
43
39
  throw new HandoffConfigError(`Config "runtime.registry.email": ${problem}`);
44
40
  };
45
41
 
46
- const provider = (email?.provider ?? DEFAULT_EMAIL_PROVIDER) as EmailProviderKind;
42
+ if (email === undefined) return { options: {} };
43
+ const provider = email.provider as EmailProviderKind;
47
44
  if (!PROVIDERS.includes(provider)) {
48
- fail(`unknown provider ${JSON.stringify(provider)}. Use "resend", "smtp" or "custom".`);
45
+ fail(
46
+ email.provider === undefined
47
+ ? '"provider" is required. Use "smtp" or "custom".'
48
+ : `unknown provider ${JSON.stringify(provider)}. Use "smtp" or "custom".`
49
+ );
49
50
  }
50
- if (email?.options !== undefined && !isPlainRecord(email.options)) {
51
+ if (email.options !== undefined && !isPlainRecord(email.options)) {
51
52
  fail('"options" must be an object.');
52
53
  }
53
- const options = { ...((email?.options as Record<string, unknown> | undefined) ?? {}) };
54
- const module = typeof email?.module === 'string' ? email.module.trim() : undefined;
54
+ const options = { ...((email.options as Record<string, unknown> | undefined) ?? {}) };
55
+ const module = typeof email.module === 'string' ? email.module.trim() : undefined;
55
56
 
56
57
  if (provider === 'custom') {
57
58
  if (!module) fail('provider "custom" needs a "module" that default-exports a defineEmailProvider() result.');
58
59
  } else {
59
- if (email?.module !== undefined) fail(`"module" is used only by provider "custom", not "${provider}".`);
60
+ if (email.module !== undefined) fail(`"module" is used only by provider "custom", not "${provider}".`);
60
61
  const allowed = BUILT_IN_OPTIONS[provider];
61
62
  const unknown = Object.keys(options).filter((key) => !allowed.includes(key));
62
63
  if (unknown.length > 0) {
@@ -67,9 +68,8 @@ export const resolveEmailFromConfig = (config: ResolvedConfig | null | undefined
67
68
  }
68
69
  }
69
70
  if (provider === 'smtp' && options.host === undefined) fail('provider "smtp" needs "options.host".');
70
- if (provider === 'resend' && options.apiKey === undefined) options.apiKey = { $env: DEFAULT_RESEND_API_KEY_ENV };
71
71
 
72
- const from = typeof email?.from === 'string' ? email.from.trim() || undefined : undefined;
72
+ const from = typeof email.from === 'string' ? email.from.trim() || undefined : undefined;
73
73
  return { from, provider, ...(module ? { module } : {}), options };
74
74
  };
75
75
 
@@ -83,11 +83,11 @@ export const parseEmailSettings = (raw: { from?: unknown; provider?: unknown; mo
83
83
  options = {};
84
84
  }
85
85
  }
86
- const provider = PROVIDERS.find((kind) => kind === raw.provider) ?? DEFAULT_EMAIL_PROVIDER;
86
+ const provider = PROVIDERS.find((kind) => kind === raw.provider);
87
87
  const module = typeof raw.module === 'string' ? raw.module.trim() : '';
88
88
  return {
89
89
  from: typeof raw.from === 'string' ? raw.from.trim() || undefined : undefined,
90
- provider,
90
+ ...(provider ? { provider } : {}),
91
91
  ...(module ? { module } : {}),
92
92
  options: isPlainRecord(options) ? options : {},
93
93
  };
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Server-only email provider contract. Handoff renders every message, and a provider only delivers it.
3
- * Resend and SMTP are built in. Anything else is a {@link defineEmailProvider} module.
3
+ * SMTP is built in. Anything else is a {@link defineEmailProvider} module.
4
4
  */
5
5
 
6
6
  /** One rendered message. */
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { orderPreviews } from '../catalog/previews';
18
18
  import { and, eq, inArray, or, sql, type SQLWrapper } from 'drizzle-orm';
19
- import type { AssetStorage, AssetStorageReadResult } from '../registry/asset-storage/types';
19
+ import type { StorageProvider, StorageReadResult } from '../registry/asset-storage/types';
20
20
  import type { RegistryDatabase } from '../registry/db/client';
21
21
  import {
22
22
  assetBlobs,
@@ -53,11 +53,11 @@ import type {
53
53
  export interface RegistryStoreContext {
54
54
  db: RegistryDatabase;
55
55
  /**
56
- * Resolve the {@link AssetStorage} adapter for a blob's recorded provider (or `null` for the inline
56
+ * Resolve the {@link StorageProvider} for a blob's recorded provider (or `null` for the inline
57
57
  * `database` provider). Injected by the app runtime so `src/store` stays free of app-side imports;
58
58
  * when omitted, only inline DB-backed asset content is resolvable.
59
59
  */
60
- resolveAssetAdapter?: (provider: AssetStorageProvider) => Promise<AssetStorage | null>;
60
+ resolveStorageProvider?: (provider: AssetStorageProvider) => Promise<StorageProvider | null>;
61
61
  }
62
62
 
63
63
  /** A persisted text-file row, as selected from `component_files`/`pattern_files`. */
@@ -422,14 +422,14 @@ export class RegistryAssetStore implements AssetStore {
422
422
  }
423
423
 
424
424
  // Object-backed - resolve through the provider that stored it.
425
- if (!row.storageRef || !this.context.resolveAssetAdapter) {
425
+ if (!row.storageRef || !this.context.resolveStorageProvider) {
426
426
  return null;
427
427
  }
428
- const adapter = await this.context.resolveAssetAdapter(row.storageProvider);
429
- if (!adapter) {
428
+ const storage = await this.context.resolveStorageProvider(row.storageProvider);
429
+ if (!storage) {
430
430
  return null;
431
431
  }
432
- const result: AssetStorageReadResult = await adapter.get(row.storageRef);
432
+ const result: StorageReadResult = await storage.get(row.storageRef);
433
433
  if (result.kind === 'redirect') {
434
434
  return { ...base, redirectUrl: result.url };
435
435
  }
@@ -129,16 +129,6 @@ interface HandoffEmailBase {
129
129
  from?: string;
130
130
  }
131
131
 
132
- /** Send through the Resend HTTP API. */
133
- export interface HandoffResendEmail extends HandoffEmailBase {
134
- /** @default "resend" */
135
- provider?: 'resend';
136
- options?: {
137
- /** Environment reference to the Resend API key. @default fromEnv('RESEND_API_KEY') */
138
- apiKey?: RuntimeEnvReference<string>;
139
- };
140
- }
141
-
142
132
  /** Send through an SMTP server, such as Amazon SES, SendGrid, Postmark, Mailgun or Microsoft 365. */
143
133
  export interface HandoffSmtpEmail extends HandoffEmailBase {
144
134
  provider: 'smtp';
@@ -170,7 +160,7 @@ export interface HandoffCustomEmail extends HandoffEmailBase {
170
160
  }
171
161
 
172
162
  /** Registry email settings. `provider` selects the shape of `options`. */
173
- export type HandoffEmailConfig = HandoffResendEmail | HandoffSmtpEmail | HandoffCustomEmail;
163
+ export type HandoffEmailConfig = HandoffSmtpEmail | HandoffCustomEmail;
174
164
 
175
165
  /**
176
166
  * One AI connection the deployment declares. Connections are the whole surface: a reader can add a
@@ -277,13 +267,13 @@ export interface HandoffRuntimeConfig {
277
267
  * `custom` module for object storage.
278
268
  * @default "database"
279
269
  */
280
- adapter?: 'database' | 'custom';
281
- /** For `adapter: "custom"` - server-only module path default-exporting a `defineAssetStorage` adapter. */
270
+ provider?: 'database' | 'custom';
271
+ /** For `provider: "custom"` - server-only module path default-exporting a `defineStorageProvider()` result. */
282
272
  module?: string;
283
273
  /** Max bytes kept inline in the database `bytea` column (larger uploads are rejected). @default 4194304 */
284
274
  maxInlineBytes?: number;
285
275
  /**
286
- * Options passed to the adapter factory. Literals are baked. Environment references are resolved
276
+ * Options passed to the provider factory. Literals are baked. Environment references are resolved
287
277
  * at request time, so secrets must be environment references.
288
278
  */
289
279
  options?: Record<string, unknown>;
package/.editorconfig DELETED
@@ -1,19 +0,0 @@
1
- # EditorConfig is awesome: https://EditorConfig.org
2
-
3
- # top-most EditorConfig file
4
- root = true
5
-
6
- [*.{css,scss,sass}]
7
- charset = utf-8
8
- indent_style = tab
9
- indent_size = unset
10
- end_of_line = lf
11
- trim_trailing_whitespace = true
12
- insert_final_newline = true
13
-
14
- [*.{js,ts,jsx,tsx}]
15
- charset = utf-8
16
- indent_style = space
17
- end_of_line = lf
18
- trim_trailing_whitespace = true
19
- insert_final_newline = true
package/.eslintrc.json DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "extends": ["next", "prettier"],
3
- "ignorePatterns": ["./src/app/out/**/*", "**/*.js"]
4
- }
package/.prettierignore DELETED
File without changes
package/.prettierrc.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "trailingComma": "es5",
3
- "semi": true,
4
- "tabWidth": 2,
5
- "singleQuote": true,
6
- "jsxSingleQuote": true,
7
- "plugins": ["prettier-plugin-tailwindcss"]
8
- }
@@ -1,17 +0,0 @@
1
- {
2
- "editor.defaultFormatter": "esbenp.prettier-vscode",
3
- "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
4
- "editor.formatOnSave": true,
5
- "editor.codeActionsOnSave": {
6
- "source.organizeImports": "explicit"
7
- },
8
- "[xml]": {
9
- "editor.defaultFormatter": "redhat.vscode-xml"
10
- },
11
- "[typescriptreact]": {
12
- "editor.defaultFormatter": "vscode.typescript-language-features"
13
- },
14
- "[markdown]": {
15
- "editor.defaultFormatter": "unifiedjs.vscode-remark"
16
- }
17
- }
package/dist/define.d.ts DELETED
@@ -1,10 +0,0 @@
1
- /**
2
- * Define helpers for custom server modules. This entry point has no runtime dependencies, so a module
3
- * that imports it does not pull the CLI into the registry bundle.
4
- */
5
- export { defineAiProvider } from './ai/define';
6
- export type { AiProvider, AiProviderContext, AiProviderFactory } from './ai/types';
7
- export { defineAssetStorage } from './registry/asset-storage/define';
8
- export type { AssetStorage, AssetStorageContext, AssetStorageFactory, AssetStorageInput, AssetStorageReadResult, AssetUpload, AssetUploadInput, } from './registry/asset-storage/types';
9
- export { defineEmailProvider } from './registry/email/define';
10
- export type { EmailMessage, EmailProvider, EmailProviderContext, EmailProviderFactory } from './registry/email/types';
@@ -1,7 +0,0 @@
1
- /**
2
- * Built-in Resend provider, selected with `runtime.registry.email.provider: "resend"` (the default).
3
- * A plain `fetch` to the Resend HTTP API, so it adds no dependency.
4
- */
5
- import type { EmailProvider } from '../types';
6
- /** Build a Resend provider from options whose references are already resolved. */
7
- export declare const createResendProvider: (options: Record<string, unknown>) => EmailProvider;
@@ -1,35 +0,0 @@
1
- "use strict";
2
- /**
3
- * Built-in Resend provider, selected with `runtime.registry.email.provider: "resend"` (the default).
4
- * A plain `fetch` to the Resend HTTP API, so it adds no dependency.
5
- */
6
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
7
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
8
- return new (P || (P = Promise))(function (resolve, reject) {
9
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
10
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
11
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
12
- step((generator = generator.apply(thisArg, _arguments || [])).next());
13
- });
14
- };
15
- Object.defineProperty(exports, "__esModule", { value: true });
16
- exports.createResendProvider = void 0;
17
- /** Build a Resend provider from options whose references are already resolved. */
18
- const createResendProvider = (options) => ({
19
- send(_a) {
20
- return __awaiter(this, arguments, void 0, function* ({ from, to, subject, html, text }) {
21
- const apiKey = typeof options.apiKey === 'string' ? options.apiKey : '';
22
- if (!apiKey)
23
- throw new Error('The Resend API key is not set.');
24
- const response = yield fetch('https://api.resend.com/emails', {
25
- method: 'POST',
26
- headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
27
- body: JSON.stringify({ from, to: [to], subject, html, text }),
28
- });
29
- if (!response.ok) {
30
- throw new Error(`Resend rejected the email (HTTP ${response.status}): ${yield response.text()}`);
31
- }
32
- });
33
- },
34
- });
35
- exports.createResendProvider = createResendProvider;
@@ -1,89 +0,0 @@
1
- import { ClientConfig } from '@handoff/types/config';
2
- import Link from 'next/link';
3
- import React from 'react';
4
- import NavLink from '../NavLink';
5
- import { useNav } from '../context/NavProvider';
6
-
7
- interface HeaderProps {
8
- config: ClientConfig;
9
- }
10
-
11
- function Header({ config }: HeaderProps) {
12
- const { menu } = useNav();
13
- const [mobile, setMobile] = React.useState('');
14
- const toggle = () => {
15
- if (mobile === 'is-active') {
16
- setMobile('');
17
- } else {
18
- setMobile('is-active');
19
- }
20
- };
21
-
22
- return (
23
- <>
24
- <header id="site-header" className="c-site-header">
25
- <div className="o-container-fluid">
26
- <div className="c-site-header__wrapper">
27
- <div>
28
- <div className="c-site-menu">
29
- <button className={`c-hamburger-icon c-hamburger-icon--slider ${mobile}`} onClick={toggle}>
30
- <div className="c-hamburger-icon__box">
31
- <div className="c-hamburger-icon__inner"></div>
32
- </div>
33
- </button>
34
- </div>
35
- <div className="c-site-title">
36
- <Link href="/" className="c-site-logo c-site-logo--basic" title="" rel="home" aria-label="logo">
37
- {/* eslint-disable-next-line @next/next/no-img-element */}
38
- <img src={`${process.env.HANDOFF_APP_BASE_PATH ?? ''}/logo.svg`} alt={config?.app?.title} />
39
- </Link>
40
- </div>
41
-
42
- <nav className="c-site-nav">
43
- <ul>
44
- {menu.map((item) => (
45
- <li key={item.path}>
46
- <NavLink href={`${item.path}`}>{item.title}</NavLink>
47
- </li>
48
- ))}
49
- </ul>
50
- </nav>
51
- </div>
52
- <div>
53
- {
54
- // TODO: Reimpliment cmd k search
55
- false && <small>hit cmd+k to search</small>
56
- }
57
- </div>
58
- </div>
59
- </div>
60
- </header>
61
- <div className={`c-offcanvas c-offcanvas--menu ${mobile}`}>
62
- <div className="c-offcanvas__inner">
63
- <nav className="c-mobilenav">
64
- <ul className="c-mobilenav__menu">
65
- {menu.map((item) => (
66
- <li key={`mobile-${item.path}`}>
67
- <NavLink href={`${item.path}`}>{item.title}</NavLink>
68
- {item.subSections.length > 0 && (
69
- <ul className="c-mobilenav__submenu">
70
- {item.subSections
71
- .filter((sub) => sub.path)
72
- .map((sub) => (
73
- <li key={sub.path}>
74
- <NavLink href={`${sub.path}`}>{sub.title}</NavLink>
75
- </li>
76
- ))}
77
- </ul>
78
- )}
79
- </li>
80
- ))}
81
- </ul>
82
- </nav>
83
- </div>
84
- </div>
85
- </>
86
- );
87
- }
88
-
89
- export default Header;
@@ -1,129 +0,0 @@
1
- import * as fs from 'fs-extra';
2
- import uniq from 'lodash/uniq';
3
- import { FileArchive } from 'lucide-react';
4
- import type { GetStaticProps } from 'next';
5
- import Head from 'next/head';
6
- import path from 'path';
7
- import * as React from 'react';
8
- import ReactMarkdown from 'react-markdown';
9
- import rehypeRaw from 'rehype-raw';
10
- import remarkGfm from 'remark-gfm';
11
- import Footer from '../../components/Footer';
12
- import { MarkdownComponents, remarkCodeMeta } from '../../components/Markdown/MarkdownComponents';
13
- import Header from '../../components/old/Header';
14
- import { buildTimeFoundationDesign, fetchDocPageMarkdown, FontDocumentationProps, getClientRuntimeConfig } from '../../components/util';
15
- import { useFoundationTokens } from '../../components/util/useFoundationTokens';
16
-
17
- const basePath = process.env.HANDOFF_APP_BASE_PATH ?? '';
18
- const isRegistry = process.env.HANDOFF_RUNTIME_MODE === 'registry';
19
-
20
- /**
21
- * This statically renders content from the markdown, creating menu and providing
22
- * metadata
23
- *
24
- * This is all done statically at build time
25
- * @param context GetStaticProps
26
- * @returns
27
- */
28
- export const getStaticProps: GetStaticProps = async () => {
29
- // Workspace/static reads the mirrored font archives; a registry build has none (hydrated at runtime).
30
- const customFonts: string[] = [];
31
- try {
32
- const fontDir = path.resolve(process.env.HANDOFF_MODULE_PATH ?? '', '.handoff', `${process.env.HANDOFF_PROJECT_ID}`, 'public', 'fonts');
33
- for (const font of fs.readdirSync(fontDir)) {
34
- if (font.endsWith('.zip')) customFonts.push(font.replace('.zip', ''));
35
- }
36
- } catch {
37
- // No local font archives (e.g. a registry build), so customFonts stays empty.
38
- }
39
-
40
- return {
41
- props: {
42
- ...(await fetchDocPageMarkdown('docs/assets/', 'fonts', `/assets`)).props,
43
- design: buildTimeFoundationDesign(),
44
- config: getClientRuntimeConfig(),
45
- customFonts,
46
- },
47
- };
48
- };
49
-
50
- const FontsPage = ({ content, menu, metadata, customFonts, design, config }: FontDocumentationProps) => {
51
- // Registry hydrates typography tokens (for the family list) + the published font archives at runtime.
52
- const tokens = useFoundationTokens('typography', { design });
53
- const [hydratedFonts, setHydratedFonts] = React.useState<string[]>(customFonts);
54
- React.useEffect(() => {
55
- if (!isRegistry) return;
56
- let active = true;
57
- fetch(`${basePath}/api/docs/assets/fonts`)
58
- .then((res) => (res.ok ? res.json() : null))
59
- .then((data) => {
60
- if (!active || !data?.assets) return;
61
- const names = (data.assets as { path: string }[])
62
- .filter((a) => typeof a.path === 'string' && /^fonts\/.+\.zip$/i.test(a.path))
63
- .map((a) => a.path.slice('fonts/'.length).replace(/\.zip$/i, ''));
64
- setHydratedFonts(names);
65
- })
66
- .catch(() => {});
67
- return () => {
68
- active = false;
69
- };
70
- }, []);
71
-
72
- const typography = tokens.design?.typography ?? [];
73
- const fontFamilies: string[] = uniq(typography.map((type) => type.values.fontFamily));
74
- const fontLinks: string[] = fontFamilies.map((fontFamily) => {
75
- const machineName = fontFamily.replace(/\s/g, '');
76
- const custom = hydratedFonts.find((font) => font === machineName);
77
- if (custom) {
78
- return isRegistry ? `${basePath}/api/docs/assets/fonts/fonts/${machineName}.zip` : `/fonts/${machineName}.zip`;
79
- }
80
- return `https://fonts.google.com/specimen/${fontFamily}`;
81
- });
82
- return (
83
- <div className="c-page">
84
- <Head>
85
- <title>{metadata.metaTitle}</title>
86
- <meta name="description" content={metadata.metaDescription} />
87
- </Head>
88
- <Header config={config} />
89
- <section className="c-content">
90
- <div className="o-container-fluid">
91
- <div className="c-hero">
92
- <div>
93
- <h1>{metadata.title}</h1>
94
- <p>{metadata.description}</p>
95
- </div>
96
- </div>
97
- {fontFamilies.map((fontFamily, i) => (
98
- <React.Fragment key={fontFamily}>
99
- <div className="o-row u-justify-between">
100
- <div className="o-col-5@md">
101
- <h4>{fontFamily}</h4>
102
- </div>
103
- <div className="o-col-6@md">
104
- <div className="c-card">
105
- <FileArchive />
106
- <h4>{fontFamily}</h4>
107
- <p>Font files for installing on a local machine.</p>
108
- <p>
109
- <a href={fontLinks[i]}>Download Font</a>
110
- </p>
111
- </div>
112
- </div>
113
- </div>
114
- <hr />
115
- </React.Fragment>
116
- ))}
117
-
118
- <div className="prose">
119
- <ReactMarkdown components={MarkdownComponents} remarkPlugins={[remarkGfm, remarkCodeMeta]} rehypePlugins={[rehypeRaw]}>
120
- {content}
121
- </ReactMarkdown>
122
- </div>
123
- </div>
124
- </section>
125
- <Footer config={config} />
126
- </div>
127
- );
128
- };
129
- export default FontsPage;