CloudCannon / bookshop

📚 A component development workflow for static websites.
MIT License
245 stars 21 forks source link

Sync/combine types with blueprints #130

Open silveltman opened 1 year ago

silveltman commented 1 year ago

Just starting out with typescript in my projects, to create interfaces for my UI components.

I see a lot af similarities between the typescript interface and the bookshop blueprint. What would be a good when using both of those in a project?

Like I said, just starting out with typescript, so not sure if this question even makes sense 😅

I'm using Asrto with Svelte components btw. Did take a look at the astro-sendit template, but my understanding of react is very limited.

silveltman commented 1 year ago

would a folder structure like this work?

src
|--
  |--components
      |--car
        |-- car.types.ts
        |-- car.svelte
        |-- car.bookshop.yaml

Alternatively, maybe this:

component-library
|--
  |--components
      |--car
        |-- car.types.ts
        |-- car.bookshop.yaml
src
|--
  |--components
      |--car
        |-- car.svelte
bglw commented 1 year ago

Hmm it's a good discussion — I don't know of (m)any existing sites with both Typescript definitions and Bookshop blueprints. I think this is an area we'll start to see more action in soon, so I'll probably be waiting to see what crops up. If you land on anything compelling let me know!

As an aside, we're working on first party Astro Bookshop support which might help build these sites out. Not yet sure how that will interact with typed components though. Will keep you posted!

silveltman commented 1 year ago

Will do! 👌

Any indication on a release for the first party Astro Bookshop support? Very excited for that 😬😬

bglw commented 1 year ago

No indication yet! Looking at reviewing progress on that soon, so hopefully I'll be back with an update in the near future 🤞

silveltman commented 9 months ago

Recently started working extensively with Bookshop again, with first party Astro.

I like how bookshop structures enforce the right schema when things come from cloudcannon. However, when working locally it's easy to make type mistakes, for which I won't get any errors.

I also like Astro's schema's using zod. These bring full markdown/yaml typescript editor support and error handling.

At the moment, for schema's, i create them using both the Astro and Cloucannon approach:

// cloudcannon schema
---
title:
description:
image:
  src:
  alt:

seo:
  title:
  description:
  image:
    src:
    alt:
---

// astro schema
import { z } from 'astro:content'
import { description, image, seo, title } from '..'

export const brandsSchema = z.object({
  title,
  description,
  image,
  seo,
})

It's OK, but it could be simplified since they're basically identical. I think one could be generated from the other. I think zod >> markdown makes more sense, since it's typescript therefore easier to read AND is has more options.

The same could be done for bookshop components aswell.

silveltman commented 9 months ago

Fixed it for collection schema's using ts-to-zod, zod-fixtures adn js-yaml.

I start with a ts interface > generate zod scehama > generate markdown schema.

zod-fixtures allows for default values, which seems like a good usecase for bookshop component blueprints/previews

import { exec } from 'child_process'
import fs from 'fs'
import yaml from 'js-yaml'
import { promisify } from 'util'
import { ZodAny, ZodNumber, ZodString } from 'zod'
import { Fixture, Generator } from 'zod-fixture'

const execAsync = promisify(exec)

async function generateSchemas(collection) {
  if (!collection) return
  try {
    // Run the 'ts-to-zod' command synchronously

    await execAsync(
      `yarn ts-to-zod ./schemas/collections/${collection}/${collection}Schema.d.ts ./schemas/collections/${collection}/${collection}Schema.ts`
    ).then(() => console.log('Successfully generated Zod schema'))

    // Import the generated Zod schema
    const zodSchema = await import(
      `./collections/${collection}/${collection}Schema.ts`
    )

    const schema = zodSchema[collection + 'Schema']

    // Custom generator for nullable strings
    const stringGenerator = Generator({
      schema: ZodString,
      output: () => null,
    })

    // Custom generator for any
    const anyGenerator = Generator({
      schema: ZodAny,
      output: () => null,
    })

    // Custom generator for any
    const numberGenerator = Generator({
      schema: ZodNumber,
      output: () => null,
    })

    // Create a fixture with the custom generator
    const fixture = new Fixture({
      seed: 1,
      array: {
        min: 1,
        max: 1,
      },
    }).extend([stringGenerator, anyGenerator, numberGenerator])
    const fixtureSchema = fixture.fromSchema(schema)

    // Convert to YAML
    const yamlFixture = yaml.dump(fixtureSchema)

    // Write to Markdown file
    fs.writeFileSync(
      `./schemas/collections/${collection}/${collection}Schema.md`,
      `---\n${yamlFixture}---`
    )
    console.log('Successfully generated markdown schema')
  } catch (error) {
    console.error('Error in schema generation and conversion:', error)
  }
}

const collections = [
  'brands',
  'categories',
  'employees',
  'pages',
  'policies',
  'posts',
  'products',
  'reviews',
  'services',
]

collections.forEach((collection) => generateSchemas(collection))