Skip to content

Customer account rc doc - support doc generate #2768

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 52 additions & 79 deletions packages/ui-extensions/docs/surfaces/admin/build-docs.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
/* eslint-disable no-undef, no-console */
import childProcess from 'child_process';
import fs from 'fs/promises';
import {existsSync} from 'fs';
import path from 'path';
import {fileURLToPath} from 'url';

import {
generateFiles,
copyGeneratedToShopifyDev,
replaceFileContent,
} from '../build-doc-shared.mjs';

const EXTENSIONS_API_VERSION = process.argv[2] || 'unstable';

const __filename = fileURLToPath(import.meta.url);
Expand All @@ -31,15 +36,8 @@ const generatedStaticPagesFile = 'generated_static_pages.json';
const componentDefs = path.join(srcPath, 'components.d.ts');
const tempComponentDefs = path.join(srcPath, 'components.ts');

const replaceFileContent = async (filePaths, searchValue, replaceValue) => {
const files = Array.isArray(filePaths) ? filePaths : [filePaths];
for (const filePath of files) {
const content = await fs.readFile(filePath, 'utf8');
// @ts-ignore -- TS should know this is a string but it doesn't
const replacedContent = content.replaceAll(searchValue, replaceValue);
await fs.writeFile(filePath, replacedContent);
}
};
const tsconfig = 'tsconfig.docs.json';
const tsconfigAppBridge = 'tsconfig.ab.docs.json';

const decodeHTML = (str) => {
return str
Expand Down Expand Up @@ -156,57 +154,6 @@ const transformJson = async (filePath, isExtensions) => {
await fs.writeFile(filePath, JSON.stringify(jsonData, null, 2));
};

const generateFiles = async (tsconfig, outputDir, isExtensions = true) => {
const scripts = [
`yarn tsc --project ${docsRelativePath}/${tsconfig} --moduleResolution node --target esNext --module CommonJS`,
`yarn generate-docs --input ./${srcRelativePath} --typesInput ./${srcRelativePath} --output ./${outputDir}`,
];

if (isExtensions) {
scripts.push(
`yarn tsc ${docsRelativePath}/staticPages/*.doc.ts --moduleResolution node --target esNext --module CommonJS`,
`yarn generate-docs --isLandingPage --input ./${docsRelativePath}/staticPages --output ./${outputDir}`,
);
}

scripts.forEach((script) => childProcess.execSync(script, {stdio: 'pipe'}));

const srcFiles = await fs.readdir(rootPath, {recursive: true});
const builtFiles = srcFiles.filter((file) => file.endsWith('.ts'));
await Promise.all(
builtFiles.map((file) => {
const jsFilePath = path.join(rootPath, file.replace('.ts', '.js'));
return existsSync(jsFilePath) ? fs.rm(jsFilePath) : Promise.resolve();
}),
);

const generatedFiles = [path.join(outputDir, generatedDocsDataFile)];
if (isExtensions) {
generatedFiles.push(path.join(outputDir, generatedStaticPagesFile));
}

// Make sure https://shopify.dev URLs are relative so they work in Spin.
// See https://github.com/Shopify/generate-docs/issues/181
await replaceFileContent(generatedFiles, 'https://shopify.dev', '');

// @ts-ignore
await transformJson(
path.join(outputDir, generatedDocsDataFile),
isExtensions,
);
};

const copyGeneratedToShopifyDev = async () => {
if (!shopifyDevExists) {
console.log(
`Not copying docs to shopify-dev because it was not found at ${shopifyDevPath}.`,
);
process.exit();
}

await fs.cp(generatedDocsPath, shopifyDevDBPath, {recursive: true});
};

const generateExtensionsDocs = async () => {
console.log(
`Building Admin UI Extensions docs for ${EXTENSIONS_API_VERSION} version`,
Expand All @@ -218,16 +165,30 @@ const generateExtensionsDocs = async () => {
);
}

const extensionsOutputDir = `${docsGeneratedRelativePath}/admin_extensions/${EXTENSIONS_API_VERSION}`;
const outputDir = `${docsGeneratedRelativePath}/admin_extensions/${EXTENSIONS_API_VERSION}`;

await generateFiles('tsconfig.docs.json', extensionsOutputDir);
const scripts = [
`yarn tsc --project ${docsRelativePath}/${tsconfig} --moduleResolution node --target esNext --module CommonJS`,
`yarn generate-docs --input ./${srcRelativePath} --typesInput ./${srcRelativePath} --output ./${outputDir}`,
`yarn tsc ${docsRelativePath}/staticPages/*.doc.ts --moduleResolution node --target esNext --module CommonJS`,
`yarn generate-docs --isLandingPage --input ./${docsRelativePath}/staticPages --output ./${outputDir}`,
];

await generateFiles({
scripts,
outputDir,
rootPath,
generatedDocsDataFile,
generatedStaticPagesFile,
transformJson: (filePath) => transformJson(filePath, true),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now this is an optional function, admin will pass it

});

// Replace 'unstable' with the exact API version in relative doc links
await replaceFileContent(
path.join(extensionsOutputDir, generatedDocsDataFile),
'/docs/api/admin-extensions/unstable/',
`/docs/api/admin-extensions/${EXTENSIONS_API_VERSION}`,
);
await replaceFileContent({
filePaths: path.join(outputDir, generatedDocsDataFile),
searchValue: '/docs/api/admin-extensions/unstable/',
replaceValue: `/docs/api/admin-extensions/${EXTENSIONS_API_VERSION}`,
});

await fs.cp(
path.join(docsPath, 'screenshots'),
Expand All @@ -243,26 +204,38 @@ const generateExtensionsDocs = async () => {
const generateAppBridgeDocs = async () => {
console.log('Building App Bridge docs');

await generateFiles(
'tsconfig.ab.docs.json',
`${docsGeneratedRelativePath}/app_bridge`,
false,
);
const outputDir = `${docsGeneratedRelativePath}/app_bridge`;
const scripts = [
`yarn tsc --project ${docsRelativePath}/${tsconfigAppBridge} --moduleResolution node --target esNext --module CommonJS`,
`yarn generate-docs --input ./${srcRelativePath} --typesInput ./${srcRelativePath} --output ./${outputDir}`,
];

await generateFiles({
scripts,
outputDir,
rootPath,
generatedDocsDataFile,
transformJson: (filePath) => transformJson(filePath, false),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we do not pass generatedStaticPagesFile here so that generateFiles will know do not replace non-existing static json file

});
};

try {
if (existsSync(generatedDocsPath)) {
await fs.rm(generatedDocsPath, {recursive: true});
}
await fs.copyFile(componentDefs, tempComponentDefs);
await replaceFileContent(
tempComponentDefs,
/typeof globalThis\.HTMLElement/g,
'any',
);
await replaceFileContent({
filePaths: tempComponentDefs,
searchValue: /typeof globalThis\.HTMLElement/g,
replaceValue: 'any',
});
await generateExtensionsDocs();
await generateAppBridgeDocs();
await copyGeneratedToShopifyDev();
await copyGeneratedToShopifyDev({
generatedDocsPath,
shopifyDevPath,
shopifyDevDBPath,
});

await fs.rm(tempComponentDefs);
} catch (error) {
Expand Down
72 changes: 72 additions & 0 deletions packages/ui-extensions/docs/surfaces/build-doc-shared.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* eslint-disable no-undef, no-console */
import childProcess from 'child_process';
import fs from 'fs/promises';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all extracted from admin's buold-docs scripts. All these functions will only use parameters, no global variables any more

import {existsSync} from 'fs';
import path from 'path';

export const replaceFileContent = async ({
filePaths,
searchValue,
replaceValue,
}) => {
const files = Array.isArray(filePaths) ? filePaths : [filePaths];
for (const filePath of files) {
const content = await fs.readFile(filePath, 'utf8');
// @ts-ignore -- TS should know this is a string but it doesn't
const replacedContent = content.replaceAll(searchValue, replaceValue);
await fs.writeFile(filePath, replacedContent);
}
};

export const generateFiles = async ({
scripts,
outputDir,
rootPath,
generatedDocsDataFile,
generatedStaticPagesFile,
transformJson,
}) => {
scripts.forEach((script) => childProcess.execSync(script, {stdio: 'pipe'}));

const srcFiles = await fs.readdir(rootPath, {recursive: true});
const builtFiles = srcFiles.filter((file) => file.endsWith('.ts'));
await Promise.all(
builtFiles.map((file) => {
const jsFilePath = path.join(rootPath, file.replace('.ts', '.js'));
return existsSync(jsFilePath) ? fs.rm(jsFilePath) : Promise.resolve();
}),
);

const generatedFiles = [path.join(outputDir, generatedDocsDataFile)];
if (generatedStaticPagesFile) {
generatedFiles.push(path.join(outputDir, generatedStaticPagesFile));
}

// Make sure https://shopify.dev URLs are relative so they work in Spin.
// See https://github.com/Shopify/generate-docs/issues/181
await replaceFileContent({
filePaths: generatedFiles,
searchValue: 'https://shopify.dev',
replaceValue: '',
});

if (transformJson) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transformJson become optional

await transformJson(path.join(outputDir, generatedDocsDataFile));
}
};

export const copyGeneratedToShopifyDev = async ({
generatedDocsPath,
shopifyDevPath,
shopifyDevDBPath,
}) => {
const shopifyDevExists = existsSync(shopifyDevPath);
if (!shopifyDevExists) {
console.log(
`Not copying docs to shopify-dev because it was not found at ${shopifyDevPath}.`,
);
process.exit();
}

await fs.cp(generatedDocsPath, shopifyDevDBPath, {recursive: true});
};
118 changes: 118 additions & 0 deletions packages/ui-extensions/docs/surfaces/customer-account/build-docs.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/* eslint-disable no-undef, no-console */
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make the script re-usable and pass in the right paths instead of duplicating? It would be better for maintainance.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't copy paste this script. Bring it up layers and make it reusable for everyone.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extracted all the shared functions, and let each service construct their own build steps

Copy link
Member

@alex-page alex-page Apr 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great progress but I think there is more to do. The directory customer-account could be an argument passed into the CLI that would remove a lot of duplicate code. I think there is a lot of duplication that still should be consolidated. extensions api version should be shared...

I think we should really aim at making one script with arguments that works for all surfaces instead of splitting it into reusable functions. I understand the time pressure folks are under so I am willing to be flexible but I think this still needs a second pass.

import fs from 'fs/promises';
import {existsSync} from 'fs';
import path from 'path';
import {fileURLToPath} from 'url';

import {
generateFiles,
copyGeneratedToShopifyDev,
replaceFileContent,
} from '../build-doc-shared.mjs';

const EXTENSIONS_API_VERSION = process.argv[2] || 'unstable';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const rootPath = path.join(__dirname, '../../..');
const docsRelativePath = 'docs/surfaces/customer-account';
const docsGeneratedRelativePath = 'docs/surfaces/customer-account/generated';
const srcRelativePath = 'src/surfaces/customer-account';
const docsPath = path.join(rootPath, docsRelativePath);
const srcPath = path.join(rootPath, srcRelativePath);
const generatedDocsPath = path.join(docsPath, 'generated');
const shopifyDevPath = path.join(rootPath, '../../../shopify-dev');
const shopifyDevDBPath = path.join(
shopifyDevPath,
'db/data/docs/templated_apis',
);

const generatedDocsDataFile = 'generated_docs_data.json';
const generatedStaticPagesFile = 'generated_static_pages.json';

const componentDefs = path.join(srcPath, 'components.d.ts');
const tempComponentDefs = path.join(srcPath, 'components.ts');

const tsconfig = 'tsconfig.docs.json';

const transformJson = async (filePath) => {
let jsonData = JSON.parse((await fs.readFile(filePath, 'utf8')).toString());

jsonData = jsonData.filter(Boolean);
await fs.writeFile(filePath, JSON.stringify(jsonData, null, 2));
};

const generateExtensionsDocs = async () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should be brought into the admin docs generator and be named something with customer accounts name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extracted some shared functions and make transformJson customizable 0459c0c

console.log(
`Building Customer Account UI Extensions docs for ${EXTENSIONS_API_VERSION} version`,
);

if (EXTENSIONS_API_VERSION === 'unstable') {
console.log(
"You can add a calver version argument (e.g. 'yarn docs:customer-account 2024-07') to generate the docs for a stable version.",
);
}

const outputDir = `${docsGeneratedRelativePath}/customer_account_ui_extensions/${EXTENSIONS_API_VERSION}`;

const scripts = [
`yarn tsc --project ${docsRelativePath}/${tsconfig} --moduleResolution node --target esNext --module CommonJS`,
`yarn generate-docs --input ./${docsRelativePath}/reference ./${docsRelativePath} ./${docsRelativePath}/../../../src/surfaces/customer-account/components --typesInput ./${srcRelativePath} --output ./${outputDir}`,
`yarn tsc ${docsRelativePath}/staticPages/*.doc.ts --moduleResolution node --target esNext --module CommonJS`,
`yarn generate-docs --isLandingPage --input ./${docsRelativePath}/staticPages --output ./${outputDir}`,
`yarn tsc ${docsRelativePath}/categories/*.doc.ts --moduleResolution node --target esNext --module CommonJS`,
`yarn generate-docs --isCategoryPage --input ./${docsRelativePath}/categories --output ./${outputDir}`,
];

await generateFiles({
scripts,
outputDir,
rootPath,
generatedDocsDataFile,
generatedStaticPagesFile,
transformJson,
});

// Replace 'unstable' with the exact API version in relative doc links
await replaceFileContent({
filePaths: path.join(outputDir, generatedDocsDataFile),
searchValue: '/docs/api//unstable/',
replaceValue: `/docs/api/customer-account-ui-extensions/${EXTENSIONS_API_VERSION}`,
});

await fs.cp(
path.join(docsPath, 'screenshots'),
path.join(
shopifyDevPath,
'app/assets/images/templated-apis-screenshots/customer-account-ui-extensions',
EXTENSIONS_API_VERSION,
),
{recursive: true},
);
};

try {
if (existsSync(generatedDocsPath)) {
await fs.rm(generatedDocsPath, {recursive: true});
}
await fs.copyFile(componentDefs, tempComponentDefs);
await replaceFileContent({
filePaths: tempComponentDefs,
searchValue: /typeof globalThis\.HTMLElement/g,
replaceValue: 'any',
});
await generateExtensionsDocs();
await copyGeneratedToShopifyDev({
generatedDocsPath,
shopifyDevPath,
shopifyDevDBPath,
});

await fs.rm(tempComponentDefs);
} catch (error) {
console.error(error);
console.log(error.stdout.toString());
console.log(error.stderr.toString());
process.exit(1);
}
Loading
Loading