-
Notifications
You must be signed in to change notification settings - Fork 256
Expand file tree
/
Copy paththeme.ts
More file actions
130 lines (118 loc) · 4.73 KB
/
theme.ts
File metadata and controls
130 lines (118 loc) · 4.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import {createExtensionSpecification} from '../specification.js'
import {BaseSchema} from '../schemas.js'
import {themeExtensionFiles} from '../../../utilities/extensions/theme.js'
import {ExtensionInstance} from '../extension-instance.js'
import {fileSize} from '@shopify/cli-kit/node/fs'
import {dirname, relativePath} from '@shopify/cli-kit/node/path'
import {AbortError} from '@shopify/cli-kit/node/error'
import {outputContent, outputToken} from '@shopify/cli-kit/node/output'
const themeSpec = createExtensionSpecification({
identifier: 'theme',
schema: BaseSchema,
partnersWebIdentifier: 'theme_app_extension',
graphQLType: 'theme_app_extension',
clientSteps: [
{
lifecycle: 'build',
steps: [
{id: 'build-theme', name: 'Build Theme Extension', type: 'build_theme', config: {}},
{id: 'bundle-theme', name: 'Bundle Theme Extension', type: 'bundle_theme', config: {}},
],
},
],
appModuleFeatures: (_) => {
return ['theme']
},
deployConfig: async () => {
return {theme_extension: {files: {}}}
},
preDeployValidation: async (extension) => {
return validateThemeExtension(extension)
},
})
export default themeSpec
// Theme extension validation helpers
interface FilenameValidation {
validator: RegExp
failureMessage: (filename: string) => string
}
const kilobytes = 1024
const megabytes = kilobytes * 1024
const BUNDLE_SIZE_LIMIT_MB = 10
const BUNDLE_SIZE_LIMIT = BUNDLE_SIZE_LIMIT_MB * megabytes
const LIQUID_SIZE_LIMIT_KB = 500
const LIQUID_SIZE_LIMIT = LIQUID_SIZE_LIMIT_KB * kilobytes
const SUPPORTED_ASSET_EXTS = ['.jpg', '.jpeg', '.json', '.js', '.css', '.png', '.svg', '.wasm']
const SUPPORTED_LOCALE_EXTS = ['.json']
const SUPPORTED_EXTS: {[dirname: string]: FilenameValidation} = {
assets: {
validator: new RegExp(`${SUPPORTED_ASSET_EXTS.join('|')}$`),
failureMessage: (_filename: string) =>
`Only these filetypes are supported in assets: ${SUPPORTED_ASSET_EXTS.join(', ')}`,
},
blocks: {
validator: /.liquid$/,
failureMessage: (_filename: string) => `Only .liquid files are allowed in blocks.`,
},
locales: {
validator: new RegExp(`${SUPPORTED_LOCALE_EXTS.join('|')}$`),
failureMessage: (_filename: string) =>
`Only these filetypes are supported in locales: ${SUPPORTED_LOCALE_EXTS.join(', ')}`,
},
snippets: {
validator: /.liquid$/,
failureMessage: (_filename: string) => `Only .liquid files are allowed in snippets.`,
},
}
const SUPPORTED_BUCKETS = Object.keys(SUPPORTED_EXTS)
async function validateThemeExtension(extension: ExtensionInstance): Promise<void> {
const themeFiles = await themeExtensionFiles(extension)
const liquidBytes: number[] = []
const extensionBytes: number[] = []
await Promise.all(
themeFiles.map(async (filepath) => {
const relativePathName = relativePath(extension.directory, filepath)
const directoryName = dirname(relativePathName)
validateFile(relativePathName, directoryName)
const filesize = await fileSize(filepath)
extensionBytes.push(filesize)
if (['blocks', 'snippets'].includes(directoryName)) liquidBytes.push(filesize)
}),
)
validateExtensionBytes(arraySum(extensionBytes))
validateLiquidBytes(arraySum(liquidBytes))
}
function validateExtensionBytes(extensionBytesTotal: number): void {
if (extensionBytesTotal > BUNDLE_SIZE_LIMIT) {
const humanBundleSize = `${(extensionBytesTotal / megabytes).toFixed(2)} MB`
throw new AbortError(
`Your theme app extension exceeds the file size limit (${BUNDLE_SIZE_LIMIT_MB} MB). It's currently ${humanBundleSize}.`,
`Reduce your total file size and try again.`,
)
}
}
function validateLiquidBytes(liquidBytesTotal: number): void {
if (liquidBytesTotal > LIQUID_SIZE_LIMIT) {
const humanLiquidSize = `${(liquidBytesTotal / kilobytes).toFixed(2)} kB`
throw new AbortError(
`Your theme app extension exceeds the total liquid file size limit (${LIQUID_SIZE_LIMIT_KB} kB). It's currently ${humanLiquidSize}.`,
`Reduce your total file size and try again.`,
)
}
}
function validateFile(filepath: string, dirname: string): void {
if (!SUPPORTED_BUCKETS.includes(dirname)) {
throw new AbortError(
outputContent`Your theme app extension includes files in an unsupported directory, ${outputToken.path(dirname)}`,
`Make sure all theme app extension files are in the supported directories: ${SUPPORTED_BUCKETS.join(', ')}`,
)
}
const filenameValidation = SUPPORTED_EXTS[dirname]!
if (!filepath.match(filenameValidation.validator)) {
throw new AbortError(`Invalid filename in your theme app extension: ${filepath}
${filenameValidation.failureMessage(filepath)}`)
}
}
function arraySum(array: number[]): number {
return array.reduce((num1, num2) => num1 + num2, 0)
}