-
Notifications
You must be signed in to change notification settings - Fork 63
Add API version update utilities #613
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import fs from 'fs/promises'; | ||
import path from 'path'; | ||
import fastGlob from 'fast-glob'; | ||
import { existsSync } from 'fs'; | ||
import { updateTomlValues } from '@shopify/toml-patch'; | ||
|
||
const ROOT_DIR = '.'; | ||
const FILE_PATTERN = '**/shopify.extension.toml.liquid'; | ||
const EXCLUDED_DIRS = ['samples', 'sample-apps', 'node_modules']; | ||
const OUTPUT_FILE = 'shopify.app.toml'; | ||
|
||
// Method to find all shopify.extension.toml.liquid files excluding specified directories | ||
async function findAllExtensionFiles() { | ||
return fastGlob(FILE_PATTERN, { | ||
cwd: ROOT_DIR, | ||
absolute: true, | ||
ignore: EXCLUDED_DIRS.map(dir => `${dir}/**`) | ||
}); | ||
} | ||
|
||
// Method to read existing shopify.app.toml if it exists | ||
async function readExistingToml() { | ||
try { | ||
if (existsSync(OUTPUT_FILE)) { | ||
return await fs.readFile(OUTPUT_FILE, 'utf8'); | ||
} | ||
return null; | ||
} catch (error) { | ||
console.error(`Error reading ${OUTPUT_FILE}:`, error); | ||
return null; | ||
} | ||
} | ||
|
||
// Main method to update the extension directories in shopify.app.toml | ||
async function configureExtensionDirectories() { | ||
try { | ||
const extensionFiles = await findAllExtensionFiles(); | ||
|
||
// Transform paths to be relative to root and exclude the filenames | ||
const extensionDirectories = extensionFiles.map(filePath => path.relative(ROOT_DIR, path.dirname(filePath))); | ||
|
||
// Remove duplicates | ||
const uniqueDirectories = [...new Set(extensionDirectories)]; | ||
|
||
// Read existing content | ||
const existingContent = await readExistingToml(); | ||
|
||
// Require an existing shopify.app.toml file | ||
if (!existingContent) { | ||
throw new Error(`${OUTPUT_FILE} not found. Please run 'shopify app config link' first to create the file.`); | ||
} | ||
|
||
// Use toml-patch to update the TOML content with extension directories | ||
const updatedContent = updateTomlValues(existingContent, [ | ||
[['extension_directories'], uniqueDirectories], | ||
[['web_directories'], []] | ||
]); | ||
|
||
// Write the updated content to the file | ||
await fs.writeFile(OUTPUT_FILE, updatedContent, 'utf8'); | ||
console.log(`Updated ${OUTPUT_FILE} with ${uniqueDirectories.length} extension directories`); | ||
} catch (error) { | ||
console.error(`Error updating extension directories:`, error); | ||
throw error; | ||
} | ||
} | ||
|
||
configureExtensionDirectories().catch(error => { | ||
console.error('Error configuring extension directories:', error); | ||
process.exit(1); | ||
}); |
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,114 @@ | ||||||||||||||
import fs from 'fs/promises'; | ||||||||||||||
import fastGlob from 'fast-glob'; | ||||||||||||||
import dayjs from 'dayjs'; | ||||||||||||||
import { updateTomlValues } from '@shopify/toml-patch'; | ||||||||||||||
|
||||||||||||||
const ROOT_DIR = '.'; | ||||||||||||||
const FILE_PATTERN = '**/shopify.extension.toml.liquid'; | ||||||||||||||
const LIQUID_PLACEHOLDER = 'LIQUID_PLACEHOLDER'; | ||||||||||||||
|
||||||||||||||
// Method to get the latest API version based on today's date | ||||||||||||||
function getLatestApiVersion() { | ||||||||||||||
const date = dayjs(); | ||||||||||||||
const year = date.year(); | ||||||||||||||
const month = date.month(); | ||||||||||||||
Comment on lines
+12
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can use JS dates directly instead of bringing in a new dependency.
Suggested change
|
||||||||||||||
const quarter = Math.floor(month / 3); | ||||||||||||||
|
||||||||||||||
const monthNum = quarter * 3 + 1; | ||||||||||||||
const paddedMonth = String(monthNum).padStart(2, '0'); | ||||||||||||||
|
||||||||||||||
return `${year}-${paddedMonth}`; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// Method to find all shopify.extension.toml.liquid files | ||||||||||||||
async function findAllExtensionFiles() { | ||||||||||||||
return fastGlob(FILE_PATTERN, { cwd: ROOT_DIR, absolute: true }); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// Function to preprocess liquid syntax for TOML parsing | ||||||||||||||
function preprocessLiquidSyntax(content) { | ||||||||||||||
const liquidExpressions = []; | ||||||||||||||
const placeholderContent = content.replace(/\{\{.*?\}\}|\{%\s.*?\s%\}/g, (match) => { | ||||||||||||||
liquidExpressions.push(match); | ||||||||||||||
return `{${LIQUID_PLACEHOLDER}:${liquidExpressions.length - 1}}`; | ||||||||||||||
}); | ||||||||||||||
return { placeholderContent, liquidExpressions }; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// Function to restore liquid syntax after TOML manipulation | ||||||||||||||
function restoreLiquidSyntax(content, liquidExpressions) { | ||||||||||||||
return content.replace(new RegExp(`\\{${LIQUID_PLACEHOLDER}:(\\d+)\\}`, 'g'), (match, index) => { | ||||||||||||||
return liquidExpressions[Number(index)]; | ||||||||||||||
}); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// Method to update the API version in the file using toml-patch | ||||||||||||||
async function updateApiVersion(filePath, latestVersion) { | ||||||||||||||
try { | ||||||||||||||
const content = await fs.readFile(filePath, 'utf8'); | ||||||||||||||
|
||||||||||||||
// Handle liquid templates if needed | ||||||||||||||
const isLiquidFile = filePath.endsWith('.liquid'); | ||||||||||||||
let liquidExpressions = []; | ||||||||||||||
let processedContent = content; | ||||||||||||||
|
||||||||||||||
if (isLiquidFile) { | ||||||||||||||
const processed = preprocessLiquidSyntax(content); | ||||||||||||||
processedContent = processed.placeholderContent; | ||||||||||||||
liquidExpressions = processed.liquidExpressions; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// Use toml-patch to update the API version | ||||||||||||||
const updates = [ | ||||||||||||||
[['api_version'], latestVersion] | ||||||||||||||
]; | ||||||||||||||
|
||||||||||||||
let updatedContent = updateTomlValues(processedContent, updates); | ||||||||||||||
|
||||||||||||||
// Restore liquid syntax if needed | ||||||||||||||
if (isLiquidFile) { | ||||||||||||||
updatedContent = restoreLiquidSyntax(updatedContent, liquidExpressions); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
await fs.writeFile(filePath, updatedContent, 'utf8'); | ||||||||||||||
console.log(`Updated API version in ${filePath} to ${latestVersion}`); | ||||||||||||||
|
||||||||||||||
} catch (error) { | ||||||||||||||
console.error(`Error updating API version in ${filePath}:`, error.message); | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like some functions run into this error because the liquid placeholder logic doesn't create a valid TOML file. For example order-routing/rust/location-rules/default/shopify.extension.toml.liquid:
Maybe we need a different approach that doesn't really expect a valid TOML file. Perhaps a simple find/replace would be sufficient here (assuming nobody does any Liquid shenanigans with the API version). |
||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// Main method to check and update API versions | ||||||||||||||
async function checkAndUpdateApiVersions() { | ||||||||||||||
const latestVersion = getLatestApiVersion(); | ||||||||||||||
console.log(`Latest API version: ${latestVersion}`); | ||||||||||||||
const extensionFiles = await findAllExtensionFiles(); | ||||||||||||||
console.log(`Found ${extensionFiles.length} extension files to check`); | ||||||||||||||
|
||||||||||||||
for (const filePath of extensionFiles) { | ||||||||||||||
try { | ||||||||||||||
const content = await fs.readFile(filePath, 'utf8'); | ||||||||||||||
const match = content.match(/api_version\s*=\s*"(\d{4}-\d{2})"/); | ||||||||||||||
|
||||||||||||||
if (match) { | ||||||||||||||
const currentVersion = match[1]; | ||||||||||||||
|
||||||||||||||
if (currentVersion !== latestVersion) { | ||||||||||||||
console.log(`Updating ${filePath} from ${currentVersion} to ${latestVersion}`); | ||||||||||||||
await updateApiVersion(filePath, latestVersion); | ||||||||||||||
} else { | ||||||||||||||
console.log(`API version in ${filePath} is already up to date (${currentVersion}).`); | ||||||||||||||
} | ||||||||||||||
} else { | ||||||||||||||
console.warn(`No API version found in ${filePath}`); | ||||||||||||||
} | ||||||||||||||
} catch (error) { | ||||||||||||||
console.error(`Error processing ${filePath}:`, error.message); | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
checkAndUpdateApiVersions().catch(error => { | ||||||||||||||
console.error('Error checking and updating API versions:', error); | ||||||||||||||
process.exit(1); | ||||||||||||||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import fs from 'fs/promises'; | ||
import { exec } from 'child_process'; | ||
import path from 'path'; | ||
import util from 'util'; | ||
import { existsSync } from 'fs'; | ||
import toml from '@iarna/toml'; | ||
|
||
const execPromise = util.promisify(exec); | ||
const APP_TOML_FILE = 'shopify.app.toml'; | ||
const COMMAND_TEMPLATE = 'shopify app function schema'; | ||
|
||
// Method to read shopify.app.toml and extract needed configuration | ||
async function getConfig() { | ||
try { | ||
if (!existsSync(APP_TOML_FILE)) { | ||
throw new Error(`${APP_TOML_FILE} does not exist. Run 'shopify app config link' first to create the file.`); | ||
} | ||
|
||
const content = await fs.readFile(APP_TOML_FILE, 'utf8'); | ||
|
||
// Parse the TOML content | ||
const parsedToml = toml.parse(content); | ||
|
||
const config = { | ||
clientId: '', | ||
directories: [] | ||
}; | ||
|
||
// Extract client_id if it exists | ||
if (parsedToml.client_id) { | ||
config.clientId = parsedToml.client_id; | ||
} | ||
|
||
// Extract extension directories if they exist | ||
if (parsedToml.extension_directories && Array.isArray(parsedToml.extension_directories)) { | ||
// Filter the directories to ensure they exist | ||
config.directories = parsedToml.extension_directories.filter(dir => { | ||
const exists = existsSync(dir); | ||
if (!exists) { | ||
console.warn(`Directory specified in config does not exist: ${dir}`); | ||
} | ||
return exists; | ||
}); | ||
} | ||
|
||
return config; | ||
} catch (error) { | ||
console.error(`Error reading ${APP_TOML_FILE}:`, error); | ||
throw error; | ||
} | ||
} | ||
|
||
// Method to run the schema update command for each directory | ||
async function updateSchemas() { | ||
try { | ||
const config = await getConfig(); | ||
|
||
if (!config.clientId) { | ||
throw new Error('Client ID not found in shopify.app.toml'); | ||
} | ||
|
||
if (config.directories.length === 0) { | ||
console.warn('No valid extension directories found in shopify.app.toml'); | ||
return; | ||
} | ||
|
||
console.log(`Found ${config.directories.length} extension directories`); | ||
console.log(`Using client ID: ${config.clientId}`); | ||
|
||
for (const dir of config.directories) { | ||
try { | ||
const command = `${COMMAND_TEMPLATE} --path ${dir}`; | ||
console.log(`\nUpdating schema for: ${dir}`); | ||
console.log(`Running: ${command}`); | ||
|
||
const { stdout, stderr } = await execPromise(command); | ||
if (stdout) console.log(`Output: ${stdout.trim()}`); | ||
if (stderr && !stderr.includes('warning')) console.error(`Error: ${stderr.trim()}`); | ||
} catch (error) { | ||
console.error(`Failed to update schema for ${dir}:`, error.message); | ||
} | ||
} | ||
|
||
console.log("\nSchema update completed"); | ||
} catch (error) { | ||
console.error('Failed to update schemas:', error); | ||
} | ||
} | ||
|
||
updateSchemas().catch(error => { | ||
console.error('Unhandled error:', error); | ||
process.exit(1); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we only want to update the versions for Liquid files? This would exclude the sample apps.