diff --git a/.changeset/chatty-kangaroos-rule.md b/.changeset/chatty-kangaroos-rule.md index 962f4a1eb2..342ac6efb5 100644 --- a/.changeset/chatty-kangaroos-rule.md +++ b/.changeset/chatty-kangaroos-rule.md @@ -2,7 +2,6 @@ "@studiocms/dashboard": patch "@studiocms/auth": patch "@studiocms/core": patch -"@studiocms/ui": patch "studiocms": patch --- @@ -112,8 +111,4 @@ Auth system overhaul: ## **`@studiocms/dashboard`** -- Refactor to utilize new `@studiocms/auth` lib for user verification - -## **`@studiocms/ui`** - -- Update `` component's available types \ No newline at end of file +- Refactor to utilize new `@studiocms/auth` lib for user verification \ No newline at end of file diff --git a/.changeset/config.json b/.changeset/config.json index 8fa03f0db9..0dc7452bdb 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -14,13 +14,12 @@ "@studiocms/frontend", "@studiocms/imagehandler", "@studiocms/renderers", - "@studiocms/robotstxt", - "@studiocms/ui" + "@studiocms/robotstxt" ] ], "linked": [], "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", - "ignore": ["docs", "node-playground", "ui-playground"] + "ignore": ["docs", "node-playground", "build-scripts"] } diff --git a/.changeset/curvy-mirrors-play.md b/.changeset/curvy-mirrors-play.md deleted file mode 100644 index 988a91c588..0000000000 --- a/.changeset/curvy-mirrors-play.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@studiocms/ui": patch ---- - -Applied various changes and fixes to different parts of the UI libary. - -- Fixed a CSS leak caused by importing css files instead of having scoped styles -- Adjusted dropdown helper API for better DX -- Adjusted modal helper API for better DX -- Various CSS fixes for different components diff --git a/.changeset/dry-mangos-reply.md b/.changeset/dry-mangos-reply.md new file mode 100644 index 0000000000..d90a6a889c --- /dev/null +++ b/.changeset/dry-mangos-reply.md @@ -0,0 +1,6 @@ +--- +"@studiocms/dashboard": patch +"@studiocms/auth": patch +--- + +Implement new Dashboard design and update API endpoints diff --git a/.changeset/great-knives-tease.md b/.changeset/great-knives-tease.md new file mode 100644 index 0000000000..6aa256de1d --- /dev/null +++ b/.changeset/great-knives-tease.md @@ -0,0 +1,12 @@ +--- +"@studiocms/imagehandler": patch +"@studiocms/dashboard": patch +"@studiocms/renderers": patch +"@studiocms/robotstxt": patch +"@studiocms/frontend": patch +"@studiocms/auth": patch +"@studiocms/core": patch +"studiocms": patch +--- + +Implement Build step with esbuild and Update for Astro v5 diff --git a/.changeset/mean-apples-joke.md b/.changeset/mean-apples-joke.md deleted file mode 100644 index fae72edb81..0000000000 --- a/.changeset/mean-apples-joke.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@studiocms/ui": patch ---- - -Added a new searchable select component and improved accessibility for normal selects diff --git a/.changeset/nervous-fans-scream.md b/.changeset/nervous-fans-scream.md new file mode 100644 index 0000000000..ce30de21bc --- /dev/null +++ b/.changeset/nervous-fans-scream.md @@ -0,0 +1,5 @@ +--- +"@studiocms/renderers": patch +--- + +New Renderer component diff --git a/.changeset/pre.json b/.changeset/pre.json index 83181a942b..cdb8db2156 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -13,9 +13,7 @@ "@studiocms/renderers": "0.1.0-beta.4", "@studiocms/robotstxt": "0.1.0-beta.4", "@studiocms/blog": "0.1.0-beta.1", - "@studiocms/ui": "0.1.0-beta.7", "node-playground": "0.0.1", - "ui-playground": "0.0.1", "docs": "0.0.1" }, "changesets": [ diff --git a/.changeset/rotten-dancers-trade.md b/.changeset/rotten-dancers-trade.md deleted file mode 100644 index 960cc7f00a..0000000000 --- a/.changeset/rotten-dancers-trade.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@studiocms/ui": patch ---- - -Added a new UI library for the dashboard rework diff --git a/.changeset/selfish-chefs-look.md b/.changeset/selfish-chefs-look.md new file mode 100644 index 0000000000..a1a342ca38 --- /dev/null +++ b/.changeset/selfish-chefs-look.md @@ -0,0 +1,8 @@ +--- +"@studiocms/dashboard": patch +"@studiocms/frontend": patch +"@studiocms/auth": patch +"@studiocms/core": patch +--- + +Implement new StudioCMS SDK in @studiocms/core and integrate it into the new dashboard and frontend package diff --git a/.changeset/sharp-zoos-tickle.md b/.changeset/sharp-zoos-tickle.md deleted file mode 100644 index 7f18113741..0000000000 --- a/.changeset/sharp-zoos-tickle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@studiocms/ui": patch ---- - -Added a theme helper and theme toggle component diff --git a/.changeset/smooth-frogs-deny.md b/.changeset/smooth-frogs-deny.md new file mode 100644 index 0000000000..de1c7115a7 --- /dev/null +++ b/.changeset/smooth-frogs-deny.md @@ -0,0 +1,5 @@ +--- +"@studiocms/auth": patch +--- + +Implement new StudioCMS Auth lib diff --git a/.changeset/spotty-beds-kiss.md b/.changeset/spotty-beds-kiss.md index e2c4cbafd9..3701720c45 100644 --- a/.changeset/spotty-beds-kiss.md +++ b/.changeset/spotty-beds-kiss.md @@ -10,7 +10,6 @@ "@studiocms/auth": patch "@studiocms/blog": patch "@studiocms/core": patch -"@studiocms/ui": patch "studiocms": patch --- diff --git a/.changeset/tasty-dancers-crash.md b/.changeset/tasty-dancers-crash.md new file mode 100644 index 0000000000..a96b868c23 --- /dev/null +++ b/.changeset/tasty-dancers-crash.md @@ -0,0 +1,5 @@ +--- +"studiocms": patch +--- + +New CLI, Updated integration logic and updated config processing. diff --git a/.changeset/thick-dryers-train.md b/.changeset/thick-dryers-train.md deleted file mode 100644 index 2020539c6e..0000000000 --- a/.changeset/thick-dryers-train.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@studiocms/ui": patch ---- - -Adjusted persistent toasts to include an outline for better visibility diff --git a/.config/tsconfig.json b/.config/tsconfig.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/.config/tsconfig.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/.github/actions/install/action.yml b/.github/actions/install/action.yml deleted file mode 100644 index 943a1e426d..0000000000 --- a/.github/actions/install/action.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Install Tools & Dependencies -description: Installs pnpm, Node.js & package dependencies - -runs: - using: composite - steps: - - name: Setup pnpm (corepack enabled) - uses: pnpm/action-setup@v3 - - - name: Setup Node.js 20.x - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4 - with: - node-version-file: '.node-version' - cache: 'pnpm' - - - name: Install Dependencies - run: pnpm ci:install - shell: bash \ No newline at end of file diff --git a/.github/workflows/ci-pr-i18n-changeset.yml b/.github/workflows/ci-pr-i18n-changeset.yml index 2ae9e91d6d..1bdfd15a6f 100644 --- a/.github/workflows/ci-pr-i18n-changeset.yml +++ b/.github/workflows/ci-pr-i18n-changeset.yml @@ -19,10 +19,10 @@ jobs: token: ${{ secrets.STUDIOCMS_SERVICE_TOKEN }} - name: Install Tools & Dependencies - uses: ./.github/actions/install + uses: withstudiocms/automations/.github/actions/install@main - name: Create Translation Changesets - run: pnpm translations:changeset + run: pnpm ci:translations:changeset env: CI_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/ci-pr-lunaria-overview.yml b/.github/workflows/ci-pr-lunaria-overview.yml index dfbb09f53b..77fa90b2bc 100644 --- a/.github/workflows/ci-pr-lunaria-overview.yml +++ b/.github/workflows/ci-pr-lunaria-overview.yml @@ -21,7 +21,7 @@ jobs: token: ${{ secrets.STUDIOCMS_SERVICE_TOKEN }} - name: Install Tools & Dependencies - uses: ./.github/actions/install + uses: withstudiocms/automations/.github/actions/install@main - name: Generate Lunaria Overview uses: lunariajs/action@astro-docs diff --git a/.github/workflows/ci-pr-snapshots.yml b/.github/workflows/ci-pr-snapshots.yml index bd19cf47ba..96739c434e 100644 --- a/.github/workflows/ci-pr-snapshots.yml +++ b/.github/workflows/ci-pr-snapshots.yml @@ -14,7 +14,10 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install Tools & Dependencies - uses: ./.github/actions/install + uses: withstudiocms/automations/.github/actions/install@main + + - name: Run Prepublish + run: pnpm ci:prepublish - name: Publish packages run: pnpm ci:snapshot diff --git a/.github/workflows/ci-push-main.yml b/.github/workflows/ci-push-main.yml index 1b8539a151..90e14fbd7d 100644 --- a/.github/workflows/ci-push-main.yml +++ b/.github/workflows/ci-push-main.yml @@ -36,7 +36,7 @@ jobs: token: ${{ secrets.STUDIOCMS_SERVICE_TOKEN }} - name: Install Tools & Dependencies - uses: ./.github/actions/install + uses: withstudiocms/automations/.github/actions/install@main - name: Format code run: pnpm run lint:fix @@ -81,7 +81,10 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install Tools & Dependencies - uses: ./.github/actions/install + uses: withstudiocms/automations/.github/actions/install@main + + - name: Run Prepublish + run: pnpm ci:prepublish - name: Create Release Pull Request or Publish to npm id: changesets @@ -126,15 +129,15 @@ jobs: # token: ${{ secrets.STUDIOCMS_SERVICE_TOKEN }} # - name: Install Tools & Dependencies -# uses: ./.github/actions/install +# uses: withstudiocms/automations/.github/actions/install@main # - name: Build Lunaria Overview -# run: pnpm lunaria:build +# run: pnpm ci:lunaria:build # - name: Upload Overview # uses: actions/upload-pages-artifact@v3 # with: -# path: "www/docs/dist/lunaria/" +# path: "docs/dist/lunaria/" # lunaria-deploy: # name: Deploy Lunaria Overview diff --git a/.github/workflows/ci-report-lunaria.yml b/.github/workflows/ci-report-lunaria.yml index fc52710b41..78c9a0a20a 100644 --- a/.github/workflows/ci-report-lunaria.yml +++ b/.github/workflows/ci-report-lunaria.yml @@ -17,11 +17,11 @@ jobs: fetch-depth: 0 - name: Install Tools & Dependencies - uses: ./.github/actions/install + uses: withstudiocms/automations/.github/actions/install@main - id: message name: Format Discord message - run: pnpm tsm --require=./scripts/filter-warnings.cjs ./www/docs/scripts/lunaria-report-bot.ts + run: pnpm ci:lunaria:report discord_message: name: Send Discord Message diff --git a/.github/workflows/lunaria-build.yml b/.github/workflows/lunaria-build.yml index 65de2bbc5a..66af5c5872 100644 --- a/.github/workflows/lunaria-build.yml +++ b/.github/workflows/lunaria-build.yml @@ -19,15 +19,15 @@ jobs: token: ${{ secrets.STUDIOCMS_SERVICE_TOKEN }} - name: Install Tools & Dependencies - uses: ./.github/actions/install + uses: withstudiocms/automations/.github/actions/install@main - name: Build Lunaria Overview - run: pnpm lunaria:build + run: pnpm ci:lunaria:build - name: Upload Overview uses: actions/upload-pages-artifact@v3 with: - path: "www/docs/dist/lunaria/" + path: "docs/dist/lunaria/" deploy-overview: runs-on: ubuntu-latest diff --git a/.moon/tasks.yml b/.moon/tasks.yml deleted file mode 100644 index ec353bf8d6..0000000000 --- a/.moon/tasks.yml +++ /dev/null @@ -1,25 +0,0 @@ -tasks: - changeset: - command: changeset - platform: node - ci-publish: - command: 'pnpm changeset publish' - platform: node - ci-version: - command: 'pnpm changeset version' - platform: node - lint: - command: 'biome check .' - platform: node - lint-fix: - command: 'biome check --apply .' - platform: node - moon: - command: moon - platform: node - dev: - command: 'pnpm --filter node-playground dev' - platform: node - update: - command: 'pnpm install' - platform: node \ No newline at end of file diff --git a/.moon/toolchain.yml b/.moon/toolchain.yml deleted file mode 100644 index 35a77ab1de..0000000000 --- a/.moon/toolchain.yml +++ /dev/null @@ -1,103 +0,0 @@ -# Enables and configures Node.js. -node: - - # Defines the explicit Node.js version specification to use. If this field is not defined, the global node binary will be used. - # Version can also be defined with .prototools or with the MOON_NODE_VERSION environment variable. - version: '20.14.0' - - # Defines which package manager to utilize. Supports npm (default), pnpm, yarn, or bun. - packageManager: 'pnpm' - - # Optional fields for defining package manager specific configuration. The chosen setting is dependent on the value of node.packageManager. If these settings are not defined, the latest version of the active package manager will be used (when applicable). - # pnpm: - - # The version setting defines the explicit package manager version specification to use. If this field is not defined, the global npm, pnpm, yarn, and bun binaries will be used. - # Version can also be defined with .prototools or with the MOON_NPM_VERSION, MOON_PNPM_VERSION, MOON_YARN_VERSION, or MOON_BUN_VERSION environment variables. - # version: '' - - # Customize the arguments that will be passed to the package manager's install command, when the InstallDeps action is triggered in the pipeline. These arguments are used both locally and in CI. - # installArgs: ['--immutable'] - - # Injects the currently configured Node.js version as an engines constraint to the root package.json field. - addEnginesConstraint: true - - # Additional command line arguments to pass to the node binary when it's being executed by running a target. This will apply arguments to all Node.js based targets, and cannot be changed on a per target basis. - # binExecArgs: - - # Will dedupe dependencies after they have been installed, added, removing, or changed in any way, in an effort to keep the workspace tree as clean and lean as possible. - dedupeOnLockfileChange: true - - - # When syncing project dependencies, customize the format that will be used for the dependency version range. The following formats are supported (but use the one most applicable to your chosen package manager): - - # - file (npm default) - Uses file:../relative/path and copies package contents. - # - link - Uses link:../relative/path and symlinks package contents. - # - star - Uses an explicit *. - # - version - Uses the explicit version from the dependent project's package.json, e.g., "1.2.3". - # - version-caret - Uses the version from the dependent project's package.json as a caret range, e.g., "^1.2.3". - # - version-tilde - Uses the version from the dependent project's package.json as a tilde range, e.g., "~1.2.3". - # - workspace (bun/pnpm/yarn default) - Uses workspace:*, which resolves to "1.2.3". Requires package workspaces. - # - workspace-caret - Uses workspace:^, which resolves to "^1.2.3". Requires package workspaces. - # - workspace-tilde - Uses workspace:~, which resolves to "~1.2.3". Requires package workspaces. - # - # This setting does not apply to peer dependencies, as they will always use a format of ^.0.0. Furthermore, if a package manager does not support a chosen format, it will fallback to another format! - dependencyVersionFormat: workspace - - - # Will infer and automatically create tasks from package.json scripts. - # - # This requires the project's language to be "javascript" or "typescript", a package.json to exist in the project, and will take the following into account: - # - # Script names will be converted to kebab-case, and will become the task ID. - # Pre, post, and life cycle hooks are ignored. - # Tasks defined in .moon/tasks.yml or moon.yml take precedence over scripts of the same name. - # To verify inferred tasks, run moon project (pass --json to view raw config and options). Tasks that are inferred will run through the configured package manager. - inferTasksFromScripts: true - - # Supports the "single version policy" or "one version rule" patterns by only allowing dependencies in the root package.json, and only installing dependencies in the workspace root, and not within individual projects. It also bypasses all workspaces checks to determine package locations. - # - # This setting does not verify that other package.jsons do not have dependencies, it merely runs "install dependency" commands in the root. It's up to you to ensure that other package.jsons do not have dependencies. - rootPackageOnly: false - - # Will sync a project's dependencies as normal dependencies within the project's package.json. If a dependent project does not have a package.json, or if a dependency of the same name has an explicit version already defined, the sync will be skipped. - syncProjectWorkspaceDependencies: true - - # Will sync the currently configured Node.js version to a 3rd-party version manager's config/rc file. Supports "nodenv" (syncs to .node-version), "nvm" (syncs to .nvmrc), or none (default). - # - # This is a special setting that ensure other Node.js processes outside of our toolchain are utilizing the same version, which is a very common practice when managing dependencies. - syncVersionManagerConfig: nodenv - -# Dictates how moon interacts with and utilizes TypeScript within the workspace. -typescript: - - # When syncing project references and a depended on project does not have a tsconfig.json, automatically create one. - createMissingConfig: true - - # When enabled and syncing project references, will inject each project reference as an entry in the include field of the respective project's tsconfig.json. These includes are sometimes required by editors for auto-completion, intellisense, and automatic imports. - includeProjectReferenceSources: true - - # When enabled, will automatically inject shared types (types/**/*) into the include field of each project's tsconfig.json. The shared types folder must be named types and must exist relative to the root setting. - includeSharedTypes: true - - # Defines the file name of the tsconfig.json found in the project root. We utilize this setting when syncing project references between projects. Defaults to tsconfig.json. - projectConfigFileName: 'tsconfig.json' - - # Defines the TypeScript root (relative from moon's workspace root), where project reference composition, common compiler options, and shared types will be located. - root: '.' - - # Defines the file name of the tsconfig.json found in the root of all projects. We utilize this setting when syncing projects as references. - rootConfigFileName: 'tsconfig.json' - - # Defines the file name of the config file found in the root that houses shared compiler options. - rootOptionsConfigFileName: '.config/tsconfig.json' - - # Updates the outDir compiler option in each project's tsconfig.json to route to moon's cache folder. This is useful when using project references and wanting to keep all the compiled .d.ts files out of the project folder. - routeOutDirToCache: true - - # Will sync a project's dependencies (when applicable) as project references within that project's tsconfig.json, and the root tsconfig.json. - # - # This setting assumes you're using the file organization as defined in our official TypeScript project references in-depth guide. - syncProjectReferences: true - - # Will sync a project's tsconfig.json project references to the paths compiler option, using the referenced project's package.json name. - syncProjectReferencesToPaths: true \ No newline at end of file diff --git a/.moon/workspace.yml b/.moon/workspace.yml deleted file mode 100644 index 3c3d1cadd5..0000000000 --- a/.moon/workspace.yml +++ /dev/null @@ -1,69 +0,0 @@ -# https://moonrepo.dev/docs/config/workspace -$schema: 'https://moonrepo.dev/schemas/workspace.json' - -projects: - root: '.' - # Packages - studiocms: 'packages/studiocms' - studiocms_assets: 'packages/studiocms_assets' - studiocms_auth: 'packages/studiocms_auth' - studiocms_betaresources: 'packages/studiocms_betaresources' - studiocms_blog: 'packages/studiocms_blog' - studiocms_core: 'packages/studiocms_core' - studiocms_dashboard: 'packages/studiocms_dashboard' - studiocms_frontend: 'packages/studiocms_frontend' - studiocms_imagehandler: 'packages/studiocms_imagehandler' - studiocms_renderers: 'packages/studiocms_renderers' - studiocms_robotstxt: 'packages/studiocms_robotstxt' - studiocms_ui: 'packages/studiocms_ui' - - # Playgrounds - playground: 'playgrounds/node' - ui-playground: 'playgrounds/ui' - # cloudflare-playground: 'playgrounds/cloudflare' - Removed for now till we start experimenting with Cloudflare again. - - # Web Sites & Docs - web: 'www/web' - docs: 'www/docs' - -# https://moonrepo.dev/docs/config/workspace#constraints -constraints: - # https://moonrepo.dev/docs/config/workspace#enforceprojecttyperelationships - enforceProjectTypeRelationships: true - -# https://moonrepo.dev/docs/config/workspace#experiments -experiments: - strictProjectAliases: true - -# https://moonrepo.dev/docs/config/workspace#runner -runner: - cacheLifetime: "7 days" - inheritColorsForPipedTasks: true - logRunningCommand: true - -# https://moonrepo.dev/docs/config/workspace#telemetry -telemetry: true - -# Configures the version control system to utilize within the workspace. A VCS -# is required for determining touched (added, modified, etc) files, calculating file hashes, -# computing affected files, and much more. -vcs: - # https://moonrepo.dev/docs/config/workspace#defaultbranch - defaultBranch: 'main' - - # https://moonrepo.dev/docs/config/workspace#hooks - # hooks: - - # https://moonrepo.dev/docs/config/workspace#synchooks - # syncHooks: - - # https://moonrepo.dev/docs/config/workspace#manager - manager: 'git' - - # https://moonrepo.dev/docs/config/workspace#provider - provider: 'github' - - # https://moonrepo.dev/docs/config/workspace#remotecandidates - remoteCandidates: - - 'origin' - - 'upstream' \ No newline at end of file diff --git a/.prototools b/.prototools index 5b0a0f2624..ff20835976 100644 --- a/.prototools +++ b/.prototools @@ -1,8 +1,6 @@ -biome = "1.9.2" -moon = "1.28.3" +biome = "1.9.4" node = "20.14.0" pnpm = "9.5.0" [plugins] biome = "source:https://raw.githubusercontent.com/Phault/proto-toml-plugins/main/biome/plugin.toml" -moon = "source:https://raw.githubusercontent.com/moonrepo/moon/master/proto-plugin.toml" diff --git a/.vscode/settings.json b/.vscode/settings.json index 44fcf1104c..508eb50ac0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,5 +8,42 @@ "[mdx]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, - "editor.gotoLocation.multipleDefinitions": "goto" + "editor.gotoLocation.multipleDefinitions": "goto", + "cSpell.words": [ + "Adammatthiesen", + "astrodtsbuilder", + "astrojs", + "CMSO", + "CMSSDK", + "Coolify", + "createpage", + "dompurify", + "editfolder", + "editpage", + "heroicons", + "inox", + "jdtjenkins", + "johndoe", + "Libravatar", + "liverender", + "Markdoc", + "Matthiesen", + "mdast", + "mgmt", + "mrmime", + "onest", + "oslojs", + "prototools", + "robotstxt", + "searchlist", + "strikethrough", + "studiocms", + "studiocmsenv", + "turso", + "twoslash", + "userpartial", + "Valladares", + "vite", + "withstudiocms" + ] } diff --git a/README.md b/README.md index a2362fb400..662d0314ed 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ To see how to get started, check out the [StudioCMS README](./packages/studiocms ## Sponsor - + ## StudioCMS Dashboard i18n Status @@ -45,7 +45,7 @@ For an up-to-date list of our main tools check out our [`.prototools`](.prototoo For more information about Proto checkout [Proto's Website](https://moonrepo.dev/proto) -## This is a [`Moonrepo`](https://moonrepo.dev) +## This is a [`Moon repository`](https://moonrepo.dev) Follow install instructions listed on Moonrepo's docs, and you'll be all set to go! Its even a super easy single line command you put in your terminal! @@ -65,16 +65,15 @@ Steps to get a running playground should be the following: - clone repo - run `pnpm i --frozen-lockfile` -- change `dbStartPage` in the [node playground's](./playgrounds/node/studiocms.config.mjs) config to `true` +- change `dbStartPage` in the [node playground's](./playground/studiocms.config.mts) config to `true` - read the first time setup instructions listed in the [main package readme](./packages/studiocms/README.md#first-start-and-setup) then replace the astro db commands with the following: Commands to run: - - `pnpm playground:login` - Login your CLI to Astro Studio - - `pnpm playground:push` - Creates the base tables on the remote database. + - `pnpm playground:push` - Push to your libSQL database assigned via environment variables - `pnpm playground:dev` - Starts the Dev server connected to the linked database -Once that process completes successfuly you are ready to navigate to http://localhost:4321/start and follow the instructions to get started. +Once that process completes successfully you are ready to navigate to http://localhost:4321/start and follow the instructions to get started. It will redirect and ask you to shutdown and change the above mentioned config option `dbStartPage` to `false` at which point that will enable full functionality of the CMS. you can now restart the dev server with `astro dev --remote` to continue viewing your new site! diff --git a/www/assets/banner-readme.png b/assets/banner-readme.png similarity index 100% rename from www/assets/banner-readme.png rename to assets/banner-readme.png diff --git a/www/assets/discord-emoji-dark.png b/assets/discord-emoji-dark.png similarity index 100% rename from www/assets/discord-emoji-dark.png rename to assets/discord-emoji-dark.png diff --git a/www/assets/discord-emoji-light.png b/assets/discord-emoji-light.png similarity index 100% rename from www/assets/discord-emoji-light.png rename to assets/discord-emoji-light.png diff --git a/playgrounds/node/public/studiocms-auth/logo-adaptive.svg b/assets/logo-adaptive.svg similarity index 100% rename from playgrounds/node/public/studiocms-auth/logo-adaptive.svg rename to assets/logo-adaptive.svg diff --git a/www/assets/logo-dark.svg b/assets/logo-dark.svg similarity index 100% rename from www/assets/logo-dark.svg rename to assets/logo-dark.svg diff --git a/www/assets/logo-discord.png b/assets/logo-discord.png similarity index 100% rename from www/assets/logo-discord.png rename to assets/logo-discord.png diff --git a/www/assets/logo-discord.svg b/assets/logo-discord.svg similarity index 100% rename from www/assets/logo-discord.svg rename to assets/logo-discord.svg diff --git a/www/assets/logo-light.svg b/assets/logo-light.svg similarity index 100% rename from www/assets/logo-light.svg rename to assets/logo-light.svg diff --git a/www/assets/logo-outlined-adaptive.svg b/assets/logo-outlined-adaptive.svg similarity index 100% rename from www/assets/logo-outlined-adaptive.svg rename to assets/logo-outlined-adaptive.svg diff --git a/www/assets/logo-outlined-dark.svg b/assets/logo-outlined-dark.svg similarity index 100% rename from www/assets/logo-outlined-dark.svg rename to assets/logo-outlined-dark.svg diff --git a/www/assets/logo-outlined-light.svg b/assets/logo-outlined-light.svg similarity index 100% rename from www/assets/logo-outlined-light.svg rename to assets/logo-outlined-light.svg diff --git a/www/assets/old/banner-readme.png b/assets/old/banner-readme.png similarity index 100% rename from www/assets/old/banner-readme.png rename to assets/old/banner-readme.png diff --git a/www/assets/old/discord-emoji-dark.png b/assets/old/discord-emoji-dark.png similarity index 100% rename from www/assets/old/discord-emoji-dark.png rename to assets/old/discord-emoji-dark.png diff --git a/www/assets/old/discord-emoji-light.png b/assets/old/discord-emoji-light.png similarity index 100% rename from www/assets/old/discord-emoji-light.png rename to assets/old/discord-emoji-light.png diff --git a/www/assets/old/logo-dark.svg b/assets/old/logo-dark.svg similarity index 100% rename from www/assets/old/logo-dark.svg rename to assets/old/logo-dark.svg diff --git a/www/assets/old/logo-discord.png b/assets/old/logo-discord.png similarity index 100% rename from www/assets/old/logo-discord.png rename to assets/old/logo-discord.png diff --git a/www/assets/old/logo-discord.svg b/assets/old/logo-discord.svg similarity index 100% rename from www/assets/old/logo-discord.svg rename to assets/old/logo-discord.svg diff --git a/www/assets/old/logo-light.svg b/assets/old/logo-light.svg similarity index 100% rename from www/assets/old/logo-light.svg rename to assets/old/logo-light.svg diff --git a/www/assets/old/studioCMS-dark.png b/assets/old/studioCMS-dark.png similarity index 100% rename from www/assets/old/studioCMS-dark.png rename to assets/old/studioCMS-dark.png diff --git a/www/assets/old/studioCMS.png b/assets/old/studioCMS.png similarity index 100% rename from www/assets/old/studioCMS.png rename to assets/old/studioCMS.png diff --git a/www/assets/old/studiocms-new-logos.zip b/assets/old/studiocms-new-logos.zip similarity index 100% rename from www/assets/old/studiocms-new-logos.zip rename to assets/old/studiocms-new-logos.zip diff --git a/www/assets/studioCMS.png b/assets/studioCMS.png similarity index 100% rename from www/assets/studioCMS.png rename to assets/studioCMS.png diff --git a/www/assets/studiocms-dark.png b/assets/studiocms-dark.png similarity index 100% rename from www/assets/studiocms-dark.png rename to assets/studiocms-dark.png diff --git a/www/assets/studiocms-new-logos.zip b/assets/studiocms-new-logos.zip similarity index 100% rename from www/assets/studiocms-new-logos.zip rename to assets/studiocms-new-logos.zip diff --git a/biome.json b/biome.json index 46f00326e8..9b57a86f4f 100644 --- a/biome.json +++ b/biome.json @@ -8,7 +8,7 @@ }, "files": { "ignoreUnknown": true, - "ignore": ["**/.astro/**", "**/package.json", "**/dist/**"] + "ignore": ["**/.astro/**", "**/package.json", "**/dist/**", "**/min.js"] }, "formatter": { "lineWidth": 100, @@ -34,6 +34,9 @@ "recommended": true, "suspicious": { "noExplicitAny": "warn" + }, + "style": { + "noParameterAssign": "off" } } }, diff --git a/build-scripts/cmd/build.js b/build-scripts/cmd/build.js new file mode 100644 index 0000000000..5c8932215b --- /dev/null +++ b/build-scripts/cmd/build.js @@ -0,0 +1,187 @@ +import { execSync } from 'node:child_process'; +import fs from 'node:fs/promises'; +import esbuild from 'esbuild'; +import { copy } from 'esbuild-plugin-copy'; +import glob from 'fast-glob'; +import { dim, gray, green, red, yellow } from 'kleur/colors'; + +/** @type {import('esbuild').BuildOptions} */ +const defaultConfig = { + minify: false, + format: 'esm', + platform: 'node', + target: 'node18', + sourcemap: false, + sourcesContent: false, +}; + +const dt = new Intl.DateTimeFormat('en-us', { + hour: '2-digit', + minute: '2-digit', +}); + +const dtsGen = (buildTsConfig) => ({ + name: 'TypeScriptDeclarationsPlugin', + setup(build) { + build.onEnd((result) => { + if (result.errors.length > 0) return; + const date = dt.format(new Date()); + console.log(`${dim(`[${date}]`)} Generating TypeScript declarations...`); + try { + const res = execSync( + `tsc --emitDeclarationOnly ${buildTsConfig ? '-p tsconfig.build.json' : ''} --outDir ./dist` + ); + console.log(res.toString()); + console.log(dim(`[${date}] `) + green('√ Generated TypeScript declarations')); + } catch (error) { + console.error(dim(`[${date}] `) + red(`${error}\n\n${error.stdout.toString()}`)); + } + }); + }, +}); + +const CopyConfig = { + assets: [ + { + from: ['./src/**/*.!(ts|js|css)'], + to: '.', + }, + ], +}; + +export default async function build(...args) { + const config = Object.assign({}, defaultConfig); + const isDev = args.slice(-1)[0] === 'IS_DEV'; + const patterns = args + .filter((f) => !!f) // remove empty args + .map((f) => f.replace(/^'/, '').replace(/'$/, '')); // Needed for Windows: glob strings contain surrounding string chars??? remove these + const entryPoints = [].concat( + ...(await Promise.all( + patterns.map((pattern) => glob(pattern, { filesOnly: true, absolute: true })) + )) + ); + const date = dt.format(new Date()); + + const noClean = args.includes('--no-clean-dist'); + const bundle = args.includes('--bundle'); + const forceCJS = args.includes('--force-cjs'); + const buildTsConfig = args.includes('--build-tsconfig'); + + const { type = 'module', dependencies = {} } = await readPackageJSON('./package.json'); + + config.define = {}; + for (const [key, value] of await getDefinedEntries()) { + config.define[`process.env.${key}`] = JSON.stringify(value); + } + const format = type === 'module' && !forceCJS ? 'esm' : 'cjs'; + + const outdir = 'dist'; + + if (!noClean) { + console.log( + `${dim(`[${date}]`)} Cleaning dist directory... ${dim(`(${entryPoints.length} files found)`)}` + ); + await clean(outdir, [`!${outdir}/**/*.d.ts`]); + } + + if (!isDev) { + console.log( + `${dim(`[${date}]`)} Building...${bundle ? '(Bundling)' : ''} ${dim(`(${entryPoints.length} files found)`)}` + ); + await esbuild.build({ + ...config, + bundle, + external: bundle ? Object.keys(dependencies) : undefined, + entryPoints, + outdir, + outExtension: forceCJS ? { '.js': '.cjs' } : {}, + format, + plugins: [copy(CopyConfig), dtsGen(buildTsConfig)], + }); + console.log(dim(`[${date}] `) + green('√ Build Complete')); + return; + } + + const rebuildPlugin = { + name: 'dev:rebuild', + setup(build) { + build.onEnd(async (result) => { + const date = dt.format(new Date()); + if (result?.errors.length) { + console.error(dim(`[${date}] `) + red(error || result.errors.join('\n'))); + } else { + if (result.warnings.length) { + console.info( + dim(`[${date}] `) + yellow(`! updated with warnings:\n${result.warnings.join('\n')}`) + ); + } + console.info(dim(`[${date}] `) + green('√ updated')); + } + }); + }, + }; + + const builder = await esbuild.context({ + ...config, + entryPoints, + outdir, + format, + sourcemap: 'linked', + plugins: [copy(CopyConfig), rebuildPlugin], + }); + + console.log( + `${dim(`[${date}] `) + gray('Watching for changes...')} ${dim(`(${entryPoints.length} files found)`)}` + ); + await builder.watch(); + + process.on('beforeExit', () => { + builder.stop?.(); + }); +} + +async function clean(outdir, skip = []) { + const files = await glob([`${outdir}/**`, ...skip], { filesOnly: true }); + await Promise.all(files.map((file) => fs.rm(file, { force: true }))); +} + +/** + * Contextual `define` values to statically replace in the built JS output. + * Available to all packages, but mostly useful for CLIs like `create-astro`. + */ +async function getDefinedEntries() { + const define = { + /** The current version (at the time of building) for the current package, such as `astro` or `@astrojs/sitemap` */ + PACKAGE_VERSION: await getInternalPackageVersion('./package.json'), + /** The current version (at the time of building) for `typescript` */ + TYPESCRIPT_VERSION: await getWorkspacePackageVersion('typescript'), + }; + for (const [key, value] of Object.entries(define)) { + if (value === undefined) { + delete define[key]; + } + } + return Object.entries(define); +} + +async function readPackageJSON(path) { + return await fs.readFile(path, { encoding: 'utf8' }).then((res) => JSON.parse(res)); +} + +async function getInternalPackageVersion(path) { + return readPackageJSON(path).then((res) => res.version); +} + +async function getWorkspacePackageVersion(packageName) { + const { dependencies, devDependencies } = await readPackageJSON( + new URL('../../package.json', import.meta.url) + ); + const deps = { ...dependencies, ...devDependencies }; + const version = deps[packageName]; + if (!version) { + throw new Error( + `Unable to resolve "${packageName}". Is it a dependency of the workspace root?` + ); + } + return version.replace(/^\D+/, ''); +} diff --git a/build-scripts/index.js b/build-scripts/index.js new file mode 100755 index 0000000000..17a4d6e676 --- /dev/null +++ b/build-scripts/index.js @@ -0,0 +1,14 @@ +#!/usr/bin/env node +export default async function run() { + const [cmd, ...args] = process.argv.slice(2); + switch (cmd) { + case 'dev': + case 'build': { + const { default: build } = await import('./cmd/build.js'); + await build(...args, cmd === 'dev' ? 'IS_DEV' : undefined); + break; + } + } +} + +run(); diff --git a/build-scripts/jsconfig.json b/build-scripts/jsconfig.json new file mode 100644 index 0000000000..5cf3835e81 --- /dev/null +++ b/build-scripts/jsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "declaration": true, + "strict": true, + "module": "esnext", + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "target": "esnext" + } +} diff --git a/build-scripts/package.json b/build-scripts/package.json new file mode 100644 index 0000000000..862fda8573 --- /dev/null +++ b/build-scripts/package.json @@ -0,0 +1,19 @@ +{ + "name": "build-scripts", + "version": "0.0.14", + "private": true, + "type": "module", + "main": "./index.js", + "bin": { + "build-scripts": "./index.js" + }, + "dependencies": { + "esbuild": "^0.24.2", + "esbuild-plugin-copy": "^2.1.1", + "fast-glob": "^3.3.2", + "kleur": "^4.1.5", + "p-limit": "^6.1.0", + "tinyexec": "^0.3.1", + "tsconfck": "^3.1.4" + } +} \ No newline at end of file diff --git a/www/docs/.gitignore b/docs/.gitignore similarity index 100% rename from www/docs/.gitignore rename to docs/.gitignore diff --git a/playgrounds/node/.vscode/extensions.json b/docs/.vscode/extensions.json similarity index 100% rename from playgrounds/node/.vscode/extensions.json rename to docs/.vscode/extensions.json diff --git a/playgrounds/node/.vscode/launch.json b/docs/.vscode/launch.json similarity index 100% rename from playgrounds/node/.vscode/launch.json rename to docs/.vscode/launch.json diff --git a/www/docs/README.md b/docs/README.md similarity index 100% rename from www/docs/README.md rename to docs/README.md diff --git a/www/docs/astro.config.mts b/docs/astro.config.mts similarity index 88% rename from www/docs/astro.config.mts rename to docs/astro.config.mts index 789aefe843..76b0f26726 100644 --- a/www/docs/astro.config.mts +++ b/docs/astro.config.mts @@ -2,9 +2,9 @@ import starlight from '@astrojs/starlight'; import starlightUtils from '@lorenzo_lewis/starlight-utils'; import { defineConfig } from 'astro/config'; import starlightImageZoom from 'starlight-image-zoom'; -import getCoolifyURL from './hostUtils'; -import rehypePluginKit from './src/plugins/rehypePluginKit'; -import { typeDocPlugins, typeDocSideBarEntry } from './typedoc.config'; +import getCoolifyURL from './hostUtils.ts'; +import rehypePluginKit from './src/plugins/rehypePluginKit.ts'; +import { typeDocPlugins, typeDocSideBarEntry } from './typedoc.config.ts'; // Define the Site URL const site = getCoolifyURL(true) || 'https://docs.studiocms.dev/'; @@ -30,9 +30,6 @@ export const locales = { export default defineConfig({ site, - experimental: { - directRenderScript: true, - }, image: { remotePatterns: [{ protocol: 'https' }], }, @@ -73,7 +70,7 @@ export default defineConfig({ './src/styles/starlight.css', ], editLink: { - baseUrl: 'https://github.com/withstudiocms/studiocms/tree/main/www/docs', + baseUrl: 'https://github.com/withstudiocms/studiocms/tree/main/docs', }, head: [ // { @@ -143,21 +140,6 @@ export default defineConfig({ autogenerate: { directory: 'customizing/studiocms-renderers' }, collapsed: true, }, - { - label: '@studiocms/ui', - badge: { text: 'New', variant: 'success' }, - items: [ - { label: 'Getting Started', link: 'customizing/studiocms-ui/' }, - { - label: 'Components', - autogenerate: { - directory: 'customizing/studiocms-ui/components', - collapsed: true, - }, - }, - ], - collapsed: true, - }, ], }, ], diff --git a/www/docs/ec.config.mjs b/docs/ec.config.mjs similarity index 87% rename from www/docs/ec.config.mjs rename to docs/ec.config.mjs index 74fc964ab4..bb02c7ba39 100644 --- a/www/docs/ec.config.mjs +++ b/docs/ec.config.mjs @@ -24,7 +24,9 @@ export default defineEcConfig({ }), ], styleOverrides: { - // @ts-expect-error - This is not a Standard EC config option, but it's a valid one from a plugin + frames: { + editorActiveTabIndicatorBottomColor: 'var(--sl-color-accent)', + }, twoSlash: { cursorColor: '#f8f8f2', }, diff --git a/www/docs/hostUtils.ts b/docs/hostUtils.ts similarity index 100% rename from www/docs/hostUtils.ts rename to docs/hostUtils.ts diff --git a/www/docs/lunaria.config.ts b/docs/lunaria.config.ts similarity index 100% rename from www/docs/lunaria.config.ts rename to docs/lunaria.config.ts diff --git a/www/docs/lunaria/components.ts b/docs/lunaria/components.ts similarity index 100% rename from www/docs/lunaria/components.ts rename to docs/lunaria/components.ts diff --git a/www/docs/lunaria/styles.ts b/docs/lunaria/styles.ts similarity index 100% rename from www/docs/lunaria/styles.ts rename to docs/lunaria/styles.ts diff --git a/www/docs/package.json b/docs/package.json similarity index 98% rename from www/docs/package.json rename to docs/package.json index 8d46c63a38..b841285648 100644 --- a/www/docs/package.json +++ b/docs/package.json @@ -16,7 +16,7 @@ "@studiocms/renderers": "workspace:*", "@studiocms/blog": "workspace:*", "@studiocms/devapps": "workspace:*", - "@studiocms/ui": "workspace:*", + "@studiocms/ui": "catalog:", "astro": "catalog:", "@astrojs/check": "catalog:", diff --git a/www/docs/public/logo-light.svg b/docs/public/logo-light.svg similarity index 100% rename from www/docs/public/logo-light.svg rename to docs/public/logo-light.svg diff --git a/www/docs/public/og.jpg b/docs/public/og.jpg similarity index 100% rename from www/docs/public/og.jpg rename to docs/public/og.jpg diff --git a/www/docs/scripts/lunaria-report-bot.ts b/docs/scripts/lunaria-report-bot.ts similarity index 100% rename from www/docs/scripts/lunaria-report-bot.ts rename to docs/scripts/lunaria-report-bot.ts diff --git a/www/docs/scripts/lunaria.mts b/docs/scripts/lunaria.mts similarity index 100% rename from www/docs/scripts/lunaria.mts rename to docs/scripts/lunaria.mts diff --git a/www/docs/src/assets/avatar.png b/docs/src/assets/avatar.png similarity index 100% rename from www/docs/src/assets/avatar.png rename to docs/src/assets/avatar.png diff --git a/www/docs/src/assets/gallery/CreateNewPageDark-1.png b/docs/src/assets/gallery/CreateNewPageDark-1.png similarity index 100% rename from www/docs/src/assets/gallery/CreateNewPageDark-1.png rename to docs/src/assets/gallery/CreateNewPageDark-1.png diff --git a/www/docs/src/assets/gallery/CreateNewPageDark.png b/docs/src/assets/gallery/CreateNewPageDark.png similarity index 100% rename from www/docs/src/assets/gallery/CreateNewPageDark.png rename to docs/src/assets/gallery/CreateNewPageDark.png diff --git a/www/docs/src/assets/gallery/CreateNewPageLight-1.png b/docs/src/assets/gallery/CreateNewPageLight-1.png similarity index 100% rename from www/docs/src/assets/gallery/CreateNewPageLight-1.png rename to docs/src/assets/gallery/CreateNewPageLight-1.png diff --git a/www/docs/src/assets/gallery/CreateNewPageLight-2.png b/docs/src/assets/gallery/CreateNewPageLight-2.png similarity index 100% rename from www/docs/src/assets/gallery/CreateNewPageLight-2.png rename to docs/src/assets/gallery/CreateNewPageLight-2.png diff --git a/www/docs/src/assets/gallery/DashboardDark.png b/docs/src/assets/gallery/DashboardDark.png similarity index 100% rename from www/docs/src/assets/gallery/DashboardDark.png rename to docs/src/assets/gallery/DashboardDark.png diff --git a/www/docs/src/assets/gallery/DashboardLight.png b/docs/src/assets/gallery/DashboardLight.png similarity index 100% rename from www/docs/src/assets/gallery/DashboardLight.png rename to docs/src/assets/gallery/DashboardLight.png diff --git a/www/docs/src/assets/gallery/EditPageDark-1.png b/docs/src/assets/gallery/EditPageDark-1.png similarity index 100% rename from www/docs/src/assets/gallery/EditPageDark-1.png rename to docs/src/assets/gallery/EditPageDark-1.png diff --git a/www/docs/src/assets/gallery/EditPageDark-2.png b/docs/src/assets/gallery/EditPageDark-2.png similarity index 100% rename from www/docs/src/assets/gallery/EditPageDark-2.png rename to docs/src/assets/gallery/EditPageDark-2.png diff --git a/www/docs/src/assets/gallery/EditPageLight-1.png b/docs/src/assets/gallery/EditPageLight-1.png similarity index 100% rename from www/docs/src/assets/gallery/EditPageLight-1.png rename to docs/src/assets/gallery/EditPageLight-1.png diff --git a/www/docs/src/assets/gallery/EditPageLight-2.png b/docs/src/assets/gallery/EditPageLight-2.png similarity index 100% rename from www/docs/src/assets/gallery/EditPageLight-2.png rename to docs/src/assets/gallery/EditPageLight-2.png diff --git a/www/docs/src/assets/gallery/ExistingPageLight.png b/docs/src/assets/gallery/ExistingPageLight.png similarity index 100% rename from www/docs/src/assets/gallery/ExistingPageLight.png rename to docs/src/assets/gallery/ExistingPageLight.png diff --git a/www/docs/src/assets/gallery/ExistingPagesDark.png b/docs/src/assets/gallery/ExistingPagesDark.png similarity index 100% rename from www/docs/src/assets/gallery/ExistingPagesDark.png rename to docs/src/assets/gallery/ExistingPagesDark.png diff --git a/www/docs/src/assets/gallery/LoginPageDark.png b/docs/src/assets/gallery/LoginPageDark.png similarity index 100% rename from www/docs/src/assets/gallery/LoginPageDark.png rename to docs/src/assets/gallery/LoginPageDark.png diff --git a/www/docs/src/assets/gallery/LoginPageLight.png b/docs/src/assets/gallery/LoginPageLight.png similarity index 100% rename from www/docs/src/assets/gallery/LoginPageLight.png rename to docs/src/assets/gallery/LoginPageLight.png diff --git a/www/docs/src/assets/gallery/SiteAdminsDark.png b/docs/src/assets/gallery/SiteAdminsDark.png similarity index 100% rename from www/docs/src/assets/gallery/SiteAdminsDark.png rename to docs/src/assets/gallery/SiteAdminsDark.png diff --git a/www/docs/src/assets/gallery/SiteConfigDark.png b/docs/src/assets/gallery/SiteConfigDark.png similarity index 100% rename from www/docs/src/assets/gallery/SiteConfigDark.png rename to docs/src/assets/gallery/SiteConfigDark.png diff --git a/www/docs/src/assets/gallery/UserProfileDark.png b/docs/src/assets/gallery/UserProfileDark.png similarity index 100% rename from www/docs/src/assets/gallery/UserProfileDark.png rename to docs/src/assets/gallery/UserProfileDark.png diff --git a/www/docs/src/assets/gallery/UserProfileLight.png b/docs/src/assets/gallery/UserProfileLight.png similarity index 100% rename from www/docs/src/assets/gallery/UserProfileLight.png rename to docs/src/assets/gallery/UserProfileLight.png diff --git a/www/docs/src/assets/index.ts b/docs/src/assets/index.ts similarity index 100% rename from www/docs/src/assets/index.ts rename to docs/src/assets/index.ts diff --git a/www/docs/src/assets/web-vitals/cv-analytics-dark.png b/docs/src/assets/web-vitals/cv-analytics-dark.png similarity index 100% rename from www/docs/src/assets/web-vitals/cv-analytics-dark.png rename to docs/src/assets/web-vitals/cv-analytics-dark.png diff --git a/www/docs/src/assets/web-vitals/cv-analytics-light.png b/docs/src/assets/web-vitals/cv-analytics-light.png similarity index 100% rename from www/docs/src/assets/web-vitals/cv-analytics-light.png rename to docs/src/assets/web-vitals/cv-analytics-light.png diff --git a/www/docs/src/assets/web-vitals/cv-byroute-dark.png b/docs/src/assets/web-vitals/cv-byroute-dark.png similarity index 100% rename from www/docs/src/assets/web-vitals/cv-byroute-dark.png rename to docs/src/assets/web-vitals/cv-byroute-dark.png diff --git a/www/docs/src/assets/web-vitals/cv-byroute-light.png b/docs/src/assets/web-vitals/cv-byroute-light.png similarity index 100% rename from www/docs/src/assets/web-vitals/cv-byroute-light.png rename to docs/src/assets/web-vitals/cv-byroute-light.png diff --git a/www/docs/src/assets/web-vitals/cv-progressbars-dark.png b/docs/src/assets/web-vitals/cv-progressbars-dark.png similarity index 100% rename from www/docs/src/assets/web-vitals/cv-progressbars-dark.png rename to docs/src/assets/web-vitals/cv-progressbars-dark.png diff --git a/www/docs/src/assets/web-vitals/cv-progressbars-light.png b/docs/src/assets/web-vitals/cv-progressbars-light.png similarity index 100% rename from www/docs/src/assets/web-vitals/cv-progressbars-light.png rename to docs/src/assets/web-vitals/cv-progressbars-light.png diff --git a/www/docs/src/assets/web-vitals/pagespeed-dark.png b/docs/src/assets/web-vitals/pagespeed-dark.png similarity index 100% rename from www/docs/src/assets/web-vitals/pagespeed-dark.png rename to docs/src/assets/web-vitals/pagespeed-dark.png diff --git a/www/docs/src/assets/web-vitals/pagespeed-light.png b/docs/src/assets/web-vitals/pagespeed-light.png similarity index 100% rename from www/docs/src/assets/web-vitals/pagespeed-light.png rename to docs/src/assets/web-vitals/pagespeed-light.png diff --git a/www/docs/src/components/ContributorList.astro b/docs/src/components/ContributorList.astro similarity index 100% rename from www/docs/src/components/ContributorList.astro rename to docs/src/components/ContributorList.astro diff --git a/www/docs/src/components/DropdownScript.astro b/docs/src/components/DropdownScript.astro similarity index 100% rename from www/docs/src/components/DropdownScript.astro rename to docs/src/components/DropdownScript.astro diff --git a/www/docs/src/components/FacePile.astro b/docs/src/components/FacePile.astro similarity index 100% rename from www/docs/src/components/FacePile.astro rename to docs/src/components/FacePile.astro diff --git a/www/docs/src/components/Gallery.astro b/docs/src/components/Gallery.astro similarity index 100% rename from www/docs/src/components/Gallery.astro rename to docs/src/components/Gallery.astro diff --git a/www/docs/src/components/Integration.astro b/docs/src/components/Integration.astro similarity index 100% rename from www/docs/src/components/Integration.astro rename to docs/src/components/Integration.astro diff --git a/www/docs/src/components/ModalScript.astro b/docs/src/components/ModalScript.astro similarity index 100% rename from www/docs/src/components/ModalScript.astro rename to docs/src/components/ModalScript.astro diff --git a/www/docs/src/components/PackageCatalog.astro b/docs/src/components/PackageCatalog.astro similarity index 93% rename from www/docs/src/components/PackageCatalog.astro rename to docs/src/components/PackageCatalog.astro index 6fa1f75430..28b325e68b 100644 --- a/www/docs/src/components/PackageCatalog.astro +++ b/docs/src/components/PackageCatalog.astro @@ -42,7 +42,7 @@ const packages = (await getCollection('package-catalog'))

{description}

- {Astro.locals.t('package-catalog.readmore.start')} {Astro.locals.t('package-catalog.readmore.end')} + {Astro.locals.t('package-catalog.readmore.start')} {Astro.locals.t('package-catalog.readmore.end')}{(href.startsWith('https://') || href.startsWith('http://')) && ' ⤴'} <>{ index < packages.length - 1 &&
} )) diff --git a/www/docs/src/components/PreviewCard.astro b/docs/src/components/PreviewCard.astro similarity index 100% rename from www/docs/src/components/PreviewCard.astro rename to docs/src/components/PreviewCard.astro diff --git a/www/docs/src/components/ReadMore.astro b/docs/src/components/ReadMore.astro similarity index 100% rename from www/docs/src/components/ReadMore.astro rename to docs/src/components/ReadMore.astro diff --git a/www/docs/src/components/Sponsors.astro b/docs/src/components/Sponsors.astro similarity index 100% rename from www/docs/src/components/Sponsors.astro rename to docs/src/components/Sponsors.astro diff --git a/www/docs/src/components/ThemeHelperScript.astro b/docs/src/components/ThemeHelperScript.astro similarity index 100% rename from www/docs/src/components/ThemeHelperScript.astro rename to docs/src/components/ThemeHelperScript.astro diff --git a/www/docs/src/components/ToasterScript.astro b/docs/src/components/ToasterScript.astro similarity index 100% rename from www/docs/src/components/ToasterScript.astro rename to docs/src/components/ToasterScript.astro diff --git a/www/docs/src/components/TursoCLI.astro b/docs/src/components/TursoCLI.astro similarity index 100% rename from www/docs/src/components/TursoCLI.astro rename to docs/src/components/TursoCLI.astro diff --git a/www/docs/src/components/Version.astro b/docs/src/components/Version.astro similarity index 100% rename from www/docs/src/components/Version.astro rename to docs/src/components/Version.astro diff --git a/www/docs/src/components/Youtube.astro b/docs/src/components/Youtube.astro similarity index 100% rename from www/docs/src/components/Youtube.astro rename to docs/src/components/Youtube.astro diff --git a/www/docs/src/components/landing/Card.astro b/docs/src/components/landing/Card.astro similarity index 100% rename from www/docs/src/components/landing/Card.astro rename to docs/src/components/landing/Card.astro diff --git a/www/docs/src/components/landing/ListCard.astro b/docs/src/components/landing/ListCard.astro similarity index 100% rename from www/docs/src/components/landing/ListCard.astro rename to docs/src/components/landing/ListCard.astro diff --git a/www/docs/src/components/landing/SplitCard.astro b/docs/src/components/landing/SplitCard.astro similarity index 100% rename from www/docs/src/components/landing/SplitCard.astro rename to docs/src/components/landing/SplitCard.astro diff --git a/docs/src/content.config.ts b/docs/src/content.config.ts new file mode 100644 index 0000000000..ea8715a5ef --- /dev/null +++ b/docs/src/content.config.ts @@ -0,0 +1,65 @@ +import { defineCollection, reference, z } from 'astro:content'; +import { docsLoader, i18nLoader } from '@astrojs/starlight/loaders'; +import { docsSchema, i18nSchema } from '@astrojs/starlight/schema'; +import { glob } from 'astro/loaders'; + +const packageCatalogSchema = z.object({ + name: z.string(), + description: z.string(), + docsLink: z.string(), + githubURL: z.string(), + catalog: z + .union([z.literal('studiocms'), z.literal('community')]) + .optional() + .default('studiocms'), + isPlugin: z.boolean().optional().default(false), + publiclyUsable: z.boolean().optional().default(false), + released: z.boolean().optional().default(true), +}); + +const baseSchema = z.object({ + type: z.literal('base').optional().default('base'), + i18nReady: z.boolean().optional().default(false), +}); + +const integrationSchema = baseSchema.extend({ + type: z.literal('integration'), + catalogEntry: reference('package-catalog'), +}); + +const redirectSchema = baseSchema.extend({ + type: z.literal('redirect'), + redirect: z.string(), +}); + +export const collections = { + docs: defineCollection({ + loader: docsLoader(), + schema: docsSchema({ extend: z.union([baseSchema, integrationSchema, redirectSchema]) }), + }), + i18n: defineCollection({ + loader: i18nLoader(), + schema: i18nSchema({ + extend: z.object({ + 'site-title.labels.docs': z.string().optional(), + 'site-title.labels.main-site': z.string().optional(), + 'site-title.labels.live-demo': z.string().optional(), + 'sponsors.sponsoredby': z.string().optional(), + 'package-catalog.readmore.start': z.string().optional(), + 'package-catalog.readmore.end': z.string().optional(), + 'integration-labels.changelog': z.string().optional(), + 'contributors.core-packages': z.string().optional(), + 'contributors.ui-library': z.string().optional(), + 'contributors.devapps': z.string().optional(), + 'contributors.plugins': z.string().optional(), + 'contributors.documentation': z.string().optional(), + 'contributors.website': z.string().optional(), + 'contributors.bots': z.string().optional(), + }), + }), + }), + 'package-catalog': defineCollection({ + loader: glob({ pattern: '*.json', base: 'src/content/package-catalog' }), + schema: packageCatalogSchema, + }), +}; diff --git a/www/docs/src/content/docs/config-reference/dashboard.mdx b/docs/src/content/docs/config-reference/dashboard/index.mdx similarity index 68% rename from www/docs/src/content/docs/config-reference/dashboard.mdx rename to docs/src/content/docs/config-reference/dashboard/index.mdx index 13040ab261..944827c2d2 100644 --- a/www/docs/src/content/docs/config-reference/dashboard.mdx +++ b/docs/src/content/docs/config-reference/dashboard/index.mdx @@ -79,59 +79,6 @@ export default defineStudioCMSConfig({ }) ``` -### `UnoCSSConfigOverride` - -- **Type:** `unocssConfigSchema{} | undefined{}` - -Allows customization of the UnoCSS Configuration. - -#### `injectReset` - -- **Type:** `boolean | undefined` -- **Default:** `false` - -Allows the user to enable or disable the UnoCSS Default Reset import. - -#### `injectEntry` - -- **Type:** `boolean | undefined` -- **Default:** `false` - -Allows the user to enable or disable the UnoCSS Default Entry import. - -#### `presetsConfig` - -- **Type:** `unocssPresetsSchema{} | undefined{}` - -Allows the user to modify the included UnoCSS Presets. - -##### `presetDaisyUI` - -- **Type:** `unocssDaisyUISchema{} | undefined{}` - -Allows the user to enable or disable the UnoCSS DaisyUI Preset. - -###### `themes` - -- **Type:** `Array | undefined` -- **Default:** `['dark', 'light']` - -Allows the user to use any of the available DaisyUI themes. - -###### `lightTheme` - -- **Type:** `string | undefined` -- **Default:** `'light'` - -Allows the user to set the default light theme. - -###### `darkTheme` - -- **Type:** `string | undefined` -- **Default:** `'dark'` - -Allows the user to set the default dark theme. - ### `developerConfig` - **Type:** `developerConfigSchema{} | undefined{}` diff --git a/www/docs/src/content/docs/config-reference/default-frontend-config.mdx b/docs/src/content/docs/config-reference/default-frontend-config/index.mdx similarity index 100% rename from www/docs/src/content/docs/config-reference/default-frontend-config.mdx rename to docs/src/content/docs/config-reference/default-frontend-config/index.mdx diff --git a/www/docs/src/content/docs/config-reference/image-service.mdx b/docs/src/content/docs/config-reference/image-service/index.mdx similarity index 100% rename from www/docs/src/content/docs/config-reference/image-service.mdx rename to docs/src/content/docs/config-reference/image-service/index.mdx diff --git a/docs/src/content/docs/config-reference/included-integrations/index.mdx b/docs/src/content/docs/config-reference/included-integrations/index.mdx new file mode 100644 index 0000000000..ac1ac45c03 --- /dev/null +++ b/docs/src/content/docs/config-reference/included-integrations/index.mdx @@ -0,0 +1,28 @@ +--- +i18nReady: true +title: includedIntegrations +description: A reference page for includedIntegrations +sidebar: + order: 6 +--- + +`includedIntegrations` is an object that is used to determine which Astro Integrations should be included in the `studioCMS`. Currently there are three Integrations that can be included: `useAstroRobots`, `astroRobotsConfig`, and `useInoxSitemap`. + +## Usage + +```ts twoslash {2-6} title="studiocms.config.mjs" +import { defineStudioCMSConfig } from 'studiocms'; +// ---cut--- +export default defineStudioCMSConfig({ + includedIntegrations: { + robotsTXT: true, + }, +}) +``` + +### `robotsTXT` + +- **Type:** `boolean` | `RobotsConfig{}` | `undefined` +- **Default:** `true` + +Allows the user to enable/disable the use of the StudioCMS Custom `astro-robots-txt` Integration. For more information on this Integration please visit the [Astro Robots Integration](https://www.npmjs.com/package/astro-robots). \ No newline at end of file diff --git a/www/docs/src/content/docs/config-reference/options-schema.mdx b/docs/src/content/docs/config-reference/options-schema.mdx similarity index 88% rename from www/docs/src/content/docs/config-reference/options-schema.mdx rename to docs/src/content/docs/config-reference/options-schema.mdx index a1817c1e7a..76e860dad0 100644 --- a/www/docs/src/content/docs/config-reference/options-schema.mdx +++ b/docs/src/content/docs/config-reference/options-schema.mdx @@ -21,7 +21,9 @@ export default defineStudioCMSConfig({ defaultFrontEndConfig: {}, dashboardConfig: {}, includedIntegrations: {}, + plugins: [], dateLocale: 'en-us', + overrides: {}, verbose: false, }); ``` @@ -85,6 +87,25 @@ export default defineStudioCMSConfig({ [See `includedIntegrations` for full options](/config-reference/included-integrations) +## `plugins` + +`plugins` is an array of plugins that can be used to extend the functionality of `studioCMS`. + +- **Type:** `Array` +- **Default:** `[]` + +### Usage + +```ts twoslash {2} title="studiocms.config.mjs" +import { defineStudioCMSConfig } from 'studiocms'; +// ---cut--- +export default defineStudioCMSConfig({ + plugins: [ + // Add your plugins here + ], +}) +``` + ## `dateLocale` Date locale used for formatting dates diff --git a/www/docs/src/content/docs/config-reference/overrides.mdx b/docs/src/content/docs/config-reference/overrides/index.mdx similarity index 100% rename from www/docs/src/content/docs/config-reference/overrides.mdx rename to docs/src/content/docs/config-reference/overrides/index.mdx diff --git a/www/docs/src/content/docs/config-reference/renderer-config.mdx b/docs/src/content/docs/config-reference/renderer-config/index.mdx similarity index 68% rename from www/docs/src/content/docs/config-reference/renderer-config.mdx rename to docs/src/content/docs/config-reference/renderer-config/index.mdx index c28bec468d..34e5d05f1e 100644 --- a/www/docs/src/content/docs/config-reference/renderer-config.mdx +++ b/docs/src/content/docs/config-reference/renderer-config/index.mdx @@ -12,10 +12,10 @@ import ReadMore from '~/components/ReadMore.astro'; The Markdown Content Renderer to use for rendering pages within StudioCMS -- **Type:** `'marked'` | `'markdoc'` | `'astro'` | `'mdx'` | `CustomRenderer` -- **Default:** `'marked'` +- **Type:** `'astro'` | `'markdoc'` | `'mdx'` | `CustomRenderer` +- **Default:** `'astro'` -`renderer` determines how Markdown content should be rendered in `studioCMS`. This is used to setup your content data. The default value is `marked` but you can also use `markdoc` or `astro` which uses Astro's built-in Remark processor. +`renderer` determines how Markdown content should be rendered in `studioCMS`. This is used to setup your content data. The default value is `astro` which uses the Astro built-in markdown processor but you can also use `markdoc`, `mdx` or define a Custom Renderer. ### Usage @@ -24,22 +24,13 @@ import { defineStudioCMSConfig } from 'studiocms'; // ---cut--- export default defineStudioCMSConfig({ rendererConfig: { - renderer: 'marked', - markedConfig: {}, + renderer: 'astro', markdocConfig: {}, mdxConfig: {}, }, }) ``` -## `markedConfig` - -`markedConfig` is an object that is used to determine how content should be rendered in the `studioCMS`. This is used to setup your content data. - -### Usage - -[See `markedConfig` for full options](/config-reference/marked-config) - ## `markdocConfig` `markdocConfig` is an object that is used to determine how content should be rendered in `studiocms` while using the MarkDoc renderer. diff --git a/www/docs/src/content/docs/config-reference/markdoc-config.mdx b/docs/src/content/docs/config-reference/renderer-config/markdoc-config.mdx similarity index 95% rename from www/docs/src/content/docs/config-reference/markdoc-config.mdx rename to docs/src/content/docs/config-reference/renderer-config/markdoc-config.mdx index 612c3231ca..37d7c00b54 100644 --- a/www/docs/src/content/docs/config-reference/markdoc-config.mdx +++ b/docs/src/content/docs/config-reference/renderer-config/markdoc-config.mdx @@ -30,7 +30,7 @@ This property has the following options: ### `renderType` -- **Type:** `'html' | 'react-static' | markdocRenderer` +- **Type:** `'html' | 'react-static' | MarkdocRenderer` The MarkDoc content renderer type to use for rendering pages and posts can be `html`, `react-static` or a custom markdocRenderer. diff --git a/www/docs/src/content/docs/config-reference/mdx-config.mdx b/docs/src/content/docs/config-reference/renderer-config/mdx-config.mdx similarity index 100% rename from www/docs/src/content/docs/config-reference/mdx-config.mdx rename to docs/src/content/docs/config-reference/renderer-config/mdx-config.mdx diff --git a/www/docs/src/content/docs/contributing/code-contributions.mdx b/docs/src/content/docs/contributing/code-contributions.mdx similarity index 97% rename from www/docs/src/content/docs/contributing/code-contributions.mdx rename to docs/src/content/docs/contributing/code-contributions.mdx index 2d3144c205..4978cf68b0 100644 --- a/www/docs/src/content/docs/contributing/code-contributions.mdx +++ b/docs/src/content/docs/contributing/code-contributions.mdx @@ -27,7 +27,7 @@ Scan through our [existing issues](https://github.com/withstudiocms/studiocms/is - Using the command line: - [Fork the repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) so that you can make your changes without affecting the original project until you're ready to merge them. -2. Install or update **Node.js** and **pnpm**, to the versions specified in [`.prototools`](https://github.com/astrolicious/studiocms/blob/main/.prototools). +2. Install or update **Node.js** and **pnpm**, to the versions specified in [`.prototools`](https://github.com/withstudiocms/studiocms/blob/main/.prototools). 3. Create a working branch and start with your changes! diff --git a/www/docs/src/content/docs/contributing/getting-started.mdx b/docs/src/content/docs/contributing/getting-started.mdx similarity index 100% rename from www/docs/src/content/docs/contributing/getting-started.mdx rename to docs/src/content/docs/contributing/getting-started.mdx diff --git a/www/docs/src/content/docs/contributing/translations.mdx b/docs/src/content/docs/contributing/translations.mdx similarity index 100% rename from www/docs/src/content/docs/contributing/translations.mdx rename to docs/src/content/docs/contributing/translations.mdx diff --git a/www/docs/src/content/docs/customizing/studiocms-renderers/index.mdx b/docs/src/content/docs/customizing/studiocms-renderers/index.mdx similarity index 73% rename from www/docs/src/content/docs/customizing/studiocms-renderers/index.mdx rename to docs/src/content/docs/customizing/studiocms-renderers/index.mdx index f7e5ddaf6e..457732e555 100644 --- a/www/docs/src/content/docs/customizing/studiocms-renderers/index.mdx +++ b/docs/src/content/docs/customizing/studiocms-renderers/index.mdx @@ -13,17 +13,16 @@ import { defineStudioCMSConfig } from 'studiocms'; // ---cut--- export default defineStudioCMSConfig({ rendererConfig: { - renderer: 'marked', - markedConfig: {}, + renderer: 'astro', markdocConfig: {}, mdxConfig: {}, }, }) ``` -## Marked Renderer +## Astro Remark Renderer -The default renderer for StudioCMS is the `marked` renderer. This renderer is used to render markdown content into HTML. This renderer is used by default when no other renderer is specified. +The default renderer for StudioCMS is the `astro` renderer. This renderer is used to render markdown content into HTML. This renderer is used by default when no other renderer is specified and adapts the markdown configuration from your `astro.config.mjs` file. ### Usage @@ -32,14 +31,14 @@ import { defineStudioCMSConfig } from 'studiocms'; // ---cut--- export default defineStudioCMSConfig({ rendererConfig: { - renderer: 'marked' + renderer: 'astro' }, }) ``` ### Configuration -see [Marked Configuration](/config-reference/marked-config/) for more information on how to configure the Marked renderer. +see [Marked Configuration](/config-reference/renderer-config/marked-config/) for more information on how to configure the Marked renderer. ## MDX Renderer @@ -59,7 +58,7 @@ export default defineStudioCMSConfig({ ### Configuration -See [MDX Configuration](/config-reference/mdx-config/) for more information on how to configure the MDX renderer. +See [MDX Configuration](/config-reference/renderer-config/mdx-config/) for more information on how to configure the MDX renderer. ## MarkDoc Renderer @@ -79,7 +78,7 @@ export default defineStudioCMSConfig({ ### Configuration -see [MarkDoc Configuration](/config-reference/markdoc-config/) for more information on how to configure the MarkDoc renderer. +see [MarkDoc Configuration](/config-reference/renderer-config/markdoc-config/) for more information on how to configure the MarkDoc renderer. ### Custom Renderers @@ -87,22 +86,6 @@ StudioCMS's MarkDoc renderer allows you to define custom renderers for your cont see [MarkDoc Renderers](/customizing/studiocms-renderers/markdoc/) for more information on how to define custom renderers for MarkDoc. -## Astro Remark Renderer - -The default Astro renderer for their markdown content. This renderer adapts the markdown configuration from your `astro.config.mjs` file. - -### Usage - -```ts twoslash {3} title="studiocms.config.mjs" -import { defineStudioCMSConfig } from 'studiocms'; -// ---cut--- -export default defineStudioCMSConfig({ - rendererConfig: { - renderer: 'astro' - }, -}) -``` - ### Configuration See [Astro's Markdown Configuration](https://docs.astro.build/en/reference/configuration-reference/#markdown-options) for more information on how to configure the Astro Remark renderer. diff --git a/www/docs/src/content/docs/customizing/studiocms-renderers/markdoc.mdx b/docs/src/content/docs/customizing/studiocms-renderers/markdoc.mdx similarity index 96% rename from www/docs/src/content/docs/customizing/studiocms-renderers/markdoc.mdx rename to docs/src/content/docs/customizing/studiocms-renderers/markdoc.mdx index 1ce9dc5003..fd6f358b00 100644 --- a/www/docs/src/content/docs/customizing/studiocms-renderers/markdoc.mdx +++ b/docs/src/content/docs/customizing/studiocms-renderers/markdoc.mdx @@ -108,4 +108,4 @@ Normally, users will not need to install the `@studiocms/renderers` package dire {/* */} -For more information on how to configure the MarkDoc renderer, see the [MarkDoc Configuration](/config-reference/markdoc-config/) documentation. +For more information on how to configure the MarkDoc renderer, see the [MarkDoc Configuration](/config-reference/renderer-config/markdoc-config/) documentation. diff --git a/www/docs/src/content/docs/how-it-works/index.mdx b/docs/src/content/docs/how-it-works/index.mdx similarity index 95% rename from www/docs/src/content/docs/how-it-works/index.mdx rename to docs/src/content/docs/how-it-works/index.mdx index cd5125b575..1a2bfbe9f5 100644 --- a/www/docs/src/content/docs/how-it-works/index.mdx +++ b/docs/src/content/docs/how-it-works/index.mdx @@ -85,15 +85,12 @@ The StudioCMS Auth integration provides authentication configuration options for ### StudioCMS: Dashboard -The StudioCMS Dashboard is a web interface that allows you to manage your StudioCMS project. It provides a user-friendly interface for creating, editing, and deleting content for your project. The StudioCMS Dashboard is built with [Astro](https://astro.build) and [DaisyUI](https://daisyui.com/) with [UnoCSS](https://unocss.dev). +The StudioCMS Dashboard is a web interface that allows you to manage your StudioCMS project. It provides a user-friendly interface for creating, editing, and deleting content for your project. The StudioCMS Dashboard is built with [Astro](https://astro.build) and our [`@studiocms/ui](/customizing/studiocms-ui/) library. #### Tech Stack - **Dashboard**: The StudioCMS Dashboard provides a user-friendly interface for managing your StudioCMS project. -- **UnoCSS**: The StudioCMS Dashboard uses UnoCSS for styling and theming. -- **DaisyUI**: The StudioCMS Dashboard uses DaisyUI for styling and theming. - **AuthConfig**: The StudioCMS Dashboard provides authentication configuration options. -- **Astrolace**: The StudioCMS Dashboard is built using [Astrolace](https://github.com/matthiesenxyz/astrolace) a [Shoelace.style](https://shoelace.style) Astro Integration. - **DashboardRouteOverride**: The StudioCMS Dashboard provides a dashboard route override configuration option. Allowing you to change the default route for the StudioCMS Dashboard and API. ### StudioCMS: ImageHandler diff --git a/www/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx similarity index 99% rename from www/docs/src/content/docs/index.mdx rename to docs/src/content/docs/index.mdx index d24f71530d..47b78c449f 100644 --- a/www/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -51,7 +51,7 @@ import { Center } from '@studiocms/ui/components'; For a more in-depth guide, check out the [Getting Started](/start-here/getting-started) guide. - Looking for a libSQL database? Check out [Turso](https://turso.tech). + Looking for a libSQL database? Check out [Turso](https://tur.so/studiocms). diff --git a/www/docs/src/content/docs/package-catalog/community-integrations/web-vitals.mdx b/docs/src/content/docs/package-catalog/community-integrations/web-vitals.mdx similarity index 100% rename from www/docs/src/content/docs/package-catalog/community-integrations/web-vitals.mdx rename to docs/src/content/docs/package-catalog/community-integrations/web-vitals.mdx diff --git a/www/docs/src/content/docs/package-catalog/index.mdx b/docs/src/content/docs/package-catalog/index.mdx similarity index 100% rename from www/docs/src/content/docs/package-catalog/index.mdx rename to docs/src/content/docs/package-catalog/index.mdx diff --git a/www/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-full-page-view.png b/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-full-page-view.png similarity index 100% rename from www/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-full-page-view.png rename to docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-full-page-view.png diff --git a/www/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-toolbar-app-closed.png b/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-toolbar-app-closed.png similarity index 100% rename from www/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-toolbar-app-closed.png rename to docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-toolbar-app-closed.png diff --git a/www/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-toolbar-app-expanded.png b/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-toolbar-app-expanded.png similarity index 100% rename from www/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-toolbar-app-expanded.png rename to docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-1-toolbar-app-expanded.png diff --git a/www/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-2-importer.png b/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-2-importer.png similarity index 100% rename from www/docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-2-importer.png rename to docs/src/content/docs/package-catalog/studiocms-integrations/assets/devapps-2-importer.png diff --git a/www/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-blog.mdx b/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-blog.mdx similarity index 80% rename from www/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-blog.mdx rename to docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-blog.mdx index 6c2c2e85fe..4ed6bb1cda 100644 --- a/www/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-blog.mdx +++ b/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-blog.mdx @@ -39,7 +39,7 @@ This Astro integration enables the StudioCMS Blog feature in your Astro project. 2. Add `@studiocms/blog` to your astro config file: - ```ts twoslash title="astro.config.mjs" ins={5, 14-19} + ```ts twoslash title="astro.config.mjs" ins={5, 14-21} import { defineConfig } from 'astro/config'; import node from '@astrojs/node'; import db from '@astrojs/db'; @@ -52,12 +52,15 @@ This Astro integration enables the StudioCMS Blog feature in your Astro project. adapter: node({ mode: "standalone" }), integrations: [ db(), // REQUIRED - studioCMS(), // REQUIRED - studioCMSBlog({ - config: { - title: "My StudioCMS Blog", - description: "A Simple Blog build with Astro, AstroDB, and StudioCMS.", - }, + studioCMS({ + plugins: [ + studioCMSBlog({ + config: { + title: "My StudioCMS Blog", + description: "A Simple Blog build with Astro, AstroDB, and StudioCMS.", + }, + }), + ], }), ], }); @@ -78,7 +81,7 @@ This integration will add the following new routes to your StudioCMS Controlled ### Example config -```ts twoslash title="astro.config.mjs" {3-4, 13-14} ins={5, 15-20} +```ts twoslash title="astro.config.mjs" {3-4, 13-14} ins={5, 15-22} import { defineConfig } from 'astro/config'; import node from '@astrojs/node'; import db from '@astrojs/db'; @@ -92,13 +95,16 @@ export default defineConfig({ adapter: node({ mode: "standalone" }), integrations: [ db(), // REQUIRED - studioCMS(), // REQUIRED - studioCMSBlog({ - config: { - title: "My StudioCMS Blog", - description: "A Simple Blog build with Astro, Astrojs/DB, and StudioCMS.", - }, - }), + studioCMS({ + plugins: [ + studioCMSBlog({ + config: { + title: "My StudioCMS Blog", + description: "A Simple Blog build with Astro, AstroDB, and StudioCMS.", + }, + }), + ], + }), ], }); ``` diff --git a/www/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-devapps.mdx b/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-devapps.mdx similarity index 100% rename from www/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-devapps.mdx rename to docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-devapps.mdx diff --git a/www/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-ui.mdx b/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-ui.mdx similarity index 77% rename from www/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-ui.mdx rename to docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-ui.mdx index 36bc106d56..ba39f6b6c6 100644 --- a/www/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-ui.mdx +++ b/docs/src/content/docs/package-catalog/studiocms-integrations/studiocms-ui.mdx @@ -1,9 +1,10 @@ --- title: "@studiocms/ui" type: "redirect" -redirect: "/customizing/studiocms-ui/" +redirect: "https://ui.studiocms.dev" description: "The UI library for StudioCMS. Includes the layouts & components we use to build StudioCMS." sidebar: + label: "@studiocms/ui ⤴" badge: text: 'Publicly Usable' variant: 'caution' diff --git a/www/docs/src/content/docs/start-here/configuration.mdx b/docs/src/content/docs/start-here/configuration.mdx similarity index 100% rename from www/docs/src/content/docs/start-here/configuration.mdx rename to docs/src/content/docs/start-here/configuration.mdx diff --git a/www/docs/src/content/docs/start-here/environment-variables.mdx b/docs/src/content/docs/start-here/environment-variables.mdx similarity index 83% rename from www/docs/src/content/docs/start-here/environment-variables.mdx rename to docs/src/content/docs/start-here/environment-variables.mdx index 38c8d48bf6..c78b200616 100644 --- a/www/docs/src/content/docs/start-here/environment-variables.mdx +++ b/docs/src/content/docs/start-here/environment-variables.mdx @@ -17,12 +17,10 @@ You can create a `.env` file in the root directory of your project and add the r For future reference on how to work with environment variables within Astro you can checkout [Environment Variables](https://docs.astro.build/guides/environment-variables) from the Astro documentation. - - ## Required Environment Variables +In order to use StudioCMS, there are a few required environment variables that you must set up in your `.env` file. + ### Database URL and Token for `@astrojs/db` `ASTRO_DB_REMOTE_URL` - The connection URL to your libSQL server @@ -50,13 +48,15 @@ openssl rand --base64 16 ``` -## oAuth Authentication Environment Variables +## Optional Environment Variables + +### oAuth Authentication Environment Variables For more information about setting up oAuth authentication, see the [Configure oAuth Authentication](/start-here/getting-started/#optional-configure-oauth-authentication) documentation. -### GitHub (Optional) +#### GitHub (Optional) To authenticate with GitHub, you need to add the following environment variables to your `.env` file: @@ -71,7 +71,7 @@ CMS_GITHUB_REDIRECT_URI= `CMS_GITHUB_REDIRECT_URI` is optional if you are using multiple redirect URIs with GitHub oAuth. -### Discord (Optional) +#### Discord (Optional) ```bash title=".env" # credentials for Discord OAuth @@ -80,7 +80,7 @@ CMS_DISCORD_CLIENT_SECRET= CMS_DISCORD_REDIRECT_URI= ``` -### Google (Optional) +#### Google (Optional) ```bash title=".env" # credentials for Google OAuth @@ -89,7 +89,7 @@ CMS_GOOGLE_CLIENT_SECRET= CMS_GOOGLE_REDIRECT_URI= ``` -### Auth0 (Optional) +#### Auth0 (Optional) ```bash title=".env" # credentials for auth0 OAuth @@ -99,9 +99,9 @@ CMS_AUTH0_DOMAIN= CMS_AUTH0_REDIRECT_URI= ``` -## Image Handler Environment Variables +### Image Handler Environment Variables -### Cloudinary (Optional) +#### Cloudinary (Optional) If you choose to use the built-in Cloudinary plugin, you will need to define the following: diff --git a/www/docs/src/content/docs/start-here/gallery.mdx b/docs/src/content/docs/start-here/gallery.mdx similarity index 66% rename from www/docs/src/content/docs/start-here/gallery.mdx rename to docs/src/content/docs/start-here/gallery.mdx index d75881bb07..aa423bba31 100644 --- a/www/docs/src/content/docs/start-here/gallery.mdx +++ b/docs/src/content/docs/start-here/gallery.mdx @@ -1,8 +1,11 @@ --- i18nReady: true title: Gallery -description: A small gallery of images to show off the StudioCMS +description: A small gallery of images to show off StudioCMS tableOfContents: false +banner: + content: | + These images are for 0.1.0-beta.7 and below. sidebar: order: 5 --- diff --git a/www/docs/src/content/docs/start-here/getting-started.mdx b/docs/src/content/docs/start-here/getting-started.mdx similarity index 100% rename from www/docs/src/content/docs/start-here/getting-started.mdx rename to docs/src/content/docs/start-here/getting-started.mdx diff --git a/www/docs/src/content/docs/start-here/why-studioCMS.mdx b/docs/src/content/docs/start-here/why-studioCMS.mdx similarity index 100% rename from www/docs/src/content/docs/start-here/why-studioCMS.mdx rename to docs/src/content/docs/start-here/why-studioCMS.mdx diff --git a/www/docs/src/content/docs/studioCMS-dark.png b/docs/src/content/docs/studioCMS-dark.png similarity index 100% rename from www/docs/src/content/docs/studioCMS-dark.png rename to docs/src/content/docs/studioCMS-dark.png diff --git a/www/docs/src/content/docs/studioCMS.png b/docs/src/content/docs/studioCMS.png similarity index 100% rename from www/docs/src/content/docs/studioCMS.png rename to docs/src/content/docs/studioCMS.png diff --git a/www/docs/src/content/i18n/en.json b/docs/src/content/i18n/en.json similarity index 100% rename from www/docs/src/content/i18n/en.json rename to docs/src/content/i18n/en.json diff --git a/www/docs/src/content/package-catalog/astrojs-web-vitals.json b/docs/src/content/package-catalog/astrojs-web-vitals.json similarity index 100% rename from www/docs/src/content/package-catalog/astrojs-web-vitals.json rename to docs/src/content/package-catalog/astrojs-web-vitals.json diff --git a/www/docs/src/content/package-catalog/studiocms-blog.json b/docs/src/content/package-catalog/studiocms-blog.json similarity index 100% rename from www/docs/src/content/package-catalog/studiocms-blog.json rename to docs/src/content/package-catalog/studiocms-blog.json diff --git a/www/docs/src/content/package-catalog/studiocms-devapps.json b/docs/src/content/package-catalog/studiocms-devapps.json similarity index 100% rename from www/docs/src/content/package-catalog/studiocms-devapps.json rename to docs/src/content/package-catalog/studiocms-devapps.json diff --git a/www/docs/src/content/package-catalog/studiocms-ui.json b/docs/src/content/package-catalog/studiocms-ui.json similarity index 84% rename from www/docs/src/content/package-catalog/studiocms-ui.json rename to docs/src/content/package-catalog/studiocms-ui.json index 0914fe8ad3..4eedb3481f 100644 --- a/www/docs/src/content/package-catalog/studiocms-ui.json +++ b/docs/src/content/package-catalog/studiocms-ui.json @@ -2,9 +2,9 @@ "$schema": "../../../.astro/collections/package-catalog.schema.json", "name": "@studiocms/ui", "description": "The UI library for StudioCMS. Includes the layouts & components we use to build StudioCMS.", - "docsLink": "/customizing/studiocms-ui/", + "docsLink": "https://ui.studiocms.dev", "githubURL": "https://github.com/withstudiocms/studiocms/tree/main/packages/studiocms_ui/", "catalog": "studiocms", - "released": false, + "released": true, "publiclyUsable": true } diff --git a/www/docs/src/content/package-catalog/studiocms.json b/docs/src/content/package-catalog/studiocms.json similarity index 100% rename from www/docs/src/content/package-catalog/studiocms.json rename to docs/src/content/package-catalog/studiocms.json diff --git a/www/docs/src/env.d.ts b/docs/src/env.d.ts similarity index 100% rename from www/docs/src/env.d.ts rename to docs/src/env.d.ts diff --git a/www/docs/src/plugins/rehype.types.ts b/docs/src/plugins/rehype.types.ts similarity index 100% rename from www/docs/src/plugins/rehype.types.ts rename to docs/src/plugins/rehype.types.ts diff --git a/www/docs/src/plugins/rehypeAutolink.ts b/docs/src/plugins/rehypeAutolink.ts similarity index 96% rename from www/docs/src/plugins/rehypeAutolink.ts rename to docs/src/plugins/rehypeAutolink.ts index 237af75c0d..97f6dc2aeb 100644 --- a/www/docs/src/plugins/rehypeAutolink.ts +++ b/docs/src/plugins/rehypeAutolink.ts @@ -3,7 +3,7 @@ import { h } from 'hastscript'; import { escape as esc } from 'html-escaper'; import rehypeAutoLink from 'rehype-autolink-headings'; import type { Options as rehypeAutolinkHeadingsOptions } from 'rehype-autolink-headings'; -import type { RehypePlugin } from './rehype.types'; +import type { RehypePlugin } from './rehype.types.ts'; const AnchorLinkIcon = h( 'span', diff --git a/www/docs/src/plugins/rehypeExternalLinks.ts b/docs/src/plugins/rehypeExternalLinks.ts similarity index 87% rename from www/docs/src/plugins/rehypeExternalLinks.ts rename to docs/src/plugins/rehypeExternalLinks.ts index f1203c27c0..9f519f1192 100644 --- a/www/docs/src/plugins/rehypeExternalLinks.ts +++ b/docs/src/plugins/rehypeExternalLinks.ts @@ -1,5 +1,5 @@ import rehypeExternal from 'rehype-external-links'; -import type { RehypePlugin } from './rehype.types'; +import type { RehypePlugin } from './rehype.types.ts'; // biome-ignore lint/suspicious/noExplicitAny: any is used to match the generic type export const rehypeExternalLinks: [RehypePlugin, any] = [ diff --git a/www/docs/src/plugins/rehypePluginKit.ts b/docs/src/plugins/rehypePluginKit.ts similarity index 51% rename from www/docs/src/plugins/rehypePluginKit.ts rename to docs/src/plugins/rehypePluginKit.ts index 4a74d0a0b4..9725166895 100644 --- a/www/docs/src/plugins/rehypePluginKit.ts +++ b/docs/src/plugins/rehypePluginKit.ts @@ -1,7 +1,7 @@ import rehypeSlug from 'rehype-slug'; -import type { RehypePlugins } from './rehype.types'; -import rehypeAutolinkHeadings from './rehypeAutolink'; -import rehypeExternalLinks from './rehypeExternalLinks'; +import type { RehypePlugins } from './rehype.types.ts'; +import rehypeAutolinkHeadings from './rehypeAutolink.ts'; +import rehypeExternalLinks from './rehypeExternalLinks.ts'; export const rehypePluginKit: RehypePlugins = [ rehypeSlug, diff --git a/www/docs/src/share-link.ts b/docs/src/share-link.ts similarity index 100% rename from www/docs/src/share-link.ts rename to docs/src/share-link.ts diff --git a/www/docs/src/starlightOverrides/Head.astro b/docs/src/starlightOverrides/Head.astro similarity index 100% rename from www/docs/src/starlightOverrides/Head.astro rename to docs/src/starlightOverrides/Head.astro diff --git a/www/docs/src/starlightOverrides/PageTitle.astro b/docs/src/starlightOverrides/PageTitle.astro similarity index 100% rename from www/docs/src/starlightOverrides/PageTitle.astro rename to docs/src/starlightOverrides/PageTitle.astro diff --git a/www/docs/src/starlightOverrides/Sidebar.astro b/docs/src/starlightOverrides/Sidebar.astro similarity index 100% rename from www/docs/src/starlightOverrides/Sidebar.astro rename to docs/src/starlightOverrides/Sidebar.astro diff --git a/www/docs/src/starlightOverrides/SiteTitle.astro b/docs/src/starlightOverrides/SiteTitle.astro similarity index 100% rename from www/docs/src/starlightOverrides/SiteTitle.astro rename to docs/src/starlightOverrides/SiteTitle.astro diff --git a/www/docs/src/styles/sponsorcolors.css b/docs/src/styles/sponsorcolors.css similarity index 100% rename from www/docs/src/styles/sponsorcolors.css rename to docs/src/styles/sponsorcolors.css diff --git a/www/docs/src/styles/starlight.css b/docs/src/styles/starlight.css similarity index 94% rename from www/docs/src/styles/starlight.css rename to docs/src/styles/starlight.css index 2e424e718c..919e3a3cb4 100644 --- a/www/docs/src/styles/starlight.css +++ b/docs/src/styles/starlight.css @@ -77,9 +77,11 @@ border-radius: 8px; } -code, +div.expressive-code, starlight-file-tree { - border-radius: 4px; + border-radius: 8px; + overflow: hidden; + border: 1px solid var(--ec-brdCol); } aside.starlight-aside { @@ -195,3 +197,10 @@ button[aria-label="Menu"][aria-controls="starlight__sidebar"] { a { text-decoration: none; } + +.expressive-code figcaption, +.expressive-code figcaption::before, +.expressive-code pre, +.expressive-code span.title { + border: none !important; +} diff --git a/www/docs/src/typedocHelpers.ts b/docs/src/typedocHelpers.ts similarity index 96% rename from www/docs/src/typedocHelpers.ts rename to docs/src/typedocHelpers.ts index db522629bb..b5700471ea 100644 --- a/www/docs/src/typedocHelpers.ts +++ b/docs/src/typedocHelpers.ts @@ -2,7 +2,7 @@ import type { StarlightTypeDocOptions } from 'starlight-typedoc'; // Utility function to create TypeDoc related paths export function getFilePathToPackage(name: string, path: string) { - return `../../packages/${name}/${path}`; + return `../packages/${name}/${path}`; } // Utility function to create TypeDoc options for the StudioCMS packages so that each package documentation is the same when generated diff --git a/www/docs/src/util-server.ts b/docs/src/util-server.ts similarity index 100% rename from www/docs/src/util-server.ts rename to docs/src/util-server.ts diff --git a/www/docs/src/util/SponsorLink.astro b/docs/src/util/SponsorLink.astro similarity index 100% rename from www/docs/src/util/SponsorLink.astro rename to docs/src/util/SponsorLink.astro diff --git a/www/docs/src/util/contributors.config.ts b/docs/src/util/contributors.config.ts similarity index 94% rename from www/docs/src/util/contributors.config.ts rename to docs/src/util/contributors.config.ts index a4d7b04502..0bb3d6702c 100644 --- a/www/docs/src/util/contributors.config.ts +++ b/docs/src/util/contributors.config.ts @@ -23,9 +23,10 @@ export const contributorConfig = (Astro: AstroGlobal): ContributorConfig[] => [ paths: [ // OLD Paths 'packages/studioCMS/', + 'playgrounds/node/', // NEW Paths 'README.md', - 'playgrounds/node/', + 'playground/', 'packages/studiocms/', 'packages/studiocms_assets/', 'packages/studiocms_auth/', @@ -48,6 +49,10 @@ export const contributorConfig = (Astro: AstroGlobal): ContributorConfig[] => [ type: 'byPath', paths: ['packages/studiocms_ui/', 'playgrounds/ui/'], }, + { + repo: 'withstudiocms/ui', + type: 'all', + }, ], }, { @@ -81,7 +86,7 @@ export const contributorConfig = (Astro: AstroGlobal): ContributorConfig[] => [ { repo: 'withstudiocms/studiocms', type: 'byPath', - paths: ['www/docs/'], + paths: ['www/docs/', 'docs/'], }, ], }, diff --git a/www/docs/src/util/getContributors.ts b/docs/src/util/getContributors.ts similarity index 98% rename from www/docs/src/util/getContributors.ts rename to docs/src/util/getContributors.ts index 79c8597ef8..37eeaa3965 100644 --- a/www/docs/src/util/getContributors.ts +++ b/docs/src/util/getContributors.ts @@ -1,6 +1,6 @@ import type { AstroGlobal } from 'astro'; -import { cachedFetch } from '../util-server'; -import { StudioCMSServiceAccounts, contributorConfig } from './contributors.config'; +import { cachedFetch } from '../util-server.ts'; +import { StudioCMSServiceAccounts, contributorConfig } from './contributors.config.ts'; export interface Contributor { login: string; diff --git a/www/docs/starlight-types.ts b/docs/starlight-types.ts similarity index 100% rename from www/docs/starlight-types.ts rename to docs/starlight-types.ts diff --git a/www/docs/starlight-virtual.d.ts b/docs/starlight-virtual.d.ts similarity index 100% rename from www/docs/starlight-virtual.d.ts rename to docs/starlight-virtual.d.ts diff --git a/www/docs/tsconfig.json b/docs/tsconfig.json similarity index 65% rename from www/docs/tsconfig.json rename to docs/tsconfig.json index 754f6f39c8..462e11b6de 100644 --- a/www/docs/tsconfig.json +++ b/docs/tsconfig.json @@ -1,13 +1,12 @@ { "extends": "astro/tsconfigs/strict", + "include": [".astro/types.d.ts", "**/*"], "exclude": ["dist"], "compilerOptions": { "allowJs": true, - "moduleResolution": "bundler", "baseUrl": ".", "paths": { "~/*": ["src/*"] - }, - "outDir": "../../.moon/cache/types/www/docs" + } } } diff --git a/www/docs/typedoc.config.ts b/docs/typedoc.config.ts similarity index 94% rename from www/docs/typedoc.config.ts rename to docs/typedoc.config.ts index 6f4637a496..773bdd1900 100644 --- a/www/docs/typedoc.config.ts +++ b/docs/typedoc.config.ts @@ -1,7 +1,7 @@ import type { StarlightPlugin } from '@astrojs/starlight/types'; import { createStarlightTypeDocPlugin } from 'starlight-typedoc'; -import { getFilePathToPackage, makeTypedocOpts } from './src/typedocHelpers'; -import type { SidebarGroup } from './starlight-types'; +import { getFilePathToPackage, makeTypedocOpts } from './src/typedocHelpers.ts'; +import type { SidebarGroup } from './starlight-types.ts'; // Create Starlight TypeDoc Plugins for different parts of the Astro StudioCMS Project @@ -39,11 +39,7 @@ const TypeDocPlugins = (isProd: boolean, testingMode: boolean): StarlightPlugin[ name: 'studiocms', output: 'studiocms', dir: 'studiocms', - entryPoints: [ - getFilePathToPackage('studiocms', 'src/index.ts'), - getFilePathToPackage('studiocms', 'src/integration.ts'), - getFilePathToPackage('studiocms', 'src/updateCheck.ts'), - ], + entryPoints: [getFilePathToPackage('studiocms', 'src/index.ts')], }) ), tdAuth( @@ -95,9 +91,9 @@ const TypeDocPlugins = (isProd: boolean, testingMode: boolean): StarlightPlugin[ getFilePathToPackage('studiocms_core', 'src/schemas/config/index.ts'), getFilePathToPackage('studiocms_core', 'src/schemas/config/integrations.ts'), getFilePathToPackage('studiocms_core', 'src/schemas/config/markdoc.ts'), - getFilePathToPackage('studiocms_core', 'src/schemas/config/marked.ts'), getFilePathToPackage('studiocms_core', 'src/schemas/config/rendererConfig.ts'), - getFilePathToPackage('studiocms_core', 'src/schemas/config/unocss.ts'), + getFilePathToPackage('studiocms_core', 'src/schemas/plugins/shared.ts'), + getFilePathToPackage('studiocms_core', 'src/schemas/plugins/index.ts'), getFilePathToPackage('studiocms_core', 'src/lib/index.ts'), getFilePathToPackage('studiocms_core', 'src/lib/configManager.ts'), getFilePathToPackage('studiocms_core', 'src/lib/convertDashboardLinksType.ts'), @@ -116,6 +112,7 @@ const TypeDocPlugins = (isProd: boolean, testingMode: boolean): StarlightPlugin[ getFilePathToPackage('studiocms_core', 'src/db/tables.ts'), getFilePathToPackage('studiocms_core', 'src/db/tsTables.ts'), getFilePathToPackage('studiocms_core', 'src/components/index.ts'), + getFilePathToPackage('studiocms_core', 'src/i18n/index.ts'), ], }) ), @@ -145,6 +142,7 @@ const TypeDocPlugins = (isProd: boolean, testingMode: boolean): StarlightPlugin[ entryPoints: [ getFilePathToPackage('studiocms_frontend', 'src/index.ts'), getFilePathToPackage('studiocms_frontend', 'src/integration.ts'), + getFilePathToPackage('studiocms_frontend', 'src/schema.ts'), getFilePathToPackage('studiocms_frontend', 'src/components/index.ts'), ], }) @@ -160,7 +158,7 @@ const TypeDocPlugins = (isProd: boolean, testingMode: boolean): StarlightPlugin[ getFilePathToPackage('studiocms_imagehandler', 'src/integration.ts'), getFilePathToPackage('studiocms_imagehandler', 'src/components/index.ts'), getFilePathToPackage('studiocms_imagehandler', 'src/components/props.ts'), - getFilePathToPackage('studiocms_imagehandler', 'src/plugins/cloudinary.ts'), + getFilePathToPackage('studiocms_imagehandler', 'src/components/plugins/cloudinary.ts'), ], }) ), @@ -183,7 +181,6 @@ const TypeDocPlugins = (isProd: boolean, testingMode: boolean): StarlightPlugin[ getFilePathToPackage('studiocms_renderers', 'src/lib/markdoc/index.ts'), getFilePathToPackage('studiocms_renderers', 'src/lib/markdoc/markdocHTML.ts'), getFilePathToPackage('studiocms_renderers', 'src/lib/markdoc/markdocReactStatic.ts'), - getFilePathToPackage('studiocms_renderers', 'src/lib/marked/index.ts'), getFilePathToPackage('studiocms_renderers', 'src/lib/mdx/index.ts'), ], }) @@ -209,15 +206,16 @@ const TypeDocPlugins = (isProd: boolean, testingMode: boolean): StarlightPlugin[ entryPoints: [ getFilePathToPackage('studiocms_devapps', 'src/index.ts'), getFilePathToPackage('studiocms_devapps', 'src/integration.ts'), + getFilePathToPackage('studiocms_devapps', 'src/apps/libsqlViewer.ts'), + getFilePathToPackage('studiocms_devapps', 'src/apps/wp-importer.ts'), + getFilePathToPackage('studiocms_devapps', 'src/apps/utils/index.ts'), + getFilePathToPackage('studiocms_devapps', 'src/schema/index.ts'), + getFilePathToPackage('studiocms_devapps', 'src/schema/appsConfig.ts'), + getFilePathToPackage('studiocms_devapps', 'src/schema/wp-api/index.ts'), getFilePathToPackage('studiocms_devapps', 'src/utils/pathGenerator.ts'), getFilePathToPackage('studiocms_devapps', 'src/utils/wp-api/utils.ts'), getFilePathToPackage('studiocms_devapps', 'src/utils/wp-api/converters.ts'), getFilePathToPackage('studiocms_devapps', 'src/utils/wp-api/index.ts'), - getFilePathToPackage('studiocms_devapps', 'src/schema/index.ts'), - getFilePathToPackage('studiocms_devapps', 'src/schema/appsConfig.ts'), - getFilePathToPackage('studiocms_devapps', 'src/schema/wp-api/index.ts'), - getFilePathToPackage('studiocms_devapps', 'src/apps/libsqlViewer.ts'), - getFilePathToPackage('studiocms_devapps', 'src/apps/wp-importer.ts'), ], }) ), diff --git a/package.json b/package.json index 4168a3453e..24b4d7310a 100644 --- a/package.json +++ b/package.json @@ -6,26 +6,27 @@ "node": "20.14.0" }, "scripts": { - "moon": "moon", - "changeset": "changeset", + "build": "pnpm build:studiocms && pnpm --filter node-playground build", + "build:docs": "pnpm --filter docs build", + "docs:dev": "pnpm build:studiocms && pnpm --filter docs dev", "playground:dev": "pnpm --filter node-playground dev", - "playground:login": "pnpm --filter node-playground db:login", - "playground:link": "pnpm --filter node-playground db:link", "playground:push": "pnpm --filter node-playground db:push", - "ui:dev": "pnpm --filter ui-playground dev", - "ui:build": "pnpm --filter ui-playground build", - "ui:preview": "pnpm --filter ui-playground preview", - "build": "pnpm --filter node-playground build", + + "build:studiocms": "pnpm --filter studiocms --filter @studiocms/* build", + "dev:studiocms": "pnpm --stream --filter studiocms --filter @studiocms/* -r -parallel dev", + "dev": "pnpm --stream --filter studiocms --filter @studiocms/* --filter node-playground -r -parallel dev", + + "build:cli": "pnpm --filter studiocms build", + "cli:test": "pnpm studiocms", + + "changeset": "changeset", "lint": "biome check .", "lint:fix": "biome check --write .", - "build:web": "pnpm --filter web build", - "build:docs": "pnpm --filter docs build", - "update:web": "pnpm --filter web up --latest", - "update:docs": "pnpm --filter docs up --latest", - "web:dev": "pnpm --filter web dev", - "docs:dev": "pnpm --filter docs dev", - "lunaria:build": "pnpm --filter docs lunaria:build", - "translations:changeset": "tsm --require=./scripts/filter-warnings.cjs ./scripts/translation-changeset.ts", + + "ci:prepublish": "pnpm build:studiocms", + "ci:lunaria:build": "pnpm --filter docs lunaria:build", + "ci:lunaria:report": "pnpm tsm --require=./scripts/filter-warnings.cjs ./www/docs/scripts/lunaria-report-bot.ts", + "ci:translations:changeset": "tsm --require=./scripts/filter-warnings.cjs ./scripts/translation-changeset.ts", "ci:lint": "biome ci --formatter-enabled=true --organize-imports-enabled=true --reporter=github", "ci:install": "pnpm install --frozen-lockfile", "ci:version": "pnpm changeset version", @@ -35,11 +36,12 @@ "devDependencies": { "@actions/core": "^1.11.1", "@biomejs/biome": "1.9.4", - "@moonrepo/cli": "1.28.3", "@changesets/cli": "2.27.9", "@changesets/config": "3.0.3", "@changesets/changelog-github": "0.5.0", "@changesets/write": "0.3.2", + "build-scripts": "workspace:*", + "studiocms": "workspace:*", "execa": "9.5.1", "pkg-pr-new": "0.0.32", "typescript": "catalog:", diff --git a/packages/studiocms/.gitignore b/packages/studiocms/.gitignore index 4e02004cb0..8f18003eca 100644 --- a/packages/studiocms/.gitignore +++ b/packages/studiocms/.gitignore @@ -18,4 +18,6 @@ pnpm-debug.log* # macOS-specific files .DS_Store -.npmrc \ No newline at end of file +.npmrc + +dist/ \ No newline at end of file diff --git a/packages/studiocms/LICENSE b/packages/studiocms/LICENSE index a30d1fbe47..94787104c4 100644 --- a/packages/studiocms/LICENSE +++ b/packages/studiocms/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Astrolicious - StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares +Copyright (c) 2024 StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/studiocms/README.md b/packages/studiocms/README.md index 0cf5b48f60..16e85c4358 100644 --- a/packages/studiocms/README.md +++ b/packages/studiocms/README.md @@ -2,9 +2,12 @@ As part of our efforts, we're excited to introduce StudioCMS - a dedicated content management system (CMS) built on top of Astro's latest feature, [Astro DB](https://docs.astro.build/en/guides/astro-db/). This project was developed by [Adam](https://github.com/Adammatthiesen), [Dreyfus](https://github.com/dreyfus92), and [Jumper](https://github.com/jdtjenkins), three passionate members of the Astro community. +> [!IMPORTANT] +> This project is still in early development and it is not yet ready for production use. If you encounter any issues or have ideas for new features, please let us know by [opening an issue](https://github.com/withstudiocms/studiocms/issues/new/choose) on our GitHub repository. + ## Sponsor - + ## Why another CMS? @@ -25,17 +28,17 @@ StudioCMS is built from the ground up to seamlessly integrate with Astro's robus ## Key Features -**Astro Foundation:** StudioCMS leverages Astro's robust and efficient framework, providing a solid base for building and scaling applications. +**Built on Astro:** StudioCMS leverages Astro's robust and efficient framework, providing a solid base for building and scaling applications. -**Enhanced Markdown:** We've incorporated 'Marked' with support for extensions, enriching the markdown experience with greater flexibility and functionality. +**Support for Markdown:** Incorporates Astro's Markdown processing, enriching the markdown experience with greater flexibility and functionality. -**Shiki Syntax Highlighting:** StudioCMS offers Shiki-powered syntax highlighting, ensuring your code is both visually appealing and easy to read. This is especially useful in non-Cloudflare environments due to bundle size considerations. +**Markdoc Integration:** Provides an alternative to Astro's Markdown with Markdoc, offering users a choice for their markdown processing needs. -**Markdoc Integration:** In addition to 'Marked', StudioCMS provides an alternative with Markdoc, offering users a choice for their markdown processing needs. +**Secure libSQL Database:** All data is securely housed within your libSQL database, ensuring accessibility to your data to only authorized users to your libSQL provider or Self-hosted server. -**Built-in Authentication:** StudioCMS features built-in authentication with support for multiple platforms, including Local and Github, enhancing security and user management (currently in development). +**Built-in Authentication:** Features built-in authentication with support for multiple platforms including Local and Github, enhancing security and user management. -**Unpic Image Service:** StudioCMS includes a free and efficient image service, Unpic, which makes managing external URLs straightforward, with support for major CDNs. +**Web Vitals:**Integration with '@astrojs/web-vitals' for monitoring and providing insights into web performance metrics, ensuring optimal user experiences. ## A Community-Driven Effort diff --git a/packages/studiocms/dev.d.ts b/packages/studiocms/dev.d.ts new file mode 100644 index 0000000000..ea19848c7c --- /dev/null +++ b/packages/studiocms/dev.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/packages/studiocms/env.d.ts b/packages/studiocms/env.d.ts deleted file mode 100644 index 86fd4d5bb6..0000000000 --- a/packages/studiocms/env.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -interface ImportMetaEnv { - readonly PROD: boolean; - readonly BASE_URL: string; -} - -interface ImportMeta { - readonly env: ImportMetaEnv; -} diff --git a/packages/studiocms/moon.yml b/packages/studiocms/moon.yml deleted file mode 100644 index 8dce3a0084..0000000000 --- a/packages/studiocms/moon.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: 'typescript' -tags: ['astro', 'typescript'] - -project: - name: 'studiocms' - description: 'A CMS that bridges Astro with Astro DB & Studio' - maintainers: ['Adammatthiesen','jdtjenkins','dreyfus92'] \ No newline at end of file diff --git a/packages/studiocms/package.json b/packages/studiocms/package.json index b8d010e99e..05ddf3b5e8 100644 --- a/packages/studiocms/package.json +++ b/packages/studiocms/package.json @@ -1,7 +1,7 @@ { "name": "studiocms", "version": "0.1.0-beta.7", - "description": "A dedicated CMS for Astro DB. Built from the ground up by the Astro community.", + "description": "A dedicated CMS for Astro and Astro DB. Built from the ground up by the Astro community.", "author": { "name": "Adam Matthiesen | Jacob Jenkins | Paul Valladares", "url": "https://studiocms.dev" @@ -17,17 +17,17 @@ ], "license": "MIT", "keywords": [ - "astro", + "cms", "astrocms", "astrodb", - "astrolicious", "astrostudio", - "astro-integration", + "astrostudiocms", + "studiocms", "astro-studio", "astro-studiocms", - "cms", - "studiocms", - "withastro" + "astro", + "withastro", + "astro-integration" ], "homepage": "https://studiocms.dev", "publishConfig": { @@ -35,11 +35,28 @@ }, "sideEffects": false, "files": [ - "src" + "dist", + "studiocms-cli.mjs", + "CHANGELOG.md", + "LICENSE", + "README.md" ], + "scripts": { + "build": "build-scripts build 'src/**/*.ts'", + "dev": "build-scripts dev 'src/**/*.ts'" + }, + "bin": { + "studiocms": "./studiocms-cli.mjs" + }, "exports": { - ".": "./src/index.ts", - "./blog": "./src/blog/index.ts" + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./config": { + "types": "./dist/config.d.ts", + "default": "./dist/config.js" + } }, "type": "module", "dependencies": { @@ -53,62 +70,50 @@ "@studiocms/renderers": "workspace:*", "@studiocms/robotstxt": "workspace:*", + "@studiocms/ui": "catalog:", "astro-integration-kit": "catalog:", + "@inox-tools/aik-mod": "0.9.1", "package-json": "catalog:studiocms", "semver": "catalog:studiocms", - "mrmime": "catalog:studiocms-core", - "remark-rehype": "catalog:studiocms-core", - "mdast-util-to-hast": "catalog:studiocms-core", - - "@oslojs/crypto": "catalog:studiocms-auth", - "@oslojs/encoding": "catalog:studiocms-auth", - "@oslojs/binary": "catalog:studiocms-auth", - "@types/bcryptjs": "catalog:studiocms-auth", - "bcryptjs": "catalog:studiocms-auth", - "@types/three": "catalog:studiocms-auth", - "arctic": "catalog:studiocms-auth", - "three": "catalog:studiocms-auth", - - "@fontsource-variable/onest": "catalog:studiocms-shared", "@inox-tools/runtime-logger": "catalog:studiocms-shared", "@matthiesenxyz/astrodtsbuilder": "catalog:studiocms-shared", - "@matthiesenxyz/integration-utils": "catalog:studiocms-shared", "rollup-plugin-copy": "catalog:studiocms-shared", - "marked": "catalog:studiocms-renderer", - "marked-alert": "catalog:studiocms-renderer", - "marked-emoji": "catalog:studiocms-renderer", - "marked-footnote": "catalog:studiocms-renderer", - "marked-shiki": "catalog:studiocms-renderer", - "marked-smartypants": "catalog:studiocms-renderer", - "@markdoc/markdoc": "catalog:studiocms-renderer", - "shiki": "catalog:studiocms-renderer", - "@shikijs/transformers": "catalog:studiocms-renderer", + "mdast": "catalog:studiocms-shared", + "mdast-util-from-markdown": "catalog:studiocms-shared", + "mdast-util-to-markdown": "catalog:studiocms-shared", + "mdast-util-to-string": "catalog:studiocms-shared", + "unist-util-visit": "catalog:studiocms-shared", - "@cloudinary/url-gen": "catalog:studiocms-imagehandler", - - "@matthiesenxyz/astrolace": "catalog:studiocms-shared", - "@matthiesenxyz/unocss-preset-daisyui": "catalog:studiocms-shared", - "@unocss/astro": "catalog:studiocms-shared", - "@unocss/reset": "catalog:studiocms-shared", - "daisyui": "catalog:studiocms-shared", - "unocss": "catalog:studiocms-shared" + "@bluwy/giget-core": "^0.1.2", + "@clack/core": "0.4.1", + "@clack/prompts": "0.9.1", + "@commander-js/extra-typings": "^13.0.0", + "commander": "^13.0.0", + "figlet": "^1.8.0", + "chalk": "^5.4.1", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0", + "boxen": "^8.0.1", + "lodash": "catalog:studiocms-shared", + "is-unicode-supported": "^2.1.0", + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0" }, "peerDependencies": { "@astrojs/db": "catalog:min", "astro": "catalog:min", - "@studiocms/blog": "workspace:*" - }, - "peerDependenciesMeta": { - "@studiocms/blog": { - "optional": true - } + "vite": "catalog:min" }, "devDependencies": { - "vite": "catalog:", "typescript": "catalog:", - "@types/semver": "catalog:studiocms" + "@types/semver": "catalog:studiocms", + "@types/mdast": "catalog:studiocms-shared", + "@types/lodash": "catalog:studiocms-shared", + "@types/figlet": "^1.7.0", + "@types/node": "catalog:" } } diff --git a/packages/studiocms/src/blog/index.ts b/packages/studiocms/src/blog/index.ts deleted file mode 100644 index c841f2a888..0000000000 --- a/packages/studiocms/src/blog/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import studioCMSBlog from '@studiocms/blog'; - -/** - * # StudioCMS Blog Theme(Integration) - * *Note: To use this export, you must have `@studiocms/blog` installed in your project dependencies.* - * #### Powered by [`astro-theme-provider`](https://github.com/astrolicious/astro-theme-provider) by [Bryce Russell](https://github.com/BryceRussell) - * - * This theme provides a Blog Index Page and RSS Feed for your StudioCMS Site as well as route handling for Blog Posts. - * - * @example - * import { defineConfig } from 'astro/config'; - * import db from '@astrojs/db'; - * import studioCMS from 'studiocms'; - * import studioCMSBlog from 'studiocms/blog'; - * - * // https://astro.build/config - * export default defineConfig({ - * site: "https://example.com", - * output: "server", - * adapter: ... - * integrations: [ - * db(), // REQUIRED - `@astrojs/db` must be included in the integrations list - * studioCMS(), // REQUIRED - StudioCMS must be included in the integrations list - * studioCMSBlog({ - * config: { - * title: "My StudioCMS Blog", - * description: "A Simple Blog build with Astro, Astrojs/DB, and StudioCMS.". - * }, - * }), - * ], - * }); - */ -const integration = studioCMSBlog; - -export default integration; diff --git a/packages/studiocms/src/cli/cmds/get-turso.ts b/packages/studiocms/src/cli/cmds/get-turso.ts new file mode 100644 index 0000000000..779ddd7c60 --- /dev/null +++ b/packages/studiocms/src/cli/cmds/get-turso.ts @@ -0,0 +1,15 @@ +import { Command } from '../lib/commander.js'; +import { runInteractiveCommand } from '../lib/runExternal'; + +await new Command('getTurso') + .description('Turso CLI Utilities') + .summary('Turso CLI Utilities') + .action(async () => { + try { + await runInteractiveCommand('curl -sSfL https://get.tur.so/install.sh | bash'); + console.log('Command completed successfully.'); + } catch (error) { + console.error(`Failed to run Turso install: ${(error as Error).message}`); + } + }) + .parseAsync(); diff --git a/packages/studiocms/src/cli/cmds/init.ts b/packages/studiocms/src/cli/cmds/init.ts new file mode 100644 index 0000000000..1ac077dc0f --- /dev/null +++ b/packages/studiocms/src/cli/cmds/init.ts @@ -0,0 +1,12 @@ +import { Option } from '@commander-js/extra-typings'; +import { Command } from '../lib/commander.js'; +import { initCMD } from './init/index.js'; + +await new Command('init') + .description('Initialize the StudioCMS project after new installation.') + .summary('Initialize StudioCMS Project') + .option('-d, --dry-run', 'Dry run mode.') + .option('--skip-banners', 'Skip all banners.') + .addOption(new Option('--debug', 'Enable debug mode.').hideHelp(true)) + .action(initCMD) + .parseAsync(); diff --git a/packages/studiocms/src/cli/cmds/init/index.ts b/packages/studiocms/src/cli/cmds/init/index.ts new file mode 100644 index 0000000000..4af1e0a1e2 --- /dev/null +++ b/packages/studiocms/src/cli/cmds/init/index.ts @@ -0,0 +1,86 @@ +import color from 'chalk'; +import type { instanceCommand } from '../../lib/commander.js'; +import { getContext } from '../../lib/context.js'; +import { StudioCMSColorwayBg, label } from '../../lib/utils.js'; +import { intro } from '../../shared/intro.js'; +import { env } from './steps/envBuilder.js'; +import { next } from './steps/nextSteps.js'; + +export { getContext }; + +export async function initCMD(this: instanceCommand) { + // Parse options + const opts = this.opts(); + + // Get context + const ctx = await getContext(opts); + + ctx.logger.log('Starting interactive CLI...'); + + ctx.debug && ctx.logger.debug(`Options: ${JSON.stringify(opts, null, 2)}`); + + ctx.debug && ctx.logger.debug(`Context: ${JSON.stringify(ctx, null, 2)}`); + + console.log(''); // Add a line break + + ctx.debug && ctx.logger.debug('Running interactive CLI Steps...'); + + ctx.p.intro( + `${label('StudioCMS', StudioCMSColorwayBg, color.black)} Interactive CLI - initializing...` + ); + + // Run intro + await intro(ctx); + + // Steps + const steps = []; + + ctx.debug && ctx.logger.debug('Running Option selection...'); + + // Get options for steps + const options = await ctx.p.multiselect({ + message: 'What would you like to do? (Select all that apply)', + options: [{ value: 'env', label: 'Setup Environment File', hint: 'Create a .env file' }], + }); + + // Cancel or add steps based on options + if (typeof options === 'symbol') { + ctx.pCancel(options); + } else { + options.includes('env') && steps.push(env); + } + + ctx.debug && ctx.logger.debug('Running steps...'); + + // No steps? Exit + if (steps.length === 0) { + ctx.p.log.error('No steps selected, exiting...'); + ctx.exit(0); + } + + // Run steps + for (const step of steps) { + await step(ctx); + } + + ctx.debug && ctx.logger.debug('Running tasks...'); + + // No tasks? Exit + if (ctx.tasks.length === 0) { + ctx.p.log.error('No tasks selected, exiting...'); + ctx.exit(0); + } + + // Run tasks + await ctx.p.tasks(ctx.tasks); + + ctx.debug && ctx.logger.debug('Running next steps...'); + + // Run next steps + await next(ctx); + + ctx.debug && ctx.logger.debug('Interactive CLI completed, exiting...'); + + // All done, exit + ctx.exit(0); +} diff --git a/packages/studiocms/src/cli/cmds/init/steps/data/studiocmsEnv.ts b/packages/studiocms/src/cli/cmds/init/steps/data/studiocmsEnv.ts new file mode 100644 index 0000000000..06961617bd --- /dev/null +++ b/packages/studiocms/src/cli/cmds/init/steps/data/studiocmsEnv.ts @@ -0,0 +1,70 @@ +import type { EnvBuilderOptions } from '../envBuilder.js'; + +export const ExampleEnv: string = `# StudioCMS Environment Variables + +# libSQL URL and Token for AstroDB +ASTRO_DB_REMOTE_URL=libsql://your-database.turso.io +ASTRO_DB_APP_TOKEN= + +# Auth encryption key +CMS_ENCRYPTION_KEY="..." # openssl rand --base64 16 + +# credentials for GitHub OAuth +CMS_GITHUB_CLIENT_ID= +CMS_GITHUB_CLIENT_SECRET= +CMS_GITHUB_REDIRECT_URI=http://localhost:4321/studiocms_api/auth/github/callback + +# credentials for Discord OAuth +CMS_DISCORD_CLIENT_ID= +CMS_DISCORD_CLIENT_SECRET= +CMS_DISCORD_REDIRECT_URI=http://localhost:4321/studiocms_api/auth/discord/callback + +# credentials for Google OAuth +CMS_GOOGLE_CLIENT_ID= +CMS_GOOGLE_CLIENT_SECRET= +CMS_GOOGLE_REDIRECT_URI=http://localhost:4321/studiocms_api/auth/google/callback + +# credentials for auth0 OAuth +CMS_AUTH0_CLIENT_ID= +CMS_AUTH0_CLIENT_SECRET= +CMS_AUTH0_DOMAIN= +CMS_AUTH0_REDIRECT_URI=http://localhost:4321/studiocms_api/auth/auth0/callback + +## Cloudinary Javascript SDK +CMS_CLOUDINARY_CLOUDNAME="demo"`; + +export function buildEnvFile(envBuilderOpts: EnvBuilderOptions): string { + return `# StudioCMS Environment Variables + +# libSQL URL and Token for AstroDB +ASTRO_DB_REMOTE_URL=${envBuilderOpts.astroDbRemoteUrl} +ASTRO_DB_APP_TOKEN=${envBuilderOpts.astroDbToken} + +# Auth encryption key +CMS_ENCRYPTION_KEY="${envBuilderOpts.encryptionKey}" # openssl rand --base64 16 + +# credentials for GitHub OAuth +CMS_GITHUB_CLIENT_ID=${envBuilderOpts.githubOAuth?.clientId} +CMS_GITHUB_CLIENT_SECRET=${envBuilderOpts.githubOAuth?.clientSecret} +CMS_GITHUB_REDIRECT_URI=${envBuilderOpts.githubOAuth?.redirectUri}/studiocms_api/auth/github/callback + +# credentials for Discord OAuth +CMS_DISCORD_CLIENT_ID=${envBuilderOpts.discordOAuth?.clientId} +CMS_DISCORD_CLIENT_SECRET=${envBuilderOpts.discordOAuth?.clientSecret} +CMS_DISCORD_REDIRECT_URI=${envBuilderOpts.discordOAuth?.redirectUri}/studiocms_api/auth/discord/callback + +# credentials for Google OAuth +CMS_GOOGLE_CLIENT_ID=${envBuilderOpts.googleOAuth?.clientId} +CMS_GOOGLE_CLIENT_SECRET=${envBuilderOpts.googleOAuth?.clientSecret} +CMS_GOOGLE_REDIRECT_URI=${envBuilderOpts.googleOAuth?.redirectUri}/studiocms_api/auth/google/callback + +# credentials for auth0 OAuth +CMS_AUTH0_CLIENT_ID=${envBuilderOpts.auth0OAuth?.clientId} +CMS_AUTH0_CLIENT_SECRET=${envBuilderOpts.auth0OAuth?.clientSecret} +CMS_AUTH0_DOMAIN=${envBuilderOpts.auth0OAuth?.domain} +CMS_AUTH0_REDIRECT_URI=${envBuilderOpts.auth0OAuth?.redirectUri}/studiocms_api/auth/auth0/callback + +## Cloudinary Javascript SDK +CMS_CLOUDINARY_CLOUDNAME="demo" +`; +} diff --git a/packages/studiocms/src/cli/cmds/init/steps/envBuilder.ts b/packages/studiocms/src/cli/cmds/init/steps/envBuilder.ts new file mode 100644 index 0000000000..bd393ef559 --- /dev/null +++ b/packages/studiocms/src/cli/cmds/init/steps/envBuilder.ts @@ -0,0 +1,473 @@ +import crypto from 'node:crypto'; +import fs from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import color from 'chalk'; +import type { Context } from '../../../lib/context.js'; +import { commandExists, runInteractiveCommand, runShellCommand } from '../../../lib/runExternal.js'; +import { + StudioCMSColorwayError, + StudioCMSColorwayInfo, + StudioCMSColorwayWarnBg, + TursoColorway, + exists, + label, +} from '../../../lib/utils.js'; +import { ExampleEnv, buildEnvFile } from './data/studiocmsEnv.js'; + +interface GenericOAuth { + clientId: string; + clientSecret: string; + redirectUri: string; +} + +interface Auth0OAuth extends GenericOAuth { + domain: string; +} + +export interface EnvBuilderOptions { + astroDbRemoteUrl?: string; + astroDbToken?: string; + encryptionKey?: string; + oAuthOptions?: ('github' | 'discord' | 'google' | 'auth0')[]; + githubOAuth?: GenericOAuth; + discordOAuth?: GenericOAuth; + googleOAuth?: GenericOAuth; + auth0OAuth?: Auth0OAuth; +} + +export async function env( + ctx: Pick< + Context, + 'cwd' | 'p' | 'pCancel' | 'pOnCancel' | 'dryRun' | 'tasks' | 'exit' | 'debug' | 'logger' + > +) { + ctx.debug && ctx.logger.debug('Running env...'); + let _env = false; + let envFileContent: string; + + const envExists = exists(path.join(ctx.cwd, '.env')); + + ctx.debug && ctx.logger.debug(`Environment file exists: ${envExists}`); + + if (envExists) { + ctx.p.log.warn( + `${label('Warning', StudioCMSColorwayWarnBg, color.black)} An environment file already exists. Would you like to overwrite it?` + ); + + const check = await ctx.p.confirm({ + message: 'Confirm Overwrite', + }); + + if (typeof check === 'symbol') { + ctx.pCancel(check); + } else { + ctx.debug && ctx.logger.debug(`Environment file overwrite selected: ${check}`); + if (!check) { + return; + } + } + } + + const EnvPrompt = await ctx.p.select({ + message: 'What kind of environment file would you like to create?', + options: [ + { value: 'builder', label: 'Use Interactive .env Builder' }, + { value: 'example', label: 'Use the Example .env file' }, + { value: 'none', label: 'Skip Environment File Creation', hint: 'Cancel' }, + ], + }); + + if (typeof EnvPrompt === 'symbol') { + ctx.pCancel(EnvPrompt); + } else { + ctx.debug && ctx.logger.debug(`Environment file type selected: ${EnvPrompt}`); + + _env = EnvPrompt !== 'none'; + } + + if (EnvPrompt === 'example') { + envFileContent = ExampleEnv; + } else if (EnvPrompt === 'builder') { + let envBuilderOpts: EnvBuilderOptions = {}; + + const isWindows = os.platform() === 'win32'; + + if (isWindows) { + ctx.p.log.warn( + `${label('Warning', StudioCMSColorwayWarnBg, color.black)} Turso DB CLI is not supported on Windows outside of WSL.` + ); + } + + let tursoDB: symbol | 'yes' | 'no' = 'no'; + + if (!isWindows) { + tursoDB = await ctx.p.select({ + message: 'Would you like us to setup a new Turso DB for you? (Runs `turso db create`)', + options: [ + { value: 'yes', label: 'Yes' }, + { value: 'no', label: 'No' }, + ], + }); + } + + if (typeof tursoDB === 'symbol') { + ctx.pCancel(tursoDB); + } else { + ctx.debug && ctx.logger.debug(`AstroDB setup selected: ${tursoDB}`); + + if (tursoDB === 'yes') { + if (!commandExists('turso')) { + ctx.p.log.error(StudioCMSColorwayError('Turso CLI is not installed.')); + + const installTurso = await ctx.p.confirm({ + message: 'Would you like to install Turso CLI now?', + }); + + if (typeof installTurso === 'symbol') { + ctx.pCancel(installTurso); + } else { + if (installTurso) { + try { + await runInteractiveCommand('curl -sSfL https://get.tur.so/install.sh | bash'); + console.log('Command completed successfully.'); + } catch (error) { + console.error(`Failed to run Turso install: ${(error as Error).message}`); + } + } else { + ctx.p.log.warn( + `${label('Warning', StudioCMSColorwayWarnBg, color.black)} You will need to setup your own AstroDB and provide the URL and Token.` + ); + } + } + } + + try { + const res = await runShellCommand('turso auth login --headless'); + + if ( + !res.includes('Already signed in as') && + !res.includes('Success! Existing JWT still valid') + ) { + ctx.p.log.message(`Please sign in to Turso to continue.\n${res}`); + + const loginToken = await ctx.p.text({ + message: 'Enter the login token ( the code within the " " )', + placeholder: 'eyJhb...tnPnw', + }); + + if (typeof loginToken === 'symbol') { + ctx.pCancel(loginToken); + } else { + const loginRes = await runShellCommand(`turso config set token "${loginToken}"`); + + if (loginRes.includes('Token set succesfully.')) { + ctx.p.log.success('Successfully logged in to Turso.'); + } else { + ctx.p.log.error(StudioCMSColorwayError('Unable to login to Turso.')); + process.exit(1); + } + } + } + } catch (error) { + if (error instanceof Error) { + ctx.p.log.error(StudioCMSColorwayError(`Error: ${error.message}`)); + process.exit(1); + } else { + ctx.p.log.error(StudioCMSColorwayError('Unknown Error: Unable to login to Turso.')); + process.exit(1); + } + } + + const customName = await ctx.p.confirm({ + message: 'Would you like to provide a custom name for the database?', + initialValue: false, + }); + + if (typeof customName === 'symbol') { + ctx.pCancel(customName); + } else { + const dbName = customName + ? await ctx.p.text({ + message: 'Enter a custom name for the database', + initialValue: 'your-database-name', + }) + : undefined; + + if (typeof dbName === 'symbol') { + ctx.pCancel(dbName); + } else { + ctx.debug && ctx.logger.debug(`Custom database name: ${dbName}`); + + const tursoSetup = ctx.p.spinner(); + tursoSetup.start( + `${label('Turso', TursoColorway, color.black)} Setting up Turso DB...` + ); + try { + tursoSetup.message( + `${label('Turso', TursoColorway, color.black)} Creating Database...` + ); + const createRes = await runShellCommand(`turso db create ${dbName ? dbName : ''}`); + + const dbNameMatch = createRes.match(/^Created database (\S+) at group/m); + + const dbFinalName = dbNameMatch ? dbNameMatch[1] : undefined; + + tursoSetup.message( + `${label('Turso', TursoColorway, color.black)} Retrieving database information...` + ); + ctx.debug && ctx.logger.debug(`Database name: ${dbFinalName}`); + + const showCMD = `turso db show ${dbFinalName}`; + const tokenCMD = `turso db tokens create ${dbFinalName}`; + + const showRes = await runShellCommand(showCMD); + + const urlMatch = showRes.match(/^URL:\s+(\S+)/m); + + const dbURL = urlMatch ? urlMatch[1] : undefined; + + ctx.debug && ctx.logger.debug(`Database URL: ${dbURL}`); + + const tokenRes = await runShellCommand(tokenCMD); + + const dbToken = tokenRes.trim(); + + ctx.debug && ctx.logger.debug(`Database Token: ${dbToken}`); + + envBuilderOpts.astroDbRemoteUrl = dbURL; + envBuilderOpts.astroDbToken = dbToken; + + tursoSetup.stop( + `${label('Turso', TursoColorway, color.black)} Database setup complete. New Database: ${dbFinalName}` + ); + ctx.p.log.message('Database Token and Url saved to environment file.'); + } catch (e) { + tursoSetup.stop(); + if (e instanceof Error) { + ctx.p.log.error(StudioCMSColorwayError(`Error: ${e.message}`)); + process.exit(1); + } else { + ctx.p.log.error( + StudioCMSColorwayError('Unknown Error: Unable to create database.') + ); + process.exit(1); + } + } + } + } + } else { + ctx.p.log.warn( + `${label('Warning', StudioCMSColorwayWarnBg, color.black)} You will need to setup your own AstroDB and provide the URL and Token.` + ); + const envBuilderStep_AstroDB = await ctx.p.group( + { + astroDbRemoteUrl: () => + ctx.p.text({ + message: 'Remote URL for AstroDB', + initialValue: 'libsql://your-database.turso.io', + }), + astroDbToken: () => + ctx.p.text({ + message: 'AstroDB Token', + initialValue: 'your-astrodb-token', + }), + }, + { + onCancel: () => ctx.pOnCancel(), + } + ); + + ctx.debug && ctx.logger.debug(`AstroDB setup: ${envBuilderStep_AstroDB}`); + + envBuilderOpts = { ...envBuilderStep_AstroDB }; + } + } + + const envBuilderStep1 = await ctx.p.group( + { + encryptionKey: () => + ctx.p.text({ + message: 'StudioCMS Auth Encryption Key', + initialValue: crypto.randomBytes(16).toString('base64'), + }), + oAuthOptions: () => + ctx.p.multiselect({ + message: 'Setup OAuth Providers', + options: [ + { value: 'github', label: 'GitHub' }, + { value: 'discord', label: 'Discord' }, + { value: 'google', label: 'Google' }, + { value: 'auth0', label: 'Auth0' }, + ], + required: false, + }), + }, + { + // On Cancel callback that wraps the group + // So if the user cancels one of the prompts in the group this function will be called + onCancel: () => ctx.pOnCancel(), + } + ); + + ctx.debug && ctx.logger.debug(`Environment Builder Step 1: ${envBuilderStep1}`); + + envBuilderOpts = { ...envBuilderStep1 }; + + if (envBuilderStep1.oAuthOptions.includes('github')) { + const githubOAuth = await ctx.p.group( + { + clientId: () => + ctx.p.text({ + message: 'GitHub Client ID', + initialValue: 'your-github-client-id', + }), + clientSecret: () => + ctx.p.text({ + message: 'GitHub Client Secret', + initialValue: 'your-github-client-secret', + }), + redirectUri: () => + ctx.p.text({ + message: 'GitHub Redirect URI Domain', + initialValue: 'http://localhost:4321', + }), + }, + { + onCancel: () => ctx.pOnCancel(), + } + ); + + ctx.debug && ctx.logger.debug(`GitHub OAuth: ${githubOAuth}`); + + envBuilderOpts.githubOAuth = githubOAuth; + } + + if (envBuilderStep1.oAuthOptions.includes('discord')) { + const discordOAuth = await ctx.p.group( + { + clientId: () => + ctx.p.text({ + message: 'Discord Client ID', + initialValue: 'your-discord-client-id', + }), + clientSecret: () => + ctx.p.text({ + message: 'Discord Client Secret', + initialValue: 'your-discord-client-secret', + }), + redirectUri: () => + ctx.p.text({ + message: 'Discord Redirect URI Domain', + initialValue: 'http://localhost:4321', + }), + }, + { + onCancel: () => ctx.pOnCancel(), + } + ); + + ctx.debug && ctx.logger.debug(`Discord OAuth: ${discordOAuth}`); + + envBuilderOpts.discordOAuth = discordOAuth; + } + + if (envBuilderStep1.oAuthOptions.includes('google')) { + const googleOAuth = await ctx.p.group( + { + clientId: () => + ctx.p.text({ + message: 'Google Client ID', + initialValue: 'your-google-client-id', + }), + clientSecret: () => + ctx.p.text({ + message: 'Google Client Secret', + initialValue: 'your-google-client-secret', + }), + redirectUri: () => + ctx.p.text({ + message: 'Google Redirect URI Domain', + initialValue: 'http://localhost:4321', + }), + }, + { + onCancel: () => ctx.pOnCancel(), + } + ); + + ctx.debug && ctx.logger.debug(`Google OAuth: ${googleOAuth}`); + + envBuilderOpts.googleOAuth = googleOAuth; + } + + if (envBuilderStep1.oAuthOptions.includes('auth0')) { + const auth0OAuth = await ctx.p.group( + { + clientId: () => + ctx.p.text({ + message: 'Auth0 Client ID', + initialValue: 'your-auth0-client-id', + }), + clientSecret: () => + ctx.p.text({ + message: 'Auth0 Client Secret', + initialValue: 'your-auth0-client-secret', + }), + domain: () => + ctx.p.text({ + message: 'Auth0 Domain', + initialValue: 'your-auth0-domain', + }), + redirectUri: () => + ctx.p.text({ + message: 'Auth0 Redirect URI Domain', + initialValue: 'http://localhost:4321', + }), + }, + { + onCancel: () => ctx.pOnCancel(), + } + ); + + ctx.debug && ctx.logger.debug(`Auth0 OAuth: ${auth0OAuth}`); + + envBuilderOpts.auth0OAuth = auth0OAuth; + } + + envFileContent = buildEnvFile(envBuilderOpts); + } + + if (ctx.dryRun) { + ctx.tasks.push({ + title: `${StudioCMSColorwayInfo.bold('--dry-run')} ${color.dim('Skipping environment file creation')}`, + task: async (message) => { + message('Creating environment file... (skipped)'); + }, + }); + } else if (_env) { + ctx.tasks.push({ + title: color.dim('Creating environment file...'), + task: async (message) => { + try { + await fs.writeFile(path.join(ctx.cwd, '.env'), envFileContent, { + encoding: 'utf-8', + }); + message('Environment file created'); + } catch (e) { + if (e instanceof Error) { + ctx.p.log.error(StudioCMSColorwayError(`Error: ${e.message}`)); + process.exit(1); + } else { + ctx.p.log.error( + StudioCMSColorwayError('Unknown Error: Unable to create environment file.') + ); + process.exit(1); + } + } + }, + }); + } + + ctx.debug && ctx.logger.debug('Environment complete'); +} diff --git a/packages/studiocms/src/cli/cmds/init/steps/nextSteps.ts b/packages/studiocms/src/cli/cmds/init/steps/nextSteps.ts new file mode 100644 index 0000000000..78f1e30084 --- /dev/null +++ b/packages/studiocms/src/cli/cmds/init/steps/nextSteps.ts @@ -0,0 +1,45 @@ +import chalk from 'chalk'; +import type { Context } from '../../../lib/context.js'; +import { + StudioCMSColorway, + StudioCMSColorwayBg, + StudioCMSColorwayInfoBg, + boxen, + label, +} from '../../../lib/utils.js'; + +export async function next( + ctx: Pick +) { + const commandMap: { [key: string]: string } = { + npm: 'npm run dev', + bun: 'bun run dev', + yarn: 'yarn dev', + pnpm: 'pnpm dev', + }; + + const devCmd = commandMap[ctx.packageManager as keyof typeof commandMap] || 'npm run dev'; + + ctx.debug && ctx.logger.debug(`Dev command: ${devCmd}`); + + ctx.debug && ctx.logger.debug('Running next steps fn...'); + + ctx.p.log.success( + boxen( + chalk.bold( + `${label('Init Complete!', StudioCMSColorwayInfoBg, chalk.bold)} Get started with StudioCMS:` + ), + { + ln1: `Ensure your ${chalk.cyanBright('.env')} file is configured correctly.`, + ln3: `Run ${chalk.cyan('astro db push')} to sync your database schema.`, + ln4: `Run ${chalk.cyan(devCmd)} to start the dev server. ${chalk.cyanBright('CTRL+C')} to stop.`, + } + ) + ); + + ctx.p.outro( + `${label('Enjoy your new CMS!', StudioCMSColorwayBg, chalk.bold)} Stuck? Join us on Discord at ${StudioCMSColorway.bold.underline('https://chat.studiocms.dev')}` + ); + + ctx.debug && ctx.logger.debug('Next steps complete'); +} diff --git a/packages/studiocms/src/cli/index.ts b/packages/studiocms/src/cli/index.ts new file mode 100644 index 0000000000..0a85d03455 --- /dev/null +++ b/packages/studiocms/src/cli/index.ts @@ -0,0 +1,28 @@ +import { Option } from '@commander-js/extra-typings'; +import { Command } from './lib/commander.js'; +import pathUtil from './lib/pathUtil.js'; +import { CLITitle, readJson } from './lib/utils.js'; + +const pkgJson = readJson<{ version: string }>(new URL('../../package.json', import.meta.url)); + +const { getRelPath } = pathUtil(import.meta.url); + +await new Command('studiocms') + .description('StudioCMS CLI Utility Toolkit') + .version(pkgJson.version) + .addHelpText('beforeAll', CLITitle) + .showHelpAfterError('(add --help for additional information)') + .enablePositionalOptions(true) + .executableDir(getRelPath('cmds')) + .helpOption('-h, --help', 'Display help for command.') + .addOption(new Option('--color', 'Force color output')) // implemented by chalk + .addOption(new Option('--no-color', 'Disable color output')) // implemented by chalk + + // Commands + .command('init', 'Initialize the StudioCMS project after new installation.', { + executableFile: 'init.js', + }) + .command('get-turso', 'Turso CLI Utilities', { executableFile: 'get-turso.js' }) + + // Parse the command line arguments + .parseAsync(); diff --git a/packages/studiocms/src/cli/lib/commander.ts b/packages/studiocms/src/cli/lib/commander.ts new file mode 100644 index 0000000000..6b480c079a --- /dev/null +++ b/packages/studiocms/src/cli/lib/commander.ts @@ -0,0 +1,107 @@ +import { + type CommandUnknownOpts, + type OutputConfiguration, + Command as _Command, + Help as _Help, +} from '@commander-js/extra-typings'; +import { type ChalkInstance, chalkStderr as chalkStdErr, default as chalkStdOut } from 'chalk'; +import stripAnsi from 'strip-ansi'; +import wrapAnsi from 'wrap-ansi'; +import { StudioCMSColorwayError, date, supportsColor } from './utils.js'; + +export class Help extends _Help { + chalk: ChalkInstance; + colorway: ChalkInstance; + colorwayError: ChalkInstance = StudioCMSColorwayError; + sortOptions = true; + sortSubcommands = true; + showGlobalOptions = true; + subcommandTerm = (cmd: CommandUnknownOpts) => + cmd.name() === 'interactive' + ? `${this.colorway(cmd.name())}${this.colorwayError('*')}` + : this.colorway(cmd.name()); + subcommandDescription = (cmd: CommandUnknownOpts) => { + const desc = cmd.summary() || cmd.description(); + return desc; + }; + + constructor() { + super(); + this.chalk = chalkStdOut; + this.colorway = this.chalk.hex('#a581f3'); + } + + prepareContext(contextOptions: { + error?: boolean; + helpWidth?: number; + outputHasColors?: boolean; + }) { + super.prepareContext(contextOptions); + if (contextOptions?.error) { + this.chalk = chalkStdErr; + } + } + + displayWidth(str: string) { + return stripAnsi(str).length; // use imported package + } + + boxWrap(str: string, width: number) { + return wrapAnsi(str, width, { hard: true }); // use imported package + } + + styleTitle(str: string) { + return this.chalk.bold(str); + } + styleCommandText(str: string) { + return this.chalk.cyan(str); + } + styleCommandDescription(str: string) { + return this.chalk.magenta(str); + } + styleDescriptionText(str: string) { + return this.chalk.italic(str); + } + styleOptionText(str: string) { + return this.chalk.green(str); + } + styleArgumentText(str: string) { + return this.chalk.yellow(str); + } + styleSubcommandText(str: string) { + return str; + } +} + +export class Command extends _Command { + chalk: ChalkInstance = chalkStdOut; + colorwayError = StudioCMSColorwayError; + supportsColor = supportsColor; + max = process.stdout.columns; + prefix = this.max < 80 ? ' ' : ' '.repeat(2); + + _outputConfiguration: OutputConfiguration | undefined = { + writeOut: (str) => process.stdout.write(str), + writeErr: (str) => process.stdout.write(`ERROR [${date}]: ${str}`), + // Output errors in red. + outputError: (str, write) => + write(`${this.chalk.red.bold(`ERROR [${date}]:`)} ${this.chalk.red(str)}`), + getOutHelpWidth: () => (process.stdout.isTTY ? (process.stdout.columns ?? 80) : 80), + getErrHelpWidth: () => (process.stderr.isTTY ? (process.stderr.columns ?? 80) : 80), + getOutHasColors: () => this.supportsColor, + getErrHasColors: () => this.supportsColor, + stripColor: (str) => stripAnsi(str), + }; + + createCommand(name: string | undefined) { + return new Command(name); + } + + createHelp() { + return Object.assign(new Help(), this.configureHelp()); + } +} + +export type newInstanceCommand = InstanceType; + +export type instanceCommand = InstanceType; diff --git a/packages/studiocms/src/cli/lib/context.ts b/packages/studiocms/src/cli/lib/context.ts new file mode 100644 index 0000000000..98e91481e0 --- /dev/null +++ b/packages/studiocms/src/cli/lib/context.ts @@ -0,0 +1,83 @@ +import os from 'node:os'; +import * as p from '@clack/prompts'; +import colors from 'chalk'; +import getSeasonalMessages from './seasonal.js'; +import { cancelMessage, getName, logger, readJson } from './utils.js'; + +const pkgJson = readJson<{ version: string }>(new URL('../../../package.json', import.meta.url)); + +interface BaseContext { + p: typeof p; + c: typeof colors; + pCancel(val: symbol): void; + pOnCancel(): void; + cwd: string; + packageManager: string; + username: string; + version: string; + exit(code: number): never; + tasks: p.Task[]; + logger: typeof logger; + welcome: string; +} + +interface InteractiveOptions { + dryRun?: boolean; + skipBanners?: boolean; + debug?: boolean; +} + +export interface Context extends BaseContext, InteractiveOptions {} + +export async function getContext(args: InteractiveOptions): Promise { + let { debug, dryRun, skipBanners } = args; + + const packageManager = detectPackageManager() ?? 'npm'; + const cwd = process.cwd(); + + const { messages } = getSeasonalMessages(); + + skipBanners = !!((os.platform() === 'win32' || skipBanners) && !process.env.CI); + + const context: Context = { + p: p, + c: colors, + pCancel(val: symbol) { + p.isCancel(val); + p.cancel(cancelMessage); + process.exit(0); + }, + pOnCancel() { + p.cancel(cancelMessage); + process.exit(0); + }, + packageManager, + username: await getName(), + version: pkgJson.version, + welcome: random(messages), + dryRun, + debug, + cwd, + skipBanners, + exit(code) { + process.exit(code); + }, + tasks: [], + logger: logger, + }; + + return context; +} + +// biome-ignore lint/suspicious/noExplicitAny: +export const random = (...arr: any[]) => { + const flattenedArray = arr.flat(1); + return flattenedArray[Math.floor(flattenedArray.length * Math.random())]; +}; + +export function detectPackageManager() { + if (!process.env.npm_config_user_agent) return; + const specifier = process.env.npm_config_user_agent.split(' ')[0]; + const name = specifier.substring(0, specifier.lastIndexOf('/')); + return name === 'npminstall' ? 'cnpm' : name; +} diff --git a/packages/studiocms/src/cli/lib/pathUtil.ts b/packages/studiocms/src/cli/lib/pathUtil.ts new file mode 100644 index 0000000000..6534dd2af0 --- /dev/null +++ b/packages/studiocms/src/cli/lib/pathUtil.ts @@ -0,0 +1,13 @@ +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +export default function pathUtil(importMetaUrl: string) { + const filename = fileURLToPath(importMetaUrl); + const dirname = path.dirname(filename); + + return { + filename, + dirname, + getRelPath: (...url: string[]) => path.join(dirname, ...url), + }; +} diff --git a/packages/studiocms/src/cli/lib/runExternal.ts b/packages/studiocms/src/cli/lib/runExternal.ts new file mode 100644 index 0000000000..8f9a01c36c --- /dev/null +++ b/packages/studiocms/src/cli/lib/runExternal.ts @@ -0,0 +1,87 @@ +import { type SpawnOptions, exec, spawn, spawnSync } from 'node:child_process'; + +/** + * Check if a command exists on the system. + * @param command The command to check. + * @returns A boolean indicating if the command exists. + */ +export function commandExists(command: string): boolean { + const result = spawnSync(command, ['--version'], { + stdio: 'ignore', + shell: true, + }); + return result.status === 0; +} + +// // Example usage +// const command = 'turso'; + +// if (commandExists(command)) { +// console.log(`${command} exists on the system.`); +// } else { +// console.log(`${command} does not exist on the system.`); +// } + +/** + * Run a shell command. + * @param command The full shell command to execute. + * @returns A Promise that resolves with the command's output or rejects with an error. + */ +export function runShellCommand(command: string): Promise { + return new Promise((resolve, reject) => { + exec(command, (error, stdout, stderr) => { + if (error) { + reject(new Error(`Error: ${error.message}\n${stderr}`)); + return; + } + resolve(stdout); + }); + }); +} + +// // Example usage +// (async () => { +// try { +// const output = await runShellCommand('curl -sSfL https://get.tur.so/install.sh | bash'); +// console.log(`Command output: ${output}`); +// } catch (error) { +// console.error(`Failed to run command: ${(error as Error).message}`); +// } +// })(); + +/** + * Run a shell command interactively. + * @param command The shell command to execute. + * @param options Optional spawn options. + * @returns A Promise that resolves when the command completes or rejects on error. + */ +export function runInteractiveCommand( + command: string, + options: SpawnOptions = { shell: true, stdio: 'inherit' } +): Promise { + return new Promise((resolve, reject) => { + const process = spawn(command, [], options); + + process.on('close', (code: number) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`Command exited with code ${code}`)); + } + }); + + process.on('error', (error) => { + reject(error); + }); + }); +} + +// // Example usage +// (async () => { +// try { +// await runInteractiveCommand('curl -sSfL https://get.tur.so/install.sh | bash'); +// console.log('Command completed successfully.'); +// } catch (error) { +// console.error(`Failed to run command: ${(error as Error).message}`); +// } +// })(); diff --git a/packages/studiocms/src/cli/lib/seasonal.ts b/packages/studiocms/src/cli/lib/seasonal.ts new file mode 100644 index 0000000000..0f1b2eb58f --- /dev/null +++ b/packages/studiocms/src/cli/lib/seasonal.ts @@ -0,0 +1,92 @@ +interface SeasonalMessages { + messages: string[]; +} + +export default function getSeasonalMessages(): SeasonalMessages { + const season = getSeason(); + switch (season) { + case 'new-year': { + const year = new Date().getFullYear(); + return { + messages: [ + `Welcome to StudioCMS! Let's create something amazing.`, + `Kicking off ${year} with StudioCMS? You're in for a treat!`, + `Happy ${year}! Ready to craft the perfect CMS?`, + `${year} is your year to create something awesome with StudioCMS!`, + `${year} is the year of StudioCMS-powered sites!`, + `Excited to see what you'll build with StudioCMS in ${year}!`, + `Thanks for choosing StudioCMS to start your ${year} journey!`, + ], + }; + } + case 'spooky': + return { + messages: [ + `Beware! You're entering the realm of StudioCMS.`, + 'Boo! Ready to create something frightfully good?', + `Let's brew up a spooktacular website together!`, + 'No tricks, just treats with StudioCMS this Halloween.', + 'Spiders spin webs, but you build the internet with StudioCMS!', + 'StudioCMS: your cauldron for conjuring up the perfect site.', + 'A hauntingly beautiful project awaits you!', + 'The only thing spooky here is how good your site will look!', + 'Prepare for chills and thrills as we craft your CMS masterpiece!', + 'StudioCMS: Helping you scare up a perfect website this Halloween!', + ], + }; + case 'holiday': + return { + messages: [ + `'Tis the season to build with StudioCMS!`, + 'Jingle all the way to your next great website!', + 'Deck the web with StudioCMS magic!', + `Let's unwrap the gift of creativity together!`, + 'May your holidays be merry and your site extraordinary!', + 'Building a website is the best kind of holiday cheer!', + 'StudioCMS: the perfect companion for your festive projects!', + 'Create a winter wonderland online with StudioCMS.', + `Ho ho ho! Let's build something unforgettable this holiday season!`, + 'Your ideas are the brightest stars on this holiday tree!', + ], + }; + default: + return { + messages: [ + `Welcome to StudioCMS! Let's get started.`, + 'Ready to shape the future of the web with StudioCMS?', + 'Claim your digital space with StudioCMS!', + 'Time to turn your ideas into reality.', + `Let's craft something amazing together!`, + `Let's make the internet a better place.`, + 'Your creativity + StudioCMS = magic.', + `We're thrilled to have you on this journey.`, + 'Building the web, one project at a time.', + `The web is yours to shape. Let's begin!`, + 'Fast, flexible, and ready to go—just like StudioCMS.', + `Let's build the CMS of your dreams!`, + `Let's make your vision a reality.`, + 'Ready to create something extraordinary?', + 'Here to assist you in building greatness.', + `It's time to create your next masterpiece.`, + 'StudioCMS: The power of the web in your hands.', + ], + }; + } +} + +type Season = 'spooky' | 'holiday' | 'new-year'; +function getSeason(): Season | undefined { + const date = new Date(); + const month = date.getMonth() + 1; + const day = date.getDate() + 1; + + if (month === 1 && day <= 7) { + return 'new-year'; + } + if (month === 10 && day > 7) { + return 'spooky'; + } + if (month === 12 && day > 7 && day < 25) { + return 'holiday'; + } +} diff --git a/packages/studiocms/src/cli/lib/utils.ts b/packages/studiocms/src/cli/lib/utils.ts new file mode 100644 index 0000000000..52ff6385a8 --- /dev/null +++ b/packages/studiocms/src/cli/lib/utils.ts @@ -0,0 +1,346 @@ +import { exec } from 'node:child_process'; +import fs from 'node:fs'; +import type { Key } from 'node:readline'; +import readline from 'node:readline'; +import type { outro as _outro } from '@clack/prompts'; +import ansiEscapes from 'ansi-escapes'; +import _boxen, { type Options as BoxenOptions } from 'boxen'; +import chalk from 'chalk'; +import cliCursor from 'cli-cursor'; +import figlet from 'figlet'; +import isUnicodeSupported from 'is-unicode-supported'; +import sliceAnsi from 'slice-ansi'; +import stripAnsi from 'strip-ansi'; +import wrapAnsi from 'wrap-ansi'; + +export const ASCIIText = figlet.textSync('StudioCMS'); + +export const dt = new Intl.DateTimeFormat('en-us', { + hour: '2-digit', + minute: '2-digit', +}); + +export const date = dt.format(new Date()); + +export const supportsColor = chalk.level > 0; + +export const StudioCMSColorway = chalk.hex('#a581f3'); +export const StudioCMSColorwayBg = chalk.bgHex('#a581f3'); +export const StudioCMSColorwayInfo = chalk.hex('#22c55e'); +export const StudioCMSColorwayInfoBg = chalk.bgHex('#22c55e'); +export const StudioCMSColorwayWarn = chalk.hex('#facc14'); +export const StudioCMSColorwayWarnBg = chalk.bgHex('#facc14'); +export const StudioCMSColorwayError = chalk.hex('#bd0249'); +export const StudioCMSColorwayErrorBg = chalk.bgHex('#bd0249'); + +export const TursoColorway = chalk.bgHex('#4ff8d2'); + +export const CLITitle = supportsColor ? StudioCMSColorway.bold(`${ASCIIText}\n`) : `${ASCIIText}\n`; + +let stdout = process.stdout; + +const stdin = process.stdin; + +/** @internal Used to mock `process.stdout.write` for testing purposes */ +export function setStdout(writable: typeof process.stdout) { + stdout = writable; +} + +export const action = (key: Key, isSelect: boolean) => { + if (key.meta && key.name !== 'escape') return; + + if (key.ctrl) { + if (key.name === 'a') return 'first'; + if (key.name === 'c') return 'abort'; + if (key.name === 'd') return 'abort'; + if (key.name === 'e') return 'last'; + if (key.name === 'g') return 'reset'; + } + + if (isSelect) { + if (key.name === 'j') return 'down'; + if (key.name === 'k') return 'up'; + if (key.ctrl && key.name === 'n') return 'down'; + if (key.ctrl && key.name === 'p') return 'up'; + } + + if (key.name === 'return') return 'submit'; + if (key.name === 'enter') return 'submit'; // ctrl + J + if (key.name === 'backspace') return 'delete'; + if (key.name === 'delete') return 'deleteForward'; + if (key.name === 'abort') return 'abort'; + if (key.name === 'escape') return 'exit'; + if (key.name === 'tab') return 'next'; + if (key.name === 'pagedown') return 'nextPage'; + if (key.name === 'pageup') return 'prevPage'; + if (key.name === 'home') return 'home'; + if (key.name === 'end') return 'end'; + + if (key.name === 'up') return 'up'; + if (key.name === 'down') return 'down'; + if (key.name === 'right') return 'right'; + if (key.name === 'left') return 'left'; + + return false; +}; + +const unicode = isUnicodeSupported(); +const s = (c: string, fallback: string) => (unicode ? c : fallback); +const S_BAR = s('│', '|'); + +export type LogMessageOptions = { + symbol?: string; +}; + +const defaultTerminalHeight = 24; +const getWidth = ({ columns = 80 }) => columns; +const fitToTerminalHeight = (stream: typeof stdout, text: string) => { + const terminalHeight = stream.rows ?? defaultTerminalHeight; + const lines = text.split('\n'); + const toRemove = Math.max(0, lines.length - terminalHeight); + return toRemove + ? sliceAnsi(text, stripAnsi(lines.slice(0, toRemove).join('\n')).length + 1) + : text; +}; + +export function createClackMessageUpdate( + stream = stdout, + { showCursor = false, symbol = chalk.gray(S_BAR) } = {} +) { + let previousLineCount = 0; + let previousWidth = getWidth(stream); + let previousOutput = ''; + + const reset = () => { + previousOutput = ''; + previousWidth = getWidth(stream); + previousLineCount = 0; + }; + + const render = (arguments_: string) => { + if (!showCursor) { + cliCursor.hide(); + } + + const parts = []; + + const [firstLine, ...lines] = arguments_.split('\n'); + parts.push(`${symbol} ${firstLine}`, ...lines.map((ln) => `${chalk.gray(S_BAR)} ${ln}`)); + + let output = fitToTerminalHeight(stream, `${parts.join('\n')}\n`); + const width = getWidth(stream); + + if (output === previousOutput && previousWidth === width) { + return; + } + + previousOutput = output; + previousWidth = width; + output = wrapAnsi(output, width, { trim: false, hard: true, wordWrap: false }); + + stream.write(ansiEscapes.eraseLines(previousLineCount) + output); + previousLineCount = output.split('\n').length; + }; + + render.clear = () => { + stream.write(ansiEscapes.eraseLines(previousLineCount)); + reset(); + }; + + render.done = () => { + reset(); + if (!showCursor) { + cliCursor.show(); + } + }; + + return render; +} + +export function boxen( + header?: string, + body?: { ln0?: string; ln1?: string; ln2?: string; ln3?: string; ln4?: string; ln5?: string }, + footer?: string, + boxenOptions: BoxenOptions = { padding: 0, borderStyle: 'none' } +) { + const prefix = stdout.columns < 80 ? ' ' : ' '.repeat(4); + const boxContent: string[] = []; + + const logo = _boxen( + [ + `${chalk.white.bold(' ████')}`, + `${chalk.white.bold(' █ ████')}`, + `${chalk.white.bold('█ █▄▄▄ ')}`, + `${chalk.white.bold('█▄▄▄ ')}`, + ].join('\n'), + { + padding: 1, + borderStyle: 'none', + backgroundColor: 'black', + } + ).split('\n'); + + if (header) { + boxContent.push(`${header}\n`); + } + + boxContent.push( + ...[ + `${logo[0]}${prefix}${body?.ln0 || ''}`, + `${logo[1]}${prefix}${body?.ln1 || ''}`, + `${logo[2]}${prefix}${body?.ln2 || ''}`, + `${logo[3]}${prefix}${body?.ln3 || ''}`, + `${logo[4]}${prefix}${body?.ln4 || ''}`, + `${logo[5]}${prefix}${body?.ln5 || ''}`, + ] + ); + + if (footer) { + boxContent.push(`\n${footer}`); + } + + return _boxen(boxContent.join('\n'), boxenOptions); +} + +const send = (message: string) => stdout.write(`${message}\n`); + +export const logger = { + log: (message: string) => { + if (!supportsColor) { + send(`[${date}]: ${message}`); + return; + } + send(`${chalk.blue.bold(`[${date}]:`)} ${message}`); + }, + debug: (message: string) => { + if (!supportsColor) { + send(`DEBUG [${date}]: ${message}`); + return; + } + send(`${chalk.blue.bold(`DEBUG [${date}]:`)} ${message}`); + }, + error: (message: string) => { + if (!supportsColor) { + send(`ERROR [${date}]: ${message}`); + return; + } + send(`${chalk.red.bold(`ERROR [${date}]:`)} ${chalk.red(message)}`); + }, + warn: (message: string) => { + if (!supportsColor) { + send(`WARN [${date}]: ${message}`); + return; + } + send(`${chalk.yellow.bold(`WARN [${date}]:`)} ${chalk.yellow(message)}`); + }, +}; + +export const say = async (msg: string | string[] = [], { clear = false } = {}) => { + const messages = Array.isArray(msg) ? msg : [msg]; + const rl = readline.createInterface({ input: stdin, escapeCodeTimeout: 50 }); + const logUpdate = createClackMessageUpdate(stdout, { showCursor: false }); + readline.emitKeypressEvents(stdin, rl); + let i = 0; + let cancelled = false; + const done = async () => { + stdin.off('keypress', done); + if (stdin.isTTY) stdin.setRawMode(false); + rl.close(); + cancelled = true; + if (i < messages.length - 1) { + logUpdate.clear(); + } else if (clear) { + logUpdate.clear(); + } else { + logUpdate.done(); + } + }; + + if (stdin.isTTY) stdin.setRawMode(true); + stdin.on('keypress', (str, key) => { + if (stdin.isTTY) stdin.setRawMode(true); + const k = action(key, true); + if (k === 'abort') { + done(); + return process.exit(0); + } + // biome-ignore lint/suspicious/noExplicitAny: + if (['up', 'down', 'left', 'right'].includes(k as any)) return; + done(); + }); + + for (let message of messages) { + // biome-ignore lint/correctness/noSelfAssign: + message = message; + const _message = Array.isArray(message) ? message : message.split(' '); + const msg = []; + let j = 0; + for (let word of [''].concat(_message)) { + // biome-ignore lint/correctness/noSelfAssign: + word = word; + if (word) msg.push(word); + logUpdate( + `\n${boxen(undefined, { ln3: msg.join(' ') }, undefined, { padding: 0, borderStyle: 'none' })}` + ); + if (!cancelled) await sleep(randomBetween(75, 200)); + j++; + } + if (!cancelled) await sleep(100); + const tmp = await Promise.all(_message).then((res) => res.join(' ')); + const text = `\n${boxen(undefined, { ln3: tmp }, undefined, { padding: 0, borderStyle: 'none' })}`; + logUpdate(text); + if (!cancelled) await sleep(randomBetween(1200, 1400)); + i++; + } + stdin.off('keypress', done); + await sleep(100); + done(); + if (stdin.isTTY) stdin.setRawMode(false); + stdin.removeAllListeners('keypress'); +}; + +export const randomBetween = (min: number, max: number) => + Math.floor(Math.random() * (max - min + 1) + min); + +// biome-ignore lint/suspicious/noExplicitAny: +export const random = (...arr: any[]) => { + const flattenedArray = arr.flat(1); + return flattenedArray[Math.floor(flattenedArray.length * Math.random())]; +}; + +export const label = (text: string, c = StudioCMSColorwayBg, t = chalk.whiteBright) => + c(` ${t(text)} `); + +export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +export const getName = () => + new Promise((resolve) => { + exec('git config user.name', { encoding: 'utf-8' }, (_1, gitName) => { + if (gitName.trim()) { + return resolve(gitName.split(' ')[0].trim()); + } + exec('whoami', { encoding: 'utf-8' }, (_3, whoami) => { + if (whoami.trim()) { + return resolve(whoami.split(' ')[0].trim()); + } + return resolve('StudioCMS User'); + }); + }); + }); + +export const cancelMessage = + "Operation cancelled, exiting... If you're stuck, join us at https://chat.studiocms.dev"; + +export function readJson(path: string | URL): T { + return JSON.parse(fs.readFileSync(path, 'utf-8')); +} + +export function exists(path: string | URL | undefined) { + if (!path) return false; + try { + fs.statSync(path); + return true; + } catch { + return false; + } +} diff --git a/packages/studiocms/src/cli/shared/intro.ts b/packages/studiocms/src/cli/shared/intro.ts new file mode 100644 index 0000000000..bb7d8c1d65 --- /dev/null +++ b/packages/studiocms/src/cli/shared/intro.ts @@ -0,0 +1,25 @@ +import color from 'chalk'; +import type { Context } from '../lib/context.js'; +import { StudioCMSColorway, StudioCMSColorwayBg, label, say } from '../lib/utils.js'; + +export async function intro( + ctx: Pick +) { + if (!ctx.skipBanners) { + ctx.debug && ctx.logger.debug('Printing welcome message...'); + await say( + [ + [ + 'Welcome', + 'to', + label('StudioCMS', StudioCMSColorwayBg, color.black), + StudioCMSColorway(`v${ctx.version}`), + ctx.username, + ], + ctx.welcome, + ] as string[], + { clear: true } + ); + ctx.debug && ctx.logger.debug('Welcome message printed'); + } +} diff --git a/packages/studiocms/src/config.ts b/packages/studiocms/src/config.ts new file mode 100644 index 0000000000..29b1a3719a --- /dev/null +++ b/packages/studiocms/src/config.ts @@ -0,0 +1,21 @@ +import { + type CustomRenderer, + type Renderer, + type SafePluginListType, + type StudioCMSOptions, + type StudioCMSPlugin, + type StudioCMSPluginOptions, + definePlugin, +} from '@studiocms/core/schemas'; +import { defineStudioCMSConfig } from '@studiocms/core/utils'; + +export { + defineStudioCMSConfig, + definePlugin, + type StudioCMSPlugin, + type CustomRenderer, + type Renderer, + type StudioCMSOptions, + type StudioCMSPluginOptions, + type SafePluginListType, +}; diff --git a/packages/studiocms/src/example.astro b/packages/studiocms/src/example.astro new file mode 100644 index 0000000000..0952c2f743 --- /dev/null +++ b/packages/studiocms/src/example.astro @@ -0,0 +1,3 @@ +--- +--- +
Hello Example
\ No newline at end of file diff --git a/packages/studiocms/src/index.ts b/packages/studiocms/src/index.ts index c5a1f7079d..dfa0be54f0 100644 --- a/packages/studiocms/src/index.ts +++ b/packages/studiocms/src/index.ts @@ -1,27 +1,368 @@ -import { defineStudioCMSConfig, defineStudioCMSPlugin } from '@studiocms/core/lib'; -import type { CustomRenderer, Renderer, StudioCMSOptions } from '@studiocms/core/schemas'; -import type { StudioCMSPluginOptions } from '@studiocms/core/types'; -import integration from './integration'; +import inlineMod from '@inox-tools/aik-mod'; +import astroDTSBuilder from '@matthiesenxyz/astrodtsbuilder'; +import auth from '@studiocms/auth'; +import core from '@studiocms/core'; +import { StudioCMSError } from '@studiocms/core/errors'; +import type { + SafePluginListType, + StudioCMSConfig, + StudioCMSOptions, +} from '@studiocms/core/schemas'; +import dashboard from '@studiocms/dashboard'; +import frontend from '@studiocms/frontend'; +import imageHandler from '@studiocms/imagehandler'; +import renderers from '@studiocms/renderers'; +import robotsTXT from '@studiocms/robotstxt'; +import ui from '@studiocms/ui'; +import { + addVirtualImports, + createResolver, + defineIntegration, + withPlugins, +} from 'astro-integration-kit'; +import { z } from 'astro/zod'; +import boxen from 'boxen'; +import packageJson from 'package-json'; +import { compare as semCompare } from 'semver'; +import type { Messages } from './types.js'; +import { addIntegrationArray } from './utils/addIntegrationArray.js'; +import { checkAstroConfig } from './utils/astroConfigCheck.js'; +import { changelogHelper } from './utils/changelog.js'; +import { watchStudioCMSConfig } from './utils/configManager.js'; +import { configResolver } from './utils/configResolver.js'; +import { integrationLogger } from './utils/integrationLogger.js'; +import { nodeNamespaceBuiltinsAstro } from './utils/integrations.js'; +import readJson from './utils/readJson.js'; + +const { name: pkgName, version: pkgVersion } = readJson<{ name: string; version: string }>( + new URL('../package.json', import.meta.url) +); /** * **StudioCMS Integration** * * A CMS built for Astro by the Astro Community for the Astro Community. * - * > **Note: Astro SSR adapters that are configured for Image Optimization will automatically take full control of the Image Optimization Service. Making the `imageService` option in this integration not have any effect.** - * - * @see [GitHub Repo: 'astrolicious/studiocms'](https://github.com/astrolicious/studiocms) for more information on how to contribute to StudioCMS. - * @see [StudioCMS Docs](https://docs.studiocms.xyz) for more information on how to use StudioCMS. + * @see The [GitHub Repo: `withstudiocms/studiocms`](https://github.com/withstudiocms/studiocms) for more information on how to contribute to StudioCMS. + * @see The [StudioCMS Docs](https://docs.studiocms.dev) for more information on how to use StudioCMS. * */ -export const studioCMS = integration; +export default defineIntegration({ + name: pkgName, + optionsSchema: z.custom(), + setup: ({ options: opts }) => { + // Resolved Options for StudioCMS + let options: StudioCMSConfig; + + // Messages Array for Logging + const messages: Messages = []; + + // Resolver Function + const { resolve } = createResolver(import.meta.url); + + const tempComponentList: Record = { + example: './example.astro', + example2: './example.astro', + }; + + // Return the Integration + return withPlugins({ + name: pkgName, + plugins: [inlineMod], + hooks: { + // DB Setup: Setup the Database Connection for AstroDB and StudioCMS + // @ts-expect-error - This is a custom Integration Hook + 'astro:db:setup': ({ extendDb }) => { + extendDb({ configEntrypoint: '@studiocms/core/db/config' }); + }, + 'astro:config:setup': async (params) => { + // Destructure the params + const { logger, defineModule } = params; + + logger.info('Checking configuration...'); + + watchStudioCMSConfig(params); + + options = await configResolver(params, opts); + + const { verbose, rendererConfig, defaultFrontEndConfig, includedIntegrations, plugins } = + options; + + // Setup Logger + integrationLogger({ logger, logLevel: 'info', verbose }, 'Setting up StudioCMS...'); + + // Check Astro Config for required settings + checkAstroConfig(params); + + // Setup Logger + integrationLogger( + { logger, logLevel: 'info', verbose }, + 'Setting up StudioCMS internals...' + ); + + // Setup StudioCMS Integrations Array (Default Integrations) + const integrations = [ + { integration: nodeNamespaceBuiltinsAstro() }, + { integration: ui({ noInjectCSS: true }) }, + { integration: core(options) }, + { integration: renderers(rendererConfig, verbose) }, + { integration: imageHandler(options) }, + { integration: auth(options) }, + { integration: dashboard(options) }, + ]; + + // Frontend Integration (Default) + if (defaultFrontEndConfig !== false) { + integrations.push({ integration: frontend(options) }); + } + + integrationLogger( + { logger, logLevel: 'info', verbose }, + 'Adding optional integrations...' + ); + + // Robots.txt Integration (Default) + if (includedIntegrations.robotsTXT === true) { + integrations.push({ integration: robotsTXT() }); + } else if (typeof includedIntegrations.robotsTXT === 'object') { + integrations.push({ integration: robotsTXT(includedIntegrations.robotsTXT) }); + } + + // Initialize and Add the default StudioCMS Plugin to the Safe Plugin List + const safePluginList: SafePluginListType = [ + { + name: 'StudioCMS (Default)', + identifier: 'studiocms', + pageTypes: [{ label: 'Normal (StudioCMS)', identifier: 'studiocms' }], + }, + ]; + + integrationLogger( + { logger, logLevel: 'info', verbose }, + 'Setting up StudioCMS plugins...' + ); + + // Resolve StudioCMS Plugins + for (const { + name, + identifier, + studiocmsMinimumVersion, + integration, + frontendNavigationLinks, + pageTypes, + settingsPage, + } of plugins || []) { + // Check if the identifier is reserved + if (identifier === 'studiocms') { + throw new StudioCMSError( + 'Plugin Identifier "studiocms" is reserved for the default StudioCMS package.', + `Plugin ${name} has the identifier "studiocms" which is reserved for the default StudioCMS package, please change the identifier to something else, if the plugin is from a third party, please contact the author to change the identifier.` + ); + } + + // Check if the plugin has a minimum version requirement + const comparison = semCompare(studiocmsMinimumVersion, pkgVersion); + + if (comparison === 1) { + throw new StudioCMSError( + `Plugin ${name} requires StudioCMS version ${studiocmsMinimumVersion} or higher.`, + `Plugin ${name} requires StudioCMS version ${studiocmsMinimumVersion} or higher, please update StudioCMS to the required version, contact the plugin author to update the minimum version requirement or remove the plugin from the StudioCMS config.` + ); + } + + // Add the plugin Integration to the Astro config + if (integration && Array.isArray(integration)) { + integrations.push(...integration.map((integration) => ({ integration }))); + } else if (integration) { + integrations.push({ integration }); + } + + safePluginList.push({ + identifier, + name, + frontendNavigationLinks, + pageTypes, + settingsPage, + }); + } + + // Setup Integrations + addIntegrationArray(params, integrations); + + defineModule('studiocms:plugins', { + defaultExport: safePluginList, + }); + + defineModule('studiocms:config', { + defaultExport: options, + constExports: { + config: options, + dashboardConfig: options.dashboardConfig, + AuthConfig: options.dashboardConfig.AuthConfig, + developerConfig: options.dashboardConfig.developerConfig, + defaultFrontEndConfig: options.defaultFrontEndConfig, + sdk: options.sdk, + }, + }); + + defineModule('studiocms:version', { + defaultExport: pkgVersion, + }); + + addVirtualImports(params, { + name: pkgName, + imports: { + 'studiocms:component-proxy': ` + export const componentKeys = ${JSON.stringify( + Object.keys(tempComponentList).map((key) => key.toLowerCase()) + )}; + + ${Object.entries(tempComponentList) + // TODO: Update Resolve to point to Astro user config + .map(([key, value]) => `export { default as ${key} } from '${resolve(value)}';`) + .join('\n')} + `, + }, + }); + + let pluginListLength = 0; + let pluginListMessage = ''; + + pluginListLength = safePluginList.length; + pluginListMessage = safePluginList.map((p, i) => `${i + 1}. ${p.name}`).join('\n'); + + const messageBox = boxen(pluginListMessage, { + padding: 1, + title: `Currently Installed StudioCMS Plugins (${pluginListLength})`, + }); + + messages.push({ + label: 'studiocms:plugins', + logLevel: 'info', + message: ` \n \n${messageBox} \n \n`, + }); + + changelogHelper(params); + }, + // Config Done: Make DTS file for StudioCMS Plugins Virtual Module + 'astro:config:done': ({ injectTypes, config }) => { + // Make DTS file for StudioCMS Plugins Virtual Module + const dtsFile = astroDTSBuilder(); + + dtsFile.addSingleLineNote( + 'This file is auto-generated by StudioCMS and should not be modified.' + ); + + dtsFile.addModule('studiocms:plugins', { + defaultExport: { + typeDef: `import('${resolve('./config.js')}').SafePluginListType`, + }, + }); + + dtsFile.addModule('studiocms:changelog', { + defaultExport: { + typeDef: 'string', + }, + }); + + dtsFile.addModule('studiocms:component-proxy', { + namedExports: [ + { + name: 'componentKeys', + typeDef: 'string[]', + }, + ...Object.keys(tempComponentList).map((key) => ({ + name: key, + // TODO: Update Resolve to point to Astro user config + typeDef: `typeof import('${resolve(tempComponentList[key])}').default`, + })), + ], + }); + + dtsFile.addModule('studiocms:mode', { + defaultExport: { + typeDef: `{ output: 'static' | 'server', prerenderRoutes: boolean }`, + }, + namedExports: [ + { + name: 'output', + typeDef: `'static' | 'server'`, + }, + { + name: 'prerenderRoutes', + typeDef: 'boolean', + }, + ], + }); + + // Inject the DTS file + injectTypes(dtsFile.makeAstroInjectedType('types.d.ts')); + + // Log Setup Complete + messages.push({ + label: 'studiocms:setup', + logLevel: 'info', + message: 'Setup Complete. 🚀', + }); + }, + // DEV SERVER: Check for updates on server start and log messages + 'astro:server:start': async ({ logger: l }) => { + const logger = l.fork(`${pkgName}:update-check`); -export default studioCMS; + try { + const { version: latestVersion } = await packageJson(pkgName.toLowerCase()); -// Config Utility -export { defineStudioCMSConfig, type StudioCMSOptions }; + const comparison = semCompare(pkgVersion, latestVersion); -// Plugin System -export { defineStudioCMSPlugin, type StudioCMSPluginOptions }; + if (comparison === -1) { + logger.warn( + `A new version of '${pkgName}' is available. Please update to ${latestVersion} using your favorite package manager.` + ); + } else if (comparison === 0) { + logger.info(`You are using the latest version of '${pkgName}' (${pkgVersion})`); + } else { + logger.info( + `You are using a newer version (${pkgVersion}) of '${pkgName}' than the latest release (${latestVersion})` + ); + } + } catch (error) { + if (error instanceof Error) { + logger.error(`Error fetching latest version from npm registry: ${error.message}`); + } else { + // Handle the case where error is not an Error object + logger.error( + 'An unknown error occurred while fetching the latest version from the npm registry.' + ); + } + } -export type { CustomRenderer, Renderer }; + // Log all messages + for (const { label, message, logLevel } of messages) { + integrationLogger( + { + logger: l.fork(label), + logLevel, + verbose: logLevel === 'info' ? options.verbose : true, + }, + message + ); + } + }, + // BUILD: Log messages at the end of the build + 'astro:build:done': ({ logger }) => { + // Log messages at the end of the build + for (const { label, message, logLevel } of messages) { + integrationLogger( + { + logger: logger.fork(label), + logLevel, + verbose: logLevel === 'info' ? options.verbose : true, + }, + message + ); + } + }, + }, + }); + }, +}); diff --git a/packages/studiocms/src/integration.ts b/packages/studiocms/src/integration.ts deleted file mode 100644 index d612617e32..0000000000 --- a/packages/studiocms/src/integration.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { addIntegrationArray } from '@matthiesenxyz/integration-utils/aikUtils'; -import { - integrationLogger, - nodeNamespaceBuiltinsAstro, -} from '@matthiesenxyz/integration-utils/astroUtils'; -import studioCMSAuth from '@studiocms/auth'; -import studioCMSCore from '@studiocms/core'; -import { getStudioConfigFileUrl, studioCMSPluginList } from '@studiocms/core/lib'; -import { - type StudioCMSOptions, - StudioCMSOptionsSchema as optionsSchema, -} from '@studiocms/core/schemas'; -import { CoreStrings, robotsTXTPreset } from '@studiocms/core/strings'; -import { - addIntegrationArrayWithCheck, - checkAstroConfig, - configResolver, -} from '@studiocms/core/utils'; -import studioCMSDashboard from '@studiocms/dashboard'; -import studioCMSFrontend from '@studiocms/frontend'; -import studioCMSImageHandler from '@studiocms/imagehandler'; -import studioCMSRenderers from '@studiocms/renderers'; -import studioCMSRobotsTXT from '@studiocms/robotstxt'; -import { defineIntegration } from 'astro-integration-kit'; -import { name, version } from '../package.json'; -import { updateCheck } from './updateCheck'; - -// Main Integration -export default defineIntegration({ - name, - optionsSchema, - setup({ name, options }) { - // Register StudioCMS into the StudioCMS Plugin List - studioCMSPluginList.set(name, { name, label: 'StudioCMS' }); - - // Resolve Options - let resolvedOptions: StudioCMSOptions; - - return { - hooks: { - // Configure `@astrojs/db` integration to include the StudioCMS Database Tables - 'astro:db:setup': ({ extendDb }) => { - extendDb({ configEntrypoint: '@studiocms/core/db/config' }); - }, - 'astro:config:setup': async (params) => { - // Destructure Params - const { config: astroConfig, addWatchFile, logger } = params; - - // Watch the StudioCMS Config File for changes (including creation/deletion) - addWatchFile(getStudioConfigFileUrl(astroConfig.root)); - - // Resolve Options - const ResolvedOptions = await configResolver(params, options); - - // Set Resolved Options - resolvedOptions = ResolvedOptions; - - // Break out resolved options - const { - verbose, - rendererConfig, - dbStartPage, - dashboardConfig, - defaultFrontEndConfig, - imageService, - overrides, - includedIntegrations, - } = ResolvedOptions; - - // Setup Logger - integrationLogger({ logger, logLevel: 'info', verbose }, CoreStrings.Start); - - // Check Astro Config for required settings - checkAstroConfig(params); - - // Setup Integrations (Internal) - addIntegrationArray(params, [ - { integration: nodeNamespaceBuiltinsAstro() }, - { integration: studioCMSCore(resolvedOptions) }, - { integration: studioCMSRenderers(rendererConfig) }, - { - integration: studioCMSFrontend({ - verbose, - dbStartPage, - defaultFrontEndConfig, - }), - }, - { - integration: studioCMSImageHandler({ - verbose, - imageService, - overrides, - }), - }, - { - integration: studioCMSAuth({ - verbose, - dbStartPage, - dashboardConfig, - }), - }, - { - integration: studioCMSDashboard({ - verbose, - dbStartPage, - dashboardConfig, - }), - }, - ]); - - // Setup Integrations (External / Optional) - addIntegrationArrayWithCheck(params, [ - { - enabled: includedIntegrations.useAstroRobots, - knownSimilar: ['astro-robots', 'astro-robots-txt'], - integration: studioCMSRobotsTXT({ - ...robotsTXTPreset, - ...includedIntegrations.astroRobotsConfig, - }), - }, - ]); - }, - 'astro:server:start': async (params) => { - // Check for Updates on Development Server Start - updateCheck(params, name, version); - }, - }, - }; - }, -}); diff --git a/packages/studiocms/src/types.ts b/packages/studiocms/src/types.ts new file mode 100644 index 0000000000..46f51e0357 --- /dev/null +++ b/packages/studiocms/src/types.ts @@ -0,0 +1,21 @@ +import type { StudioCMSOptions } from '@studiocms/core/schemas'; + +export type Messages = { + label: string; + logLevel: 'info' | 'warn' | 'error' | 'debug'; + message: string; +}[]; + +export type ServerStartOptions = { + pkgName: string; + pkgVersion: string; + verbose: boolean; + messages: Messages; +}; + +export type ConfigSetupOptions = { + pkgName: string; + pkgVersion: string; + opts: StudioCMSOptions; + messages: Messages; +}; diff --git a/packages/studiocms/src/updateCheck.ts b/packages/studiocms/src/updateCheck.ts deleted file mode 100644 index bb7f9df963..0000000000 --- a/packages/studiocms/src/updateCheck.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { defineUtility } from 'astro-integration-kit'; -import packageJson from 'package-json'; -import * as semver from 'semver'; - -/** - * Fetches the latest version of a package from the npm registry. - * @param packageName - The name of the package. - * @returns A promise that resolves to the latest version of the package. - */ -async function fetchlatestVersion(packageName: string): Promise { - const { version } = await packageJson(packageName.toLowerCase()); - return version; -} - -/** - * Checks for updates of a specified package on npm registry. - * @param {import("astro").HookParameters<"astro:config:setup">} params - The Astro parameters object. - * @param currentVersion - The current version of the package. - */ -export const updateCheck = defineUtility('astro:server:start')( - async (params, name: string, currentVersion: string): Promise => { - const logger = params.logger.fork(`${name}:update-check`); - - try { - const latestVersion = await fetchlatestVersion(name); - - const comparison = semver.compare(currentVersion, latestVersion); - - if (comparison === -1) { - logger.warn( - `A new version of 'studiocms' is available. Please update to ${latestVersion} using your favorite package manager.` - ); - } else if (comparison === 0) { - logger.info(`You are using the latest version of '${name}' (${currentVersion})`); - } else { - logger.info( - `You are using a newer version (${currentVersion}) of '${name}' than the latest release (${latestVersion})` - ); - } - } catch (error) { - if (error instanceof Error) { - logger.error(`Error fetching latest version from npm registry: ${error.message}`); - } else { - // Handle the case where error is not an Error object - logger.error( - 'An unknown error occurred while fetching the latest version from the npm registry.' - ); - } - } - } -); diff --git a/packages/studiocms/src/utils/addIntegrationArray.ts b/packages/studiocms/src/utils/addIntegrationArray.ts new file mode 100644 index 0000000000..5e818f05d0 --- /dev/null +++ b/packages/studiocms/src/utils/addIntegrationArray.ts @@ -0,0 +1,35 @@ +import type { AstroIntegration } from 'astro'; +import { addIntegration, defineUtility } from 'astro-integration-kit'; + +/** + * Easily add a list of integrations from within an integration. + * + * @param {import("astro").HookParameters<"astro:config:setup">} params + * @param {array} integrations + * + * @example + * ```ts + * import Vue from "@astrojs/vue"; + * import tailwindcss from "@astrojs/tailwind"; + * + * addIntegrationArray(params, [ + * { integration: Vue(), ensureUnique: true } + * { integration: tailwindcss() } + * ]) + * ``` + * + * @see https://astro-integration-kit.netlify.app/utilities/add-integration/ + */ +export const addIntegrationArray = defineUtility('astro:config:setup')( + ( + params, + integrations: Array<{ + integration: AstroIntegration; + ensureUnique?: boolean | undefined; + }> + ): void => { + for (const { integration, ensureUnique } of integrations) { + addIntegration(params, { integration, ensureUnique }); + } + } +); diff --git a/packages/studiocms/src/utils/astroConfigCheck.ts b/packages/studiocms/src/utils/astroConfigCheck.ts new file mode 100644 index 0000000000..65c95afcb2 --- /dev/null +++ b/packages/studiocms/src/utils/astroConfigCheck.ts @@ -0,0 +1,39 @@ +import { StudioCMSCoreError } from '@studiocms/core/errors'; +import { defineUtility } from 'astro-integration-kit'; +import { integrationLogger } from './integrationLogger.js'; + +/** + * Checks the Users Astro Config for the following: + * + * - Astro:DB Integration + * - SSR Mode (output: "server") + * - Site URL is set (can be "http://localhost:4321" for local development) + */ +export const checkAstroConfig = defineUtility('astro:config:setup')( + ({ config: astroConfig, logger }) => { + // Check for Astro:DB Integration + if (!astroConfig.integrations.find(({ name }) => name === 'astro:db')) { + throw new StudioCMSCoreError( + 'Astro DB Integration not found in Astro Config', + 'Run `astro add db` to install `@astrojs/db` and add it to your Astro config.' + ); + } + + // Check for SSR Mode (output: "server") + if (astroConfig.output !== 'server') { + throw new StudioCMSCoreError("StudioCMS is only supported in 'Output: server' SSR mode."); + } + + // Check for Site URL + if (!astroConfig.site) { + throw new StudioCMSCoreError( + "StudioCMS requires a 'site' configuration in your Astro Config. This can be your domain ( 'https://example.com' ) or localhost ( 'http://localhost:4321' - localhost should only be used during development and should not be used in production)." + ); + } + + integrationLogger( + { logger, logLevel: 'info', verbose: true }, + 'Astro Config `output` & `site` options valid' + ); + } +); diff --git a/packages/studiocms/src/utils/changelog.ts b/packages/studiocms/src/utils/changelog.ts new file mode 100644 index 0000000000..eb87294c8d --- /dev/null +++ b/packages/studiocms/src/utils/changelog.ts @@ -0,0 +1,67 @@ +import { addVirtualImports, createResolver, defineUtility } from 'astro-integration-kit'; +import type { List, Root } from 'mdast'; +import { toMarkdown } from 'mdast-util-to-markdown'; +import { loadChangelog, semverCategories } from './changelogLoader.js'; + +const { resolve } = createResolver(import.meta.url); + +export const changelogHelper = defineUtility('astro:config:setup')(async (params) => { + const changelog = loadChangelog(resolve('../../CHANGELOG.md')); + + // Generate markdown output + const output: string[] = ['# Release Notes']; + + const ast: Root = { + type: 'root', + children: [], + }; + + // Get the latest version changelog + const latestVersion = changelog.versions[0]; + + // Get the latest version changes + const latestVersionChanges: List = { type: 'list', children: [] }; + + if (latestVersion) { + for (const semverCategory of semverCategories) { + for (const listItem of latestVersion.changes[semverCategory].children) { + latestVersionChanges.children.push(listItem); + } + } + + if (latestVersion.includes.size) { + latestVersionChanges.children.push({ + type: 'listItem', + children: [ + { + type: 'paragraph', + children: [ + { type: 'text', value: `Includes: ${[...latestVersion.includes].join(', ')} ` }, + ], + }, + ], + }); + } + + ast.children.push({ + type: 'heading', + depth: 2, + children: [{ type: 'text', value: `${latestVersion.version}` }], + }); + } + + if (latestVersionChanges.children.length) { + ast.children.push(latestVersionChanges); + } + + output.push(toMarkdown(ast, { bullet: '-' })); + + const markdownString = output.join('\n'); + + addVirtualImports(params, { + name: 'studiocms/changelog', + imports: { + 'studiocms:changelog': `export default ${JSON.stringify(markdownString)};`, + }, + }); +}); diff --git a/packages/studiocms/src/utils/changelogLoader.ts b/packages/studiocms/src/utils/changelogLoader.ts new file mode 100644 index 0000000000..4deafdee8e --- /dev/null +++ b/packages/studiocms/src/utils/changelogLoader.ts @@ -0,0 +1,140 @@ +import { readFileSync } from 'node:fs'; +import type { List } from 'mdast'; +import { fromMarkdown } from 'mdast-util-from-markdown'; +import { toString as ToString } from 'mdast-util-to-string'; +import { visit } from 'unist-util-visit'; + +export type Changelog = { + packageName: string; + versions: Version[]; +}; + +export type Version = { + version: string; + changes: { [key in SemverCategory]: List }; + includes: Set; +}; + +export const semverCategories = ['major', 'minor', 'patch'] as const; +export type SemverCategory = (typeof semverCategories)[number]; + +export function loadChangelog(path: string): Changelog { + let markdown = readFileSync(path, 'utf8'); + + // Convert GitHub usernames in "Thanks ..." sentences to links + markdown = markdown.replace( + /(?<=Thank[^.!]*? )@([a-z0-9-]+)(?=[\s,.!])/gi, + '[@$1](https://github.com/$1)' + ); + + const ast = fromMarkdown(markdown); + // const lines = readFileSync(path, 'utf8') + // .split(/\r?\n/) + // .map((line) => line.trimEnd()) + const changelog: Changelog = { + packageName: '', + versions: [], + }; + type ParserState = 'packageName' | 'version' | 'semverCategory' | 'changes'; + let state: ParserState = 'packageName'; + let version: Version | undefined; + let semverCategory: SemverCategory | undefined; + + function handleNode(node: ReturnType['children'][number]) { + if (node.type === 'heading') { + if (node.depth === 1) { + if (state !== 'packageName') throw new Error('Unexpected h1'); + changelog.packageName = ToString(node); + state = 'version'; + return; + } + if (node.depth === 2) { + if (state === 'packageName') throw new Error('Unexpected h2'); + version = { + version: ToString(node), + changes: { + major: { type: 'list', children: [] }, + minor: { type: 'list', children: [] }, + patch: { type: 'list', children: [] }, + }, + includes: new Set(), + }; + changelog.versions.push(version); + state = 'semverCategory'; + return; + } + if (node.depth === 3) { + if (state === 'packageName' || state === 'version') throw new Error('Unexpected h3'); + semverCategory = (ToString(node).split(' ')[0] || '').toLowerCase() as SemverCategory; + if (!semverCategories.includes(semverCategory)) + throw new Error(`Unexpected semver category: ${semverCategory}`); + state = 'changes'; + return; + } + } + if (node.type === 'list') { + if (state !== 'changes' || !version || !semverCategory) throw new Error('Unexpected list'); + // Go through list items + for (let listItemIdx = 0; listItemIdx < node.children.length; listItemIdx++) { + const listItem = node.children[listItemIdx]; + if (!listItem) continue; + + // Check if the current list item ends with a nested sublist that consists + // of items matching the pattern `@` + const lastChild = listItem.children[listItem.children.length - 1]; + if (lastChild?.type === 'list') { + const packageRefs: string[] = []; + // biome-ignore lint/complexity/noForEach: + lastChild.children.forEach((subListItem) => { + const text = ToString(subListItem); + if (parsePackageReference(text)) packageRefs.push(text); + }); + if (packageRefs.length === lastChild.children.length) { + // If so, add the packages to `includes` + for (const packageRef of packageRefs) { + version.includes.add(packageRef); + } + // Remove the sub-list from the list item + listItem.children.pop(); + } + } + + const firstPara = + listItem.children[0]?.type === 'paragraph' ? listItem.children[0] : undefined; + if (firstPara) { + // Remove IDs like `bfed62a: ...` or `... [85dbab8]` from the first paragraph + visit(firstPara, 'text', (textNode) => { + textNode.value = textNode.value.replace(/(^[0-9a-f]{7,}: | \[[0-9a-f]{7,}\]$)/, ''); + }); + // Skip list items that only contain the text `Updated dependencies` + const firstParaText = ToString(firstPara); + if (firstParaText === 'Updated dependencies') continue; + // If the list item is a package reference, add it to `includes` instead + const packageRef = parsePackageReference(firstParaText); + if (packageRef) { + version.includes.add(firstParaText); + continue; + } + // Add the list item to the changes + version.changes[semverCategory].children.push(listItem); + } + } + return; + } + throw new Error(`Unexpected node: ${JSON.stringify(node)}`); + } + + // biome-ignore lint/complexity/noForEach: + ast.children.forEach((node) => { + handleNode(node); + }); + + return changelog; +} + +function parsePackageReference(str: string) { + const matches = str.match(/^([@/a-z0-9-]+)@([0-9.]+)$/); + if (!matches) return; + const [, packageName, version] = matches; + return { packageName, version }; +} diff --git a/packages/studiocms/src/utils/configManager.ts b/packages/studiocms/src/utils/configManager.ts new file mode 100644 index 0000000000..2cbdb17686 --- /dev/null +++ b/packages/studiocms/src/utils/configManager.ts @@ -0,0 +1,155 @@ +import { statSync } from 'node:fs'; +import { StudioCMSCoreError } from '@studiocms/core/errors'; +import type { StudioCMSOptions } from '@studiocms/core/schemas'; +import { defineUtility } from 'astro-integration-kit'; + +// This File was created based on Expressive Code's Astro Integration by Hippotastic on github +// see: https://expressive-code.com/ & https://github.com/expressive-code/expressive-code + +/** + * Paths to search for the StudioCMS config file, + * sorted by how likely they're to appear. + */ +const configPaths = Object.freeze([ + 'studiocms.config.js', + 'studiocms.config.mjs', + 'studiocms.config.cjs', + 'studiocms.config.ts', + 'studiocms.config.mts', + 'studiocms.config.cts', +]); + +function findConfig(projectRootUrl: string) { + for (const path of configPaths) { + const configUrl = `${projectRootUrl}${path}`; + if (exists(configUrl)) { + return configUrl; + } + } + + return undefined; +} + +export function exists(path: string | undefined) { + if (!path) return false; + try { + statSync(path); + return true; + } catch { + return false; + } +} + +/** + * Returns a URL to the optional StudioCMS config file in the Astro project root. + */ +export function getStudioConfigFileUrl(projectRootUrl: string) { + const configPath = findConfig(projectRootUrl); + if (configPath) { + return configPath; + } + return undefined; +} + +/** + * Watches the StudioCMS configuration file for changes and adds it to the watch list. + * This utility is defined for the 'astro:config:setup' event. + * + * @param params - The parameters provided by the Astro configuration setup event. + * @param params.addWatchFile - Function to add a file to the watch list. + * @param params.config - The current Astro configuration object. + * @returns void + */ +export const watchStudioCMSConfig = defineUtility('astro:config:setup')( + ({ + addWatchFile, + config: { + root: { pathname }, + }, + }) => { + const configFileUrl = getStudioConfigFileUrl(pathname); + if (configFileUrl) { + addWatchFile(configFileUrl); + } + return; + } +); + +/** + * Attempts to import an StudioCMS config file in the Astro project root and returns its default export. + * + * If no config file is found, an empty object is returned. + */ +export async function loadStudioCMSConfigFile(projectRootUrl: URL): Promise { + const pathsToTry = [ + new URL(`./studiocms.config.js?t=${Date.now()}`, projectRootUrl).href, + new URL(`./studiocms.config.cjs?t=${Date.now()}`, projectRootUrl).href, + new URL(`./studiocms.config.mjs?t=${Date.now()}`, projectRootUrl).href, + new URL(`./studiocms.config.ts?t=${Date.now()}`, projectRootUrl).href, + new URL(`./studiocms.config.cts?t=${Date.now()}`, projectRootUrl).href, + new URL(`./studiocms.config.mts?t=${Date.now()}`, projectRootUrl).href, + ]; + + // @ts-ignore + if (import.meta.env?.BASE_URL?.length) { + pathsToTry.push( + `/studiocms.config.js?t=${Date.now()}`, + `/studiocms.config.mjs?t=${Date.now()}`, + `/studiocms.config.cjs?t=${Date.now()}`, + `/studiocms.config.ts?t=${Date.now()}`, + `/studiocms.config.mts?t=${Date.now()}`, + `/studiocms.config.cts?t=${Date.now()}` + ); + } + + /** + * Checks the error received on attempting to import StudioCMS config file. + * Bun's choice to throw ResolveMessage for import resolver messages means + * type comparison (error instanceof Error) isn't portable. + * @param error Error object, which could be string, Error, or ResolveMessage. + * @returns object containing message and, if present, error code. + */ + function coerceError(error: unknown): { message: string; code?: string | undefined } { + if (typeof error === 'object' && error !== null && 'message' in error) { + return error as { message: string; code?: string | undefined }; + } + return { message: error as string }; + } + + for (const path of pathsToTry) { + try { + const module = (await import(/* @vite-ignore */ path)) as { default: StudioCMSOptions }; + if (!module.default) { + throw new StudioCMSCoreError( + 'Missing or invalid default export. Please export your StudioCMS config object as the default export.' + ); + } + return module.default; + } catch (error) { + const { message, code } = coerceError(error); + + if (code === 'ERR_MODULE_NOT_FOUND' || code === 'ERR_LOAD_URL') { + const msgCheck = message.replace(/(imported )?from .*$/, ''); + if ( + msgCheck.includes('studiocms.config.js') || + msgCheck.includes('studiocms.config.mjs') || + msgCheck.includes('studiocms.config.cjs') || + msgCheck.includes('studiocms.config.ts') || + msgCheck.includes('studiocms.config.mts') || + msgCheck.includes('studiocms.config.cts') + ) + continue; + } + + throw new StudioCMSCoreError( + `Your project includes an StudioCMS config file (${path}) that could not be loaded due to ${ + code ? `the error ${code}` : 'the following error' + }: ${message}`.replace(/\s+/g, ' '), + error instanceof Error ? error.stack : '' + ); + } + } + + // Return an empty object if no config file is found or if all attempts fail + return undefined; +} diff --git a/packages/studiocms/src/utils/configResolver.ts b/packages/studiocms/src/utils/configResolver.ts new file mode 100644 index 0000000000..87307806c0 --- /dev/null +++ b/packages/studiocms/src/utils/configResolver.ts @@ -0,0 +1,139 @@ +import { StudioCMSCoreError } from '@studiocms/core/errors'; +import { + type StudioCMSConfig, + type StudioCMSOptions, + StudioCMSOptionsSchema, +} from '@studiocms/core/schemas'; +import { defineUtility } from 'astro-integration-kit'; +import { z } from 'astro/zod'; +import lo from 'lodash'; +import { loadStudioCMSConfigFile } from './configManager.js'; + +export function parseConfig(opts: StudioCMSOptions): StudioCMSConfig { + try { + return StudioCMSOptionsSchema.parse(opts); + } catch (error) { + if (error instanceof Error) { + throw new StudioCMSCoreError( + `Invalid StudioCMS Config Options: ${error.message}`, + error.stack + ); + } + throw new StudioCMSCoreError( + 'Invalid StudioCMS Options', + 'An unknown error occurred while parsing the StudioCMS options.' + ); + } +} + +// biome-ignore lint/suspicious/noExplicitAny: +function deepRemoveDefaults(schema: z.ZodTypeAny): any { + if (schema instanceof z.ZodDefault) return deepRemoveDefaults(schema.removeDefault()); + + if (schema instanceof z.ZodObject) { + // biome-ignore lint/suspicious/noExplicitAny: + const newShape: any = {}; + + for (const key in schema.shape) { + const fieldSchema = schema.shape[key]; + newShape[key] = z.ZodOptional.create(deepRemoveDefaults(fieldSchema)); + } + return new z.ZodObject({ + ...schema._def, + shape: () => newShape, + // biome-ignore lint/suspicious/noExplicitAny: + }) as any; + } + + if (schema instanceof z.ZodArray) return z.ZodArray.create(deepRemoveDefaults(schema.element)); + + if (schema instanceof z.ZodOptional) + return z.ZodOptional.create(deepRemoveDefaults(schema.unwrap())); + + if (schema instanceof z.ZodNullable) + return z.ZodNullable.create(deepRemoveDefaults(schema.unwrap())); + + if (schema instanceof z.ZodTuple) + // biome-ignore lint/suspicious/noExplicitAny: + return z.ZodTuple.create(schema.items.map((item: any) => deepRemoveDefaults(item))); + + return schema; +} + +function parseAndMerge( + schema: T, + inlineConfig: T['_output'], + studioCMSConfigFile: T['_input'] +): T['_output'] { + try { + const ZeroDefaultsSchema = deepRemoveDefaults(schema); + const parsedConfigFile = ZeroDefaultsSchema.parse(studioCMSConfigFile); + return lo.merge({}, inlineConfig, parsedConfigFile); + } catch (error) { + if (error instanceof Error) { + throw new StudioCMSCoreError( + `Invalid StudioCMS Config Options: ${error.message}`, + error.stack + ); + } + throw new StudioCMSCoreError( + 'Invalid StudioCMS Options', + 'An unknown error occurred while parsing the StudioCMS options.' + ); + } +} + +/** + * Resolves the StudioCMS Options + * + * @param {import("astro").HookParameters<"astro:config:setup">} params + * @param {StudioCMSOptions} options + * + * @returns {StudioCMSConfig} The resolved StudioCMS Options + */ +export const configResolver = defineUtility('astro:config:setup')( + async (params, options: StudioCMSOptions) => { + // Destructure Params + const { logger: l, config: astroConfig } = params; + + const logger = l.fork('studiocms:config'); + + const inlineConfigExists = options !== undefined; + + const inlineConfig: StudioCMSConfig = parseConfig(options); + + // Merge the given options with the ones from a potential StudioCMS config file + const studioCMSConfigFile = await loadStudioCMSConfigFile(astroConfig.root); + + if (!studioCMSConfigFile) { + return inlineConfig; + } + + try { + if (inlineConfigExists) { + logger.warn( + 'Both an inline StudioCMS config (in your Astro config file) and a StudioCMS config file (studiocms.config.{js|mjs|cjs|ts|mts|cts}) were found. The StudioCMS config file will override the inline config during merging.' + ); + } + + const mergedOptions = parseAndMerge( + StudioCMSOptionsSchema, + inlineConfig, + studioCMSConfigFile + ); + + return mergedOptions; + } catch (error) { + if (error instanceof Error) { + throw new StudioCMSCoreError( + `Invalid StudioCMS Config Options: ${error.message}`, + error.stack + ); + } + throw new StudioCMSCoreError( + 'Invalid StudioCMS Options', + 'An unknown error occurred while parsing the StudioCMS options.' + ); + } + } +); diff --git a/packages/studiocms/src/utils/integrationLogger.ts b/packages/studiocms/src/utils/integrationLogger.ts new file mode 100644 index 0000000000..099180ac20 --- /dev/null +++ b/packages/studiocms/src/utils/integrationLogger.ts @@ -0,0 +1,24 @@ +import type { AstroIntegrationLogger } from 'astro'; + +export type LoggerOpts = { + logLevel: 'info' | 'warn' | 'error' | 'debug'; + logger: AstroIntegrationLogger; + verbose?: boolean; +}; + +export const integrationLogger = async (opts: LoggerOpts, message: string): Promise => { + const { logLevel, logger, verbose } = opts; + + switch (verbose) { + case true: + logger[logLevel](message); + break; + case false: + if (logLevel !== 'debug' && logLevel !== 'info') { + logger[logLevel](message); + } + break; + default: + logger[logLevel](message); + } +}; diff --git a/packages/studiocms/src/utils/integrations.ts b/packages/studiocms/src/utils/integrations.ts new file mode 100644 index 0000000000..cf3a3c33c4 --- /dev/null +++ b/packages/studiocms/src/utils/integrations.ts @@ -0,0 +1,32 @@ +import { builtinModules as builtins } from 'node:module'; +import type { AstroIntegration } from 'astro'; +import { addVitePlugin, hasVitePlugin } from 'astro-integration-kit'; +import type { PluginOption } from 'vite'; + +export function namespaceBuiltinsPlugin(): PluginOption { + return { + name: 'namespace-builtins', + enforce: 'pre', + // biome-ignore lint/suspicious/noExplicitAny: This is a Vite plugin, so we don't have control over the type of `id` + resolveId(id: any) { + if (id[0] === '.' || id[0] === '/') return; + + if (builtins.includes(id)) { + return { id: `node:${id}`, external: true }; + } + return; + }, + }; +} +export function nodeNamespaceBuiltinsAstro(): AstroIntegration { + return { + name: 'vite-namespace-builtins', + hooks: { + 'astro:config:setup': (params) => { + if (!hasVitePlugin(params, { plugin: 'namespace-builtins' })) { + addVitePlugin(params, { plugin: namespaceBuiltinsPlugin() }); + } + }, + }, + }; +} diff --git a/packages/studiocms/src/utils/readJson.ts b/packages/studiocms/src/utils/readJson.ts new file mode 100644 index 0000000000..1490cf858a --- /dev/null +++ b/packages/studiocms/src/utils/readJson.ts @@ -0,0 +1,5 @@ +import fs from 'node:fs'; + +export default function readJson(path: string | URL): T { + return JSON.parse(fs.readFileSync(path, 'utf-8')); +} diff --git a/packages/studiocms/studiocms-cli.mjs b/packages/studiocms/studiocms-cli.mjs new file mode 100755 index 0000000000..78839d875f --- /dev/null +++ b/packages/studiocms/studiocms-cli.mjs @@ -0,0 +1,13 @@ +#!/usr/bin/env node + +const currentVersion = process.versions.node; +const requiredMajorVersion = Number.parseInt(currentVersion.split('.')[0], 10); +const minimumMajorVersion = 18; + +if (requiredMajorVersion < minimumMajorVersion) { + console.error(`Node.js v${currentVersion} is out of date and unsupported!`); + console.error(`Please use Node.js v${minimumMajorVersion} or higher.`); + process.exit(1); +} + +import('./dist/cli/index.js').then((mod) => mod); diff --git a/packages/studiocms/tsconfig.json b/packages/studiocms/tsconfig.json index 73fb2bc445..6073eb52d0 100644 --- a/packages/studiocms/tsconfig.json +++ b/packages/studiocms/tsconfig.json @@ -1,86 +1,10 @@ { - "extends": "astro/tsconfigs/strictest", - "files": [], + "extends": "../../tsconfig.base.json", + "include": ["src"], "compilerOptions": { - "outDir": "../../.moon/cache/types/packages/studiocms", - "composite": true, - "noEmit": false, - "allowImportingTsExtensions": false, - "emitDeclarationOnly": false, - "paths": { - "node-playground/*": ["../../playgrounds/node/src/*"], - "@studiocms/assets": ["../studiocms_assets/src/index.ts"], - "@studiocms/assets/*": ["../studiocms_assets/src/*"], - "@studiocms/auth": ["../studiocms_auth/src/index.ts"], - "@studiocms/auth/*": ["../studiocms_auth/src/*"], - "@studiocms/betaresources": ["../studiocms_betaresources/src/index.ts"], - "@studiocms/betaresources/*": ["../studiocms_betaresources/src/*"], - "@studiocms/blog": ["../studiocms_blog/index.ts"], - "@studiocms/blog/*": ["../studiocms_blog/src/*"], - "@studiocms/core": ["../studiocms_core/src/index.ts"], - "@studiocms/core/*": ["../studiocms_core/src/*"], - "@studiocms/dashboard": ["../studiocms_dashboard/src/index.ts"], - "@studiocms/dashboard/*": ["../studiocms_dashboard/src/*"], - "@studiocms/frontend": ["../studiocms_frontend/src/index.ts"], - "@studiocms/frontend/*": ["../studiocms_frontend/src/*"], - "@studiocms/imagehandler": ["../studiocms_imagehandler/src/index.ts"], - "@studiocms/imagehandler/*": ["../studiocms_imagehandler/src/*"], - "@studiocms/renderers": ["../studiocms_renderers/src/index.ts"], - "@studiocms/renderers/*": ["../studiocms_renderers/src/*"], - "@studiocms/robotstxt": ["../studiocms_robotstxt/src/index.ts"], - "@studiocms/robotstxt/*": ["../studiocms_robotstxt/src/*"] - } - }, - "references": [ - { - "path": "../../playgrounds/node" - }, - { - "path": "../studiocms_assets" - }, - { - "path": "../studiocms_auth" - }, - { - "path": "../studiocms_betaresources" - }, - { - "path": "../studiocms_blog" - }, - { - "path": "../studiocms_core" - }, - { - "path": "../studiocms_dashboard" - }, - { - "path": "../studiocms_frontend" - }, - { - "path": "../studiocms_imagehandler" - }, - { - "path": "../studiocms_renderers" - }, - { - "path": "../studiocms_robotstxt" - } - ], - "include": [ - "./package.json", - "../../playgrounds/node/**/*", - "../../playgrounds/node/.astro/**/*", - "../studiocms_assets/**/*", - "../studiocms_auth/**/*", - "../studiocms_betaresources/**/*", - "../studiocms_blog/**/*", - "../studiocms_core/**/*", - "../studiocms_dashboard/**/*", - "../studiocms_frontend/**/*", - "../studiocms_imagehandler/**/*", - "../studiocms_renderers/**/*", - "../studiocms_robotstxt/**/*", - "./**/*", - "./src/**/*.json" - ] + "outDir": "./dist", + "emitDeclarationOnly": true, + "resolveJsonModule": true, + "rootDir": "./src" + } } diff --git a/packages/studiocms_assets/LICENSE b/packages/studiocms_assets/LICENSE index a30d1fbe47..94787104c4 100644 --- a/packages/studiocms_assets/LICENSE +++ b/packages/studiocms_assets/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Astrolicious - StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares +Copyright (c) 2024 StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/studiocms_assets/moon.yml b/packages/studiocms_assets/moon.yml deleted file mode 100644 index aa80fbb0bb..0000000000 --- a/packages/studiocms_assets/moon.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: 'typescript' -tags: ['astro', 'typescript'] - -project: - name: '@studiocms/assets' - description: 'A CMS that bridges Astro with Astro DB & Studio' - maintainers: ['Adammatthiesen','jdtjenkins','dreyfus92'] \ No newline at end of file diff --git a/packages/studiocms_assets/package.json b/packages/studiocms_assets/package.json index 5dd2cfc23e..9b6bd99f72 100644 --- a/packages/studiocms_assets/package.json +++ b/packages/studiocms_assets/package.json @@ -40,7 +40,6 @@ "astro": "catalog:min" }, "devDependencies": { - "vite": "catalog:", "typescript": "catalog:" } } diff --git a/packages/studiocms_assets/src/svgs/discord.svg b/packages/studiocms_assets/src/svgs/discord.svg index d8d7fee4b2..dfe3f56d1d 100644 --- a/packages/studiocms_assets/src/svgs/discord.svg +++ b/packages/studiocms_assets/src/svgs/discord.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/studiocms_assets/tsconfig.json b/packages/studiocms_assets/tsconfig.json index c2c3dc4d88..d175d082a9 100644 --- a/packages/studiocms_assets/tsconfig.json +++ b/packages/studiocms_assets/tsconfig.json @@ -2,25 +2,10 @@ "extends": "astro/tsconfigs/strictest", "files": [], "compilerOptions": { - "outDir": "../../.moon/cache/types/packages/studiocms_assets", "composite": true, "noEmit": false, "allowImportingTsExtensions": false, - "emitDeclarationOnly": false, - "paths": { - "node-playground/*": ["../../playgrounds/node/src/*"] - } + "emitDeclarationOnly": false }, - "references": [ - { - "path": "../../playgrounds/node" - } - ], - "include": [ - "./package.json", - "../../playgrounds/node/**/*", - "../../playgrounds/node/.astro/**/*", - "./**/*", - "./src/**/*.json" - ] + "include": ["./package.json", "./**/*", "./src/**/*.json"] } diff --git a/packages/studiocms_auth/.gitignore b/packages/studiocms_auth/.gitignore index 4e02004cb0..8f18003eca 100644 --- a/packages/studiocms_auth/.gitignore +++ b/packages/studiocms_auth/.gitignore @@ -18,4 +18,6 @@ pnpm-debug.log* # macOS-specific files .DS_Store -.npmrc \ No newline at end of file +.npmrc + +dist/ \ No newline at end of file diff --git a/packages/studiocms_auth/LICENSE b/packages/studiocms_auth/LICENSE index a30d1fbe47..94787104c4 100644 --- a/packages/studiocms_auth/LICENSE +++ b/packages/studiocms_auth/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Astrolicious - StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares +Copyright (c) 2024 StudioCMS - Adam Matthiesen, Jacob Jenkins, Paul Valladares Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/packages/studiocms_auth/assets/astro.d.ts b/packages/studiocms_auth/assets/astro.d.ts new file mode 100644 index 0000000000..60e1ff7f0c --- /dev/null +++ b/packages/studiocms_auth/assets/astro.d.ts @@ -0,0 +1,10 @@ +/// + +interface Window { + theme: { + setTheme: (theme: 'system' | 'dark' | 'light') => void; + getTheme: () => 'system' | 'dark' | 'light'; + getSystemTheme: () => 'light' | 'dark'; + getDefaultTheme: () => 'system' | 'dark' | 'light'; + }; +} diff --git a/packages/studiocms_auth/src/components/OAuthButton.astro b/packages/studiocms_auth/assets/components/OAuthButton.astro similarity index 86% rename from packages/studiocms_auth/src/components/OAuthButton.astro rename to packages/studiocms_auth/assets/components/OAuthButton.astro index d36d78531f..cf2150d61a 100644 --- a/packages/studiocms_auth/src/components/OAuthButton.astro +++ b/packages/studiocms_auth/assets/components/OAuthButton.astro @@ -1,5 +1,5 @@ --- -import { Button } from '@studiocms/ui/components'; +import { Button } from 'studiocms:ui/components'; interface Props { href: string; diff --git a/packages/studiocms_auth/src/components/OAuthButtonStack.astro b/packages/studiocms_auth/assets/components/OAuthButtonStack.astro similarity index 74% rename from packages/studiocms_auth/src/components/OAuthButtonStack.astro rename to packages/studiocms_auth/assets/components/OAuthButtonStack.astro index bae54cdfc1..9a5955358a 100644 --- a/packages/studiocms_auth/src/components/OAuthButtonStack.astro +++ b/packages/studiocms_auth/assets/components/OAuthButtonStack.astro @@ -1,10 +1,10 @@ --- -import { getLangFromUrl, useTranslations } from 'studiocms:i18n'; -import { Divider } from '@studiocms/ui/components'; +import { useTranslations } from 'studiocms:i18n'; +import { Divider } from 'studiocms:ui/components'; import OAuthButton from './OAuthButton.astro'; import { providerData, showOAuth } from './oAuthButtonProviders'; -const lang = getLangFromUrl(Astro.url); +const lang = 'en-us'; const t = useTranslations(lang, '@studiocms/auth:oauth-stack'); const shouldShowOAuth = showOAuth && providerData.some(({ enabled }) => enabled); diff --git a/packages/studiocms_auth/assets/components/StaticAuthCheck.astro b/packages/studiocms_auth/assets/components/StaticAuthCheck.astro new file mode 100644 index 0000000000..672601501b --- /dev/null +++ b/packages/studiocms_auth/assets/components/StaticAuthCheck.astro @@ -0,0 +1,20 @@ +--- +import { getUserData } from 'studiocms:auth/lib/user'; +import { StudioCMSRoutes } from 'studiocms:lib'; + +const { isLoggedIn } = await getUserData(Astro); +--- + + + \ No newline at end of file diff --git a/packages/studiocms_auth/src/components/StudioCMSLogoSVG.astro b/packages/studiocms_auth/assets/components/StudioCMSLogoSVG.astro similarity index 100% rename from packages/studiocms_auth/src/components/StudioCMSLogoSVG.astro rename to packages/studiocms_auth/assets/components/StudioCMSLogoSVG.astro diff --git a/packages/studiocms_auth/assets/components/oAuthButtonProviders.ts b/packages/studiocms_auth/assets/components/oAuthButtonProviders.ts new file mode 100644 index 0000000000..19985b3654 --- /dev/null +++ b/packages/studiocms_auth/assets/components/oAuthButtonProviders.ts @@ -0,0 +1,41 @@ +import { authEnvCheck } from 'studiocms:auth/utils/authEnvCheck'; +import { AuthConfig } from 'studiocms:config'; +import { StudioCMSRoutes } from 'studiocms:lib'; + +const authEnv = await authEnvCheck(AuthConfig.providers); + +export const showOAuth = authEnv.SHOW_OAUTH; + +export type ProviderData = { + enabled: boolean; + href: string; + label: string; + image: string; +}; + +export const providerData: ProviderData[] = [ + { + enabled: authEnv.GITHUB.ENABLED, + href: StudioCMSRoutes.authLinks.githubIndex, + label: 'GitHub', + image: ``, + }, + { + enabled: authEnv.DISCORD.ENABLED, + href: StudioCMSRoutes.authLinks.discordIndex, + label: 'Discord', + image: ``, + }, + { + enabled: authEnv.GOOGLE.ENABLED, + href: StudioCMSRoutes.authLinks.googleIndex, + label: 'Google', + image: ``, + }, + { + enabled: authEnv.AUTH0.ENABLED, + href: StudioCMSRoutes.authLinks.auth0Index, + label: 'Auth0', + image: ``, + }, +]; diff --git a/packages/studiocms_auth/assets/layouts/AuthLayout.astro b/packages/studiocms_auth/assets/layouts/AuthLayout.astro new file mode 100644 index 0000000000..86d2ceca11 --- /dev/null +++ b/packages/studiocms_auth/assets/layouts/AuthLayout.astro @@ -0,0 +1,74 @@ +--- +import 'studiocms:ui/global-css'; +import '@fontsource-variable/onest/index.css'; +import './authlayout.css'; +import { Generator } from 'studiocms:components'; +import { Toaster } from 'studiocms:ui/components'; +import onestWoff2 from '@fontsource-variable/onest/files/onest-latin-wght-normal.woff2?url'; +import OAuthButtonStack from '../components/OAuthButtonStack.astro'; +import StaticAuthCheck from '../components/StaticAuthCheck.astro'; +import FallbackCanvas from './FallbackCanvas.astro'; +import ThemeManager from './ThemeManager.astro'; +import ThreeCanvasLoader from './ThreeCanvasLoader.astro'; + +interface Props { + title: string; + description: string; + lang: string; + disableScreen?: boolean; + checkLogin?: boolean; +} + +const { title, description, lang, disableScreen, checkLogin } = Astro.props; +--- + + + + {/* Global Metadata */} + + + + + {/* Favicon */} + + + + + {/* Primary Meta Tags */} + {title} + + + + + {/* Theme Manager */} + + + {/* Fonts */} + + + + +
+
+ +
+ +
+ { checkLogin && ( + + )} + + + diff --git a/packages/studiocms_auth/assets/layouts/FallbackCanvas.astro b/packages/studiocms_auth/assets/layouts/FallbackCanvas.astro new file mode 100644 index 0000000000..31d0b09e58 --- /dev/null +++ b/packages/studiocms_auth/assets/layouts/FallbackCanvas.astro @@ -0,0 +1,124 @@ +--- +import { Image } from 'astro:assets'; +import { validImages } from 'studiocms:auth/utils/validImages'; +import studioCMS_SDK_Cache from 'studiocms:sdk/cache'; +import StudioCMSLogoSVG from '../components/StudioCMSLogoSVG.astro'; + +// Get the site config +const { + data: { loginPageBackground, loginPageCustomImage }, +} = await studioCMS_SDK_Cache.GET.siteConfig(); + +const fallbackImageSrc = + loginPageBackground === 'custom' + ? loginPageCustomImage + : validImages.find((x) => x.name !== 'custom' && x.name === loginPageBackground)?.dark; +--- + +
+
+ + \ No newline at end of file diff --git a/packages/studiocms_auth/assets/layouts/ThemeManager.astro b/packages/studiocms_auth/assets/layouts/ThemeManager.astro new file mode 100644 index 0000000000..c69f615071 --- /dev/null +++ b/packages/studiocms_auth/assets/layouts/ThemeManager.astro @@ -0,0 +1,53 @@ + + + \ No newline at end of file diff --git a/packages/studiocms_auth/assets/layouts/ThreeCanvasLoader.astro b/packages/studiocms_auth/assets/layouts/ThreeCanvasLoader.astro new file mode 100644 index 0000000000..4ef318170b --- /dev/null +++ b/packages/studiocms_auth/assets/layouts/ThreeCanvasLoader.astro @@ -0,0 +1,18 @@ +--- +import studioCMS_SDK_Cache from 'studiocms:sdk/cache'; + +// Get the site config +const { + data: { loginPageBackground, loginPageCustomImage }, +} = await studioCMS_SDK_Cache.GET.siteConfig(); +--- +