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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions bin/docs/build-dev-docs.sh
Original file line number Diff line number Diff line change
@@ -1,17 +1,47 @@
echo "STARTING"
COMPILE_DOCS="npx tsc --project bin/docs/tsconfig.docs.json --moduleResolution node --target esNext && npx generate-docs --overridePath ./bin/docs/typeOverride.json --input ./docs-shopify.dev/commands --output ./docs-shopify.dev/generated && rm -rf docs-shopify.dev/commands/**/*.doc.js docs-shopify.dev/commands/*.doc.js"

# Check if schema docs exist (generated by `shopify docs generate-schema`)
HAS_SCHEMA_DOCS=false
if [ -d "docs-shopify.dev/configuration" ] && [ "$(ls -A docs-shopify.dev/configuration/*.doc.ts 2>/dev/null)" ]; then
HAS_SCHEMA_DOCS=true
fi

# Step 1: Compile TypeScript for commands
COMPILE_CMD_TS="npx tsc --project bin/docs/tsconfig.docs.json --moduleResolution node --target esNext"
# Step 2: Compile TypeScript for schema docs (if present)
COMPILE_SCHEMA_TS="npx tsc --project bin/docs/tsconfig.schema-docs.json --moduleResolution node --target esNext"
# Step 3: Run generate-docs with all inputs at once so they end up in a single output file
GENERATE_DOCS_INPUT="./docs-shopify.dev/commands"
CLEANUP="rm -rf docs-shopify.dev/commands/**/*.doc.js docs-shopify.dev/commands/*.doc.js"

COMPILE_STATIC_PAGES="npx tsc docs-shopify.dev/static/*.doc.ts --moduleResolution node --target esNext && npx generate-docs --isLandingPage --input ./docs-shopify.dev/static --output ./docs-shopify.dev/generated && rm -rf docs-shopify.dev/static/*.doc.js"
COMPILE_CATEGORY_PAGES="npx tsc docs-shopify.dev/categories/*.doc.ts --moduleResolution node --target esNext && generate-docs --isCategoryPage --input ./docs-shopify.dev/categories --output ./docs-shopify.dev/generated && rm -rf docs-shopify.dev/categories/*.doc.js"
COMPILE_CATEGORY_PAGES="npx tsc docs-shopify.dev/categories/*.doc.ts --moduleResolution node --target esNext && npx generate-docs --isCategoryPage --input ./docs-shopify.dev/categories --output ./docs-shopify.dev/generated && rm -rf docs-shopify.dev/categories/*.doc.js"

OUTPUT_DIR="./docs-shopify.dev/generated"

if [ "$1" = "isTest" ];
then
COMPILE_DOCS="npx tsc --project bin/docs/tsconfig.docs.json --moduleResolution node --target esNext && npx generate-docs --overridePath ./bin/docs/typeOverride.json --input ./docs-shopify.dev/commands --output ./docs-shopify.dev/static/temp && rm -rf docs-shopify.dev/commands/**/*.doc.js docs-shopify.dev/commands/*.doc.js"
OUTPUT_DIR="./docs-shopify.dev/static/temp"
COMPILE_STATIC_PAGES="npx tsc docs-shopify.dev/static/*.doc.ts --moduleResolution node --target esNext && npx generate-docs --isLandingPage --input ./docs-shopify.dev/static/docs-shopify.dev --output ./docs-shopify.dev/static/temp && rm -rf docs-shopify.dev/static/*.doc.js"
fi

echo $1
echo "RUNNING"
eval $COMPILE_DOCS

# Compile command docs TypeScript
eval $COMPILE_CMD_TS

# Compile schema docs TypeScript if present, and add to input
if [ "$HAS_SCHEMA_DOCS" = true ]; then
eval $COMPILE_SCHEMA_TS
GENERATE_DOCS_INPUT="./docs-shopify.dev/commands ./docs-shopify.dev/configuration"
CLEANUP="$CLEANUP && rm -rf docs-shopify.dev/configuration/**/*.doc.js docs-shopify.dev/configuration/*.doc.js"
fi

# Generate all reference entity docs in a single pass
npx generate-docs --overridePath ./bin/docs/typeOverride.json --input $GENERATE_DOCS_INPUT --output $OUTPUT_DIR
eval $CLEANUP

eval $COMPILE_STATIC_PAGES
eval $COMPILE_CATEGORY_PAGES
echo "DONE"
9 changes: 9 additions & 0 deletions bin/docs/tsconfig.schema-docs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"rootDir": "/"
},
"include": [
"../../docs-shopify.dev/configuration/**/*.doc.ts"
],
"exclude": []
}
10 changes: 10 additions & 0 deletions docs-shopify.dev/categories/app-configuration.doc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {CategoryTemplateSchema} from '@shopify/generate-docs'

const data: CategoryTemplateSchema = {
// Name of the category
category: 'app-configuration',
title: 'App configuration (app.toml)',
sections: [],
}

export default data
10 changes: 10 additions & 0 deletions docs-shopify.dev/categories/extension-configuration.doc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {CategoryTemplateSchema} from '@shopify/generate-docs'

const data: CategoryTemplateSchema = {
// Name of the category
category: 'extension-configuration',
title: 'Extension configuration (extension.toml)',
sections: [],
}

export default data
3 changes: 2 additions & 1 deletion packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@
"@types/react-dom": "^19.0.0",
"@types/which": "3.0.4",
"@types/ws": "^8.5.13",
"@vitest/coverage-istanbul": "^3.1.4"
"@vitest/coverage-istanbul": "^3.1.4",
"zod-to-json-schema": "^3.24.1"
},
"engines": {
"node": ">=20.10.0"
Expand Down
157 changes: 157 additions & 0 deletions packages/app/src/cli/commands/app/docs/generate-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import {appFromIdentifiers} from '../../../services/context.js'
import {fetchSpecifications} from '../../../services/generate/fetch-extension-specifications.js'
import {AppSchema} from '../../../models/app/app.js'
import {
extractFieldsFromSpec,
zodSchemaToFields,
extensionSlug,
generateAppConfigDocFile,
generateAppConfigSectionInterface,
generateAppConfigExampleToml,
generateExtensionDocFile,
generateExtensionInterfaceFile,
generateExtensionExampleToml,
} from '../../../services/docs/schema-to-docs.js'

/* eslint-disable @nx/enforce-module-boundaries -- internal tooling command, not lazy-loaded at runtime */
import Command from '@shopify/cli-kit/node/base-command'
import {mkdir, writeFile} from '@shopify/cli-kit/node/fs'
import {cwd, joinPath} from '@shopify/cli-kit/node/path'
import {outputInfo, outputSuccess} from '@shopify/cli-kit/node/output'
import type {AppConfigSection, MergedSpec} from '../../../services/docs/schema-to-docs.js'
/* eslint-enable @nx/enforce-module-boundaries */

const DOCS_PATH = 'docs-shopify.dev/configuration'

/**
* The client ID of the e2e test app. Used to authenticate and fetch specs.
* This is the same value used in packages/e2e/.env (SHOPIFY_FLAG_CLIENT_ID).
*/
const E2E_CLIENT_ID = 'c7e63b628cf2a97f4fca7a3dc122a5ef'

/**
* App config specs to skip in docs — these share a schema with another spec and
* would produce duplicate sections. Their fields are already covered by the other spec.
*/
const SKIP_APP_CONFIG_SPECS = new Set([
// Uses the same WebhooksSchema as 'webhooks'; its fields are covered by the Webhooks section
'privacy_compliance_webhooks',
// Branding fields (name, handle) are added to the Global section instead
'branding',
])

export default class DocsGenerateSchema extends Command {
static description = 'Generate TOML configuration schema documentation'
static hidden = true

async run(): Promise<void> {
const basePath = joinPath(cwd(), DOCS_PATH)

outputInfo('Authenticating and fetching app...')
const app = await appFromIdentifiers({apiKey: E2E_CLIENT_ID})
const {developerPlatformClient} = app

outputInfo('Fetching extension specifications...')
const specs = await fetchSpecifications({
developerPlatformClient,
app: {apiKey: app.apiKey, organizationId: app.organizationId, id: app.id},
})

// Partition: single = app.toml config modules, uuid/dynamic = extension types
const appConfigSpecs: MergedSpec[] = []
const extensionSpecs: MergedSpec[] = []
for (const spec of specs) {
const merged = spec as MergedSpec
if (merged.uidStrategy === 'single') {
if (!SKIP_APP_CONFIG_SPECS.has(merged.identifier)) {
appConfigSpecs.push(merged)
}
} else {
extensionSpecs.push(merged)
}
}

outputInfo(
`Found ${specs.length} specifications (${appConfigSpecs.length} app config, ${extensionSpecs.length} extensions). Generating docs...`,
)

// Ensure output directories exist
await mkdir(basePath)
await mkdir(joinPath(basePath, 'interfaces'))
await mkdir(joinPath(basePath, 'examples'))

// --- App configuration: one consolidated page ---

// Start with root-level fields from AppSchema (client_id, build, extension_directories, etc.)
// Also include name and handle which are root-level app.toml fields contributed by the branding spec.
const globalFields = [
...zodSchemaToFields(AppSchema),
{name: 'name', type: 'string', required: true, description: 'The name of your app.'},
{name: 'handle', type: 'string', required: false, description: 'The URL handle of your app.'},
]
const appSections: AppConfigSection[] = [
{
identifier: 'global',
externalName: 'Global',
fields: globalFields,
},
]
outputInfo(` App config section: global (${globalFields.length} fields)`)

const appConfigFieldPromises = appConfigSpecs.map(async (spec) => {
const fields = await extractFieldsFromSpec(spec)
return {
identifier: spec.identifier,
externalName: spec.externalName,
fields,
}
})
const resolvedAppConfigSections = await Promise.all(appConfigFieldPromises)
for (const section of resolvedAppConfigSections) {
appSections.push(section)
outputInfo(` App config section: ${section.identifier} (${section.fields.length} fields)`)
}

const appDocContent = generateAppConfigDocFile(appSections)
await writeFile(joinPath(basePath, 'app-configuration.doc.ts'), appDocContent)

// Write one interface file per app config section
const interfaceWrites = appSections
.filter((section) => section.fields.length > 0)
.map(async (section) => {
const sectionSlug = section.identifier.replace(/_/g, '-')
const interfaceContent = generateAppConfigSectionInterface(section)
await writeFile(joinPath(basePath, 'interfaces', `${sectionSlug}.interface.ts`), interfaceContent)
})
await Promise.all(interfaceWrites)

// Write combined app.toml example
const appExampleContent = generateAppConfigExampleToml(appSections)
await writeFile(joinPath(basePath, 'examples', 'app-configuration.example.toml'), appExampleContent)

// --- Extensions: one page per extension type ---
const extensionWrites = extensionSpecs.map(async (spec) => {
const fields = await extractFieldsFromSpec(spec)
const slug = extensionSlug(spec)

const docContent = generateExtensionDocFile(spec, fields)
await writeFile(joinPath(basePath, `${slug}.doc.ts`), docContent)

if (fields.length > 0) {
const interfaceContent = generateExtensionInterfaceFile(spec, fields)
await writeFile(joinPath(basePath, 'interfaces', `${slug}.interface.ts`), interfaceContent)
}

const exampleContent = generateExtensionExampleToml(spec, fields)
await writeFile(joinPath(basePath, 'examples', `${slug}.example.toml`), exampleContent)

outputInfo(` Extension: ${slug} (${fields.length} fields)`)
})

await Promise.all(extensionWrites)

outputSuccess(
`Generated documentation in ${DOCS_PATH}/: 1 app config page (${appSections.length} sections), ${extensionSpecs.length} extension pages`,
)
}
}
3 changes: 3 additions & 0 deletions packages/app/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ import DevClean from './commands/app/dev/clean.js'
import AppUnlinkedCommand from './utilities/app-unlinked-command.js'
import FunctionInfo from './commands/app/function/info.js'
import ImportCustomDataDefinitions from './commands/app/import-custom-data-definitions.js'
import DocsGenerateSchema from './commands/app/docs/generate-schema.js'
import OrganizationList from './commands/organization/list.js'
// eslint-disable-next-line @nx/enforce-module-boundaries -- pre-existing import, nx false positive from docs:generate-schema dependency chain
import BaseCommand from '@shopify/cli-kit/node/base-command'

/**
Expand Down Expand Up @@ -76,6 +78,7 @@ export const commands: {[key: string]: typeof AppLinkedCommand | typeof AppUnlin
'app:versions:list': VersionsList,
'app:webhook:trigger': WebhookTrigger,
'webhook:trigger': WebhookTriggerDeprecated,
'docs:generate-schema': DocsGenerateSchema,
'demo:watcher': DemoWatcher,
'organization:list': OrganizationList,
}
Expand Down
Loading
Loading