@shopify/theme-check-docs-updater
Advanced tools
Comparing version 2.5.0 to 2.5.1
# @shopify/theme-check-docs-updater | ||
## 2.5.1 | ||
### Patch Changes | ||
- 312d804: Add logging support to the docs loader | ||
- Updated dependencies [ec1fbd7] | ||
- @shopify/theme-check-common@2.5.1 | ||
## 2.5.0 | ||
@@ -4,0 +12,0 @@ |
@@ -1,2 +0,2 @@ | ||
{ "revision": "0fa0bcaf791e40fa3770dff923c9578424201a88" } | ||
{ "revision": "770bfd56080c022ede5dbfd7682ce8ccf43b5f08" } | ||
@@ -5,14 +5,14 @@ { | ||
"schemas": [ | ||
{ | ||
"uri": "theme/translations.json", | ||
"fileMatch": ["locales/*.json"] | ||
}, | ||
{ | ||
"uri": "theme/section.json", | ||
"fileMatch": ["sections/*.liquid"] | ||
}, | ||
{ | ||
"uri": "theme/input_settings.json" | ||
} | ||
{ "fileMatch": ["locales/*.json"], "uri": "theme/translations.json" }, | ||
{ "fileMatch": ["blocks/*.liquid"], "uri": "theme/theme_block.json" }, | ||
{ "fileMatch": ["config/settings_schema.json"], "uri": "theme/theme_settings.json" }, | ||
{ "fileMatch": ["sections/*.liquid"], "uri": "theme/section.json" }, | ||
{ "uri": "theme/settings.json" }, | ||
{ "uri": "theme/setting.json" }, | ||
{ "uri": "theme/default_setting_values.json" }, | ||
{ "uri": "theme/app_block_entry.json" }, | ||
{ "uri": "theme/theme_block_entry.json" }, | ||
{ "uri": "theme/targetted_block_entry.json" }, | ||
{ "uri": "theme/local_block_entry.json" } | ||
] | ||
} |
@@ -7,104 +7,116 @@ { | ||
"properties": { | ||
"$schema": { "type": "string" }, | ||
"name": { | ||
"type": "string", | ||
"description": "The section title shown in the theme editor." | ||
"description": "The name attribute determines the section title that is shown in the theme editor.", | ||
"markdownDescription": "The `name` attribute determines the section title that is shown in the theme editor.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#name)" | ||
}, | ||
"tag": { | ||
"type": "string", | ||
"description": "The HTML element to use for the section.", | ||
"description": "The HTML element that is used to wrap the section.", | ||
"markdownDescription": "The HTML element that is used to wrap the section.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#tag)", | ||
"enum": ["article", "aside", "div", "footer", "header", "section"] | ||
}, | ||
"class": { | ||
"type": "string", | ||
"description": "Additional CSS class for the section." | ||
"description": "Additional CSS class for the section.", | ||
"markdownDescription": "Additional CSS class for the section.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#class)" | ||
}, | ||
"limit": { | ||
"type": "integer", | ||
"description": "The number of times a section can be added to a template or section group.", | ||
"markdownDescription": "The number of times a section can be added to a template or section group.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#limit)", | ||
"minimum": 1, | ||
"maximum": 2 | ||
}, | ||
"settings": { | ||
"description": "Section specific settings.", | ||
"$ref": "./input_settings.json" | ||
"$ref": "./settings.json" | ||
}, | ||
"max_blocks": { | ||
"type": "integer", | ||
"description": "The maximum number of blocks allowed in the section.", | ||
"description": "There's a limit of 50 blocks per section. You can specify a lower limit with the max_blocks attribute", | ||
"markdownDescription": "There's a limit of 50 blocks per section. You can specify a lower limit with the `max_blocks` attribute.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#max_blocks)", | ||
"minimum": 1, | ||
"maximum": 50 | ||
}, | ||
"blocks": { | ||
"type": "array", | ||
"description": "Section blocks.", | ||
"description": "Blocks are reusable modules of content that can be added, removed, and reordered within a section.", | ||
"markdownDescription": "Blocks are reusable modules of content that can be added, removed, and reordered within a section.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#blocks)", | ||
"properties": { | ||
"type": { | ||
"description": "The block type. Can be one of the following values: @theme, @app, or a custom block type.", | ||
"markdownDescription": "The block type. Can be one of the following values: @theme, @app, or a custom block type.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#blocks)" | ||
} | ||
}, | ||
"items": { | ||
"type": "object", | ||
"required": ["type"], | ||
"properties": { | ||
"name": { | ||
"type": "string", | ||
"description": "The block name." | ||
}, | ||
"type": { | ||
"type": "string", | ||
"description": "The block type." | ||
}, | ||
"settings": { | ||
"description": "Block settings.", | ||
"$ref": "./input_settings.json" | ||
"description": "The type of block that can be added to this block.", | ||
"markdownDescription": "The type of block that can be added to this block.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/blocks/theme-blocks/schema#blocks)" | ||
} | ||
}, | ||
"if": { | ||
"properties": { | ||
"type": { | ||
"const": "@app" | ||
"$comment": "The allOf rule here exists because that's how we do discriminated unions in JSON schemas. If a rule matches, that rule will be used to document the type property. Otherwise we fallback to the docs above.", | ||
"allOf": [ | ||
{ | ||
"if": { "required": ["type"], "properties": { "type": { "const": "@theme" } } }, | ||
"then": { "$ref": "./theme_block_entry.json" } | ||
}, | ||
{ | ||
"if": { "required": ["type"], "properties": { "type": { "const": "@app" } } }, | ||
"then": { "$ref": "./app_block_entry.json" } | ||
}, | ||
{ | ||
"if": { | ||
"required": ["type"], | ||
"properties": { | ||
"type": { | ||
"type": "string", | ||
"not": { "enum": ["@app", "@theme"] } | ||
} | ||
} | ||
}, | ||
"then": { | ||
"oneOf": [ | ||
{ "$ref": "./targetted_block_entry.json" }, | ||
{ "$ref": "./local_block_entry.json" } | ||
] | ||
} | ||
} | ||
}, | ||
"then": { | ||
"required": ["type"] | ||
}, | ||
"else": { | ||
"required": ["name", "type"] | ||
} | ||
] | ||
} | ||
}, | ||
"presets": { | ||
"type": "array", | ||
"description": "Section presets.", | ||
"description": "Presets are default configurations of sections that enable users to easily add a section to a JSON template through the theme editor.", | ||
"markdownDescription": "Presets are default configurations of sections that enable users to easily add a section to a [JSON template](https://shopify.dev/docs/themes/architecture/templates/json-templates) through the theme editor.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#presets)", | ||
"items": { | ||
"type": "object", | ||
"required": ["name"], | ||
"properties": { | ||
"name": { | ||
"type": "string", | ||
"description": "The preset name." | ||
"description": "The preset name, which will show in the Add section picker of the theme editor.", | ||
"markdownDescription": "The preset name, which will show in the Add section picker of the theme editor.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#presets)" | ||
}, | ||
"settings": { | ||
"type": "object", | ||
"description": "Default values for settings.", | ||
"additionalProperties": { | ||
"anyOf": [ | ||
{ | ||
"type": "number" | ||
}, | ||
{ | ||
"type": "boolean" | ||
}, | ||
{ | ||
"type": "string" | ||
}, | ||
{ | ||
"type": "array", | ||
"items": { | ||
"type": "string" | ||
} | ||
} | ||
] | ||
} | ||
"$ref": "./default_setting_values.json" | ||
}, | ||
"blocks": { | ||
"type": "array", | ||
"description": "Default blocks.", | ||
"description": "Default blocks configurations to ship with this preset.", | ||
"markdownDescription": "Default blocks configurations to ship with this preset.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#presets)", | ||
"items": { | ||
"type": "object", | ||
"required": ["type"], | ||
"additionalProperties": false, | ||
"properties": { | ||
@@ -116,64 +128,29 @@ "type": { | ||
"settings": { | ||
"type": "object", | ||
"description": "Block settings.", | ||
"additionalProperties": { | ||
"anyOf": [ | ||
{ | ||
"type": "number" | ||
}, | ||
{ | ||
"type": "boolean" | ||
}, | ||
{ | ||
"type": "string" | ||
}, | ||
{ | ||
"type": "array", | ||
"items": { | ||
"type": "string" | ||
} | ||
} | ||
] | ||
} | ||
"$ref": "./default_setting_values.json" | ||
}, | ||
"blocks": { | ||
"$ref": "#/properties/presets/items/properties/blocks" | ||
} | ||
}, | ||
"required": ["type"] | ||
} | ||
} | ||
} | ||
}, | ||
"required": ["name"] | ||
} | ||
} | ||
}, | ||
"default": { | ||
"type": "object", | ||
"description": "Default configuration for statically rendered sections.", | ||
"markdownDescription": "Default configuration for statically rendered sections.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#default)", | ||
"properties": { | ||
"settings": { | ||
"type": "object", | ||
"description": "Default values for settings.", | ||
"additionalProperties": { | ||
"anyOf": [ | ||
{ | ||
"type": "number" | ||
}, | ||
{ | ||
"type": "boolean" | ||
}, | ||
{ | ||
"type": "string" | ||
}, | ||
{ | ||
"type": "array", | ||
"items": { | ||
"type": "string" | ||
} | ||
} | ||
] | ||
} | ||
"$ref": "./default_setting_values.json" | ||
}, | ||
"blocks": { | ||
"type": "array", | ||
"description": "Default blocks.", | ||
"description": "Default blocks configurations to ship with this default.", | ||
"markdownDescription": "Default blocks configurations to ship with this default.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#default)", | ||
"items": { | ||
"type": "object", | ||
"required": ["type"], | ||
"properties": { | ||
@@ -185,26 +162,5 @@ "type": { | ||
"settings": { | ||
"type": "object", | ||
"description": "Block settings.", | ||
"additionalProperties": { | ||
"anyOf": [ | ||
{ | ||
"type": "number" | ||
}, | ||
{ | ||
"type": "boolean" | ||
}, | ||
{ | ||
"type": "string" | ||
}, | ||
{ | ||
"type": "array", | ||
"items": { | ||
"type": "string" | ||
} | ||
} | ||
] | ||
} | ||
"$ref": "./default_setting_values.json" | ||
} | ||
}, | ||
"required": ["type"] | ||
} | ||
} | ||
@@ -214,5 +170,7 @@ } | ||
}, | ||
"locales": { | ||
"type": "object", | ||
"description": "A set of translated strings for the section.", | ||
"description": "Sections can provide their own set of translated strings through the locales object. This is separate from the locales directory of the theme, which makes it a useful feature for sections that are meant to be installed on multiple themes or shops.", | ||
"markdownDescription": "Sections can provide their own set of translated strings through the `locales` object. This is separate from the `locales` directory of the theme, which makes it a useful feature for sections that are meant to be installed on multiple themes or shops.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#locales)", | ||
"additionalProperties": { | ||
@@ -225,11 +183,16 @@ "type": "object", | ||
}, | ||
"enabled_on": { | ||
"description": "Restrict the section to certain template page types and section group types.", | ||
"markdownDescription": "Restrict the section to certain template page types and section group types.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#enabled_on)", | ||
"$ref": "#/definitions/sectionToggle" | ||
}, | ||
"disabled_on": { | ||
"description": "Prevent the section from being used on certain template page types and section group types.", | ||
"markdownDescription": "Prevent the section from being used on certain template page types and section group types.\n\n---\n\n[Shopify reference](https://shopify.dev/docs/themes/architecture/sections/section-schema#disabled_on)", | ||
"$ref": "#/definitions/sectionToggle" | ||
} | ||
}, | ||
"definitions": { | ||
@@ -239,2 +202,3 @@ "sectionToggle": { | ||
"description": "Restrict the section to certain template page types and section group types.", | ||
"additionalProperties": false, | ||
"properties": { | ||
@@ -280,6 +244,5 @@ "templates": { | ||
} | ||
}, | ||
"additionalProperties": false | ||
} | ||
} | ||
} | ||
} |
@@ -0,1 +1,2 @@ | ||
import { Logger } from './utils'; | ||
export declare const root: string; | ||
@@ -10,4 +11,5 @@ export declare const ThemeLiquidDocsRoot = "https://raw.githubusercontent.com/Shopify/theme-liquid-docs/main"; | ||
}; | ||
export declare function downloadSchema(relativeUri: string, destination?: string): Promise<string>; | ||
export declare function downloadResource(resource: Resource | 'latest', destination?: string): Promise<string>; | ||
export declare function downloadSchema(relativeUri: string, destination?: string, log?: Logger): Promise<string>; | ||
export declare function downloadResource(resource: Resource | 'latest', destination?: string, log?: Logger): Promise<string>; | ||
export declare function download(path: string, log: Logger): Promise<string>; | ||
export declare function resourcePath(resource: Resource | 'latest', destination?: string): string; | ||
@@ -18,2 +20,2 @@ export declare function resourceUrl(resource: Resource | 'latest'): string; | ||
export declare function exists(path: string): Promise<boolean>; | ||
export declare function downloadThemeLiquidDocs(destination?: string): Promise<void>; | ||
export declare function downloadThemeLiquidDocs(destination: string, log: Logger): Promise<void>; |
@@ -6,3 +6,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.downloadThemeLiquidDocs = exports.exists = exports.schemaUrl = exports.schemaPath = exports.resourceUrl = exports.resourcePath = exports.downloadResource = exports.downloadSchema = exports.Manifests = exports.Resources = exports.ThemeLiquidDocsSchemaRoot = exports.ThemeLiquidDocsRoot = exports.root = void 0; | ||
exports.downloadThemeLiquidDocs = exports.exists = exports.schemaUrl = exports.schemaPath = exports.resourceUrl = exports.resourcePath = exports.download = exports.downloadResource = exports.downloadSchema = exports.Manifests = exports.Resources = exports.ThemeLiquidDocsSchemaRoot = exports.ThemeLiquidDocsRoot = exports.root = void 0; | ||
const env_paths_1 = __importDefault(require("env-paths")); | ||
@@ -12,2 +12,3 @@ const node_fetch_1 = __importDefault(require("node-fetch")); | ||
const node_path_1 = __importDefault(require("node:path")); | ||
const utils_1 = require("./utils"); | ||
const paths = (0, env_paths_1.default)('theme-liquid-docs'); | ||
@@ -38,20 +39,34 @@ exports.root = paths.cache; | ||
}; | ||
async function downloadSchema(relativeUri, destination = exports.root) { | ||
async function downloadSchema(relativeUri, destination = exports.root, log = utils_1.noop) { | ||
const remotePath = schemaUrl(relativeUri); | ||
const localPath = schemaPath(relativeUri, destination); | ||
const res = await (0, node_fetch_1.default)(remotePath); | ||
const text = await res.text(); | ||
promises_1.default.writeFile(localPath, text, 'utf8'); | ||
const text = await download(remotePath, log); | ||
await promises_1.default.writeFile(localPath, text, 'utf8'); | ||
return text; | ||
} | ||
exports.downloadSchema = downloadSchema; | ||
async function downloadResource(resource, destination = exports.root) { | ||
async function downloadResource(resource, destination = exports.root, log = utils_1.noop) { | ||
const remotePath = resourceUrl(resource); | ||
const localPath = resourcePath(resource, destination); | ||
const res = await (0, node_fetch_1.default)(remotePath); | ||
const text = await res.text(); | ||
promises_1.default.writeFile(localPath, text, 'utf8'); | ||
const text = await download(remotePath, log); | ||
await promises_1.default.writeFile(localPath, text, 'utf8'); | ||
return text; | ||
} | ||
exports.downloadResource = downloadResource; | ||
async function download(path, log) { | ||
if (path.startsWith('file:')) { | ||
return await promises_1.default | ||
.readFile(path.replace(/^file:/, ''), 'utf8') | ||
.then((0, utils_1.tap)(() => log(`Using local file: ${path}`))) | ||
.catch((error) => { | ||
log(`Failed to read local file: ${path}`); | ||
throw error; | ||
}); | ||
} | ||
else { | ||
const res = await (0, node_fetch_1.default)(path); | ||
return res.text(); | ||
} | ||
} | ||
exports.download = download; | ||
function resourcePath(resource, destination = exports.root) { | ||
@@ -63,3 +78,6 @@ return node_path_1.default.join(destination, `${resource}.json`); | ||
const relativePath = THEME_LIQUID_DOCS[resource]; | ||
return `${exports.ThemeLiquidDocsRoot}/${relativePath}`; | ||
const resourceRoot = process.env.SHOPIFY_TLD_ROOT | ||
? `file:${process.env.SHOPIFY_TLD_ROOT}` | ||
: exports.ThemeLiquidDocsRoot; | ||
return `${resourceRoot}/${relativePath}`; | ||
} | ||
@@ -72,3 +90,6 @@ exports.resourceUrl = resourceUrl; | ||
function schemaUrl(relativeUri) { | ||
return `${exports.ThemeLiquidDocsSchemaRoot}/${relativeUri}`; | ||
const schemaRoot = process.env.SHOPIFY_TLD_ROOT | ||
? `file:${process.env.SHOPIFY_TLD_ROOT}` | ||
: exports.ThemeLiquidDocsRoot; | ||
return `${schemaRoot}/schemas/${relativeUri}`; | ||
} | ||
@@ -86,3 +107,3 @@ exports.schemaUrl = schemaUrl; | ||
exports.exists = exists; | ||
async function downloadThemeLiquidDocs(destination = exports.root) { | ||
async function downloadThemeLiquidDocs(destination, log) { | ||
if (!(await exists(destination))) { | ||
@@ -93,3 +114,8 @@ await promises_1.default.mkdir(destination); | ||
const resourceContents = await Promise.all(resources.map((file) => { | ||
return downloadResource(file, destination); | ||
return downloadResource(file, destination, log) | ||
.then((0, utils_1.tap)(() => log(`Successfully downloaded latest resource:\n\t${resourceUrl(file)}\n\t> ${resourcePath(file, destination)}`))) | ||
.catch((error) => { | ||
log(`Failed to download latest resource:\n\t${resourceUrl(file)} to\n\t${resourcePath(file, destination)}\n${error}`); | ||
throw error; | ||
}); | ||
})); | ||
@@ -101,3 +127,8 @@ const manifests = Object.values(exports.Manifests) | ||
const relativeUris = manifests.flatMap((manifest) => manifest.schemas.map((schema) => schema.uri)); | ||
await Promise.all(unique(relativeUris).map((uri) => downloadSchema(uri, destination))); | ||
await Promise.all(unique(relativeUris).map((uri) => downloadSchema(uri, destination, log) | ||
.then((0, utils_1.tap)(() => log(`Successfully downloaded schema:\n\t${schemaUrl(uri)}\n\t> ${schemaPath(uri, destination)}`))) | ||
.catch((error) => { | ||
log(`Failed to download schema: ${uri}, ${error}`); | ||
throw error; | ||
}))); | ||
} | ||
@@ -104,0 +135,0 @@ exports.downloadThemeLiquidDocs = downloadThemeLiquidDocs; |
import { FilterEntry, JsonValidationSet, ObjectEntry, SchemaDefinition, TagEntry, ThemeDocset, Translations } from '@shopify/theme-check-common'; | ||
type Logger = (message: string) => void; | ||
import { Logger } from './utils'; | ||
export declare class ThemeLiquidDocsManager implements ThemeDocset, JsonValidationSet { | ||
@@ -26,2 +26,1 @@ private log; | ||
} | ||
export {}; |
@@ -15,22 +15,22 @@ "use strict"; | ||
this.filters = (0, utils_1.memo)(async () => { | ||
return findSuitableResource(this.loaders('filters'), JSON.parse, []); | ||
return findSuitableResource(this.loaders('filters'), JSON.parse, [], this.log); | ||
}); | ||
this.objects = (0, utils_1.memo)(async () => { | ||
return findSuitableResource(this.loaders('objects'), JSON.parse, []); | ||
return findSuitableResource(this.loaders('objects'), JSON.parse, [], this.log); | ||
}); | ||
this.tags = (0, utils_1.memo)(async () => { | ||
return findSuitableResource(this.loaders('tags'), JSON.parse, []); | ||
return findSuitableResource(this.loaders('tags'), JSON.parse, [], this.log); | ||
}); | ||
this.systemTranslations = (0, utils_1.memo)(async () => { | ||
return findSuitableResource(this.loaders('shopify_system_translations'), JSON.parse, {}); | ||
return findSuitableResource(this.loaders('shopify_system_translations'), JSON.parse, {}, this.log); | ||
}); | ||
this.schemas = (0, utils_1.memoize)((mode) => findSuitableResource(this.loaders(themeLiquidDocsDownloader_1.Manifests[mode]), JSON.parse, { | ||
schemas: [], | ||
}).then((manifest) => { | ||
}, this.log).then((manifest) => { | ||
return Promise.all(manifest.schemas.map(async (schemaDefinition) => ({ | ||
uri: `${themeLiquidDocsDownloader_1.ThemeLiquidDocsSchemaRoot}/${schemaDefinition.uri}`, | ||
fileMatch: schemaDefinition.fileMatch, | ||
schema: await findSuitableResource(this.schemaLoaders(schemaDefinition.uri), identity, ''), | ||
schema: await findSuitableResource(this.schemaLoaders(schemaDefinition.uri), utils_1.identity, '', this.log), | ||
}))); | ||
}), (identity)); | ||
}), (utils_1.identity)); | ||
/** | ||
@@ -49,11 +49,12 @@ * The setup method checks that the latest revision matches the one from | ||
const local = await this.latestRevision(); | ||
await (0, themeLiquidDocsDownloader_1.downloadResource)('latest'); | ||
await (0, themeLiquidDocsDownloader_1.downloadResource)('latest', themeLiquidDocsDownloader_1.root, this.log); | ||
const remote = await this.latestRevision(); | ||
if (local !== remote) { | ||
await (0, themeLiquidDocsDownloader_1.downloadThemeLiquidDocs)(); | ||
await (0, themeLiquidDocsDownloader_1.downloadThemeLiquidDocs)(themeLiquidDocsDownloader_1.root, this.log); | ||
} | ||
} | ||
catch (error) { | ||
if (error instanceof Error) | ||
this.log(error.message); | ||
if (error instanceof Error) { | ||
this.log(`Failed to setup with the following error: ${error.message}`); | ||
} | ||
return; | ||
@@ -65,3 +66,3 @@ } | ||
var _a; | ||
const latest = await findSuitableResource([() => this.load('latest')], JSON.parse, {}); | ||
const latest = await findSuitableResource([loader(() => this.load('latest'), 'loadLatestRevision')], JSON.parse, {}, this.log); | ||
return (_a = latest['revision']) !== null && _a !== void 0 ? _a : ''; | ||
@@ -73,3 +74,3 @@ } | ||
await this.setup(); | ||
return this.load(name); | ||
return this.load(name).then((0, utils_1.tap)(() => this.log(`Loaded resource from ${(0, themeLiquidDocsDownloader_1.resourcePath)(name)}`))); | ||
} | ||
@@ -80,13 +81,23 @@ async load(name) { | ||
async loadSchema(relativeUri) { | ||
return promises_1.default.readFile((0, themeLiquidDocsDownloader_1.schemaPath)(relativeUri), 'utf8'); | ||
return promises_1.default | ||
.readFile((0, themeLiquidDocsDownloader_1.schemaPath)(relativeUri), 'utf8') | ||
.then((0, utils_1.tap)(() => this.log(`Loaded schema from ${(0, themeLiquidDocsDownloader_1.schemaPath)(relativeUri)}`))); | ||
} | ||
loaders(name) { | ||
return [() => this.loadResource(name), () => fallbackResource(name)]; | ||
return [ | ||
loader(() => this.loadResource(name), `loadResource(${name})`), | ||
loader(() => fallbackResource(name, this.log), `fallbackResource(${name})`), | ||
]; | ||
} | ||
schemaLoaders(relativeUri) { | ||
return [() => this.loadSchema(relativeUri), () => fallbackSchema(relativeUri)]; | ||
return [ | ||
loader(() => this.loadSchema(relativeUri), `loadSchema(${relativeUri})`), | ||
loader(() => fallbackSchema(relativeUri, this.log), `fallbackSchema(${relativeUri})`), | ||
]; | ||
} | ||
} | ||
exports.ThemeLiquidDocsManager = ThemeLiquidDocsManager; | ||
const identity = (x) => x; | ||
function loader(fn, loaderName) { | ||
return Object.assign(fn, { loaderName }); | ||
} | ||
/** | ||
@@ -103,3 +114,3 @@ * Find the first resource that can be loaded and transformed. | ||
*/ | ||
async function findSuitableResource(dataLoaders, transform, defaultValue) { | ||
async function findSuitableResource(dataLoaders, transform, defaultValue, log) { | ||
for (const loader of dataLoaders) { | ||
@@ -109,3 +120,4 @@ try { | ||
} | ||
catch (_) { | ||
catch (e) { | ||
log(`Failed to load or transform ${loader.loaderName} with the following error:\n${e instanceof Error ? e.message : e}`); | ||
continue; | ||
@@ -129,10 +141,17 @@ } | ||
/** Returns the at-build-time path to the fallback data file. */ | ||
async function fallbackResource(name) { | ||
return promises_1.default.readFile(node_path_1.default.resolve(dataRoot(), `${name}.json`), 'utf8'); | ||
async function fallbackResource(name, log) { | ||
const sourcePath = node_path_1.default.resolve(dataRoot(), `${name}.json`); | ||
return promises_1.default | ||
.readFile(sourcePath, 'utf8') | ||
.then((0, utils_1.tap)(() => log(`Loaded fallback resource\n\t${name} from\n\t${sourcePath}`))); | ||
} | ||
/** Returns the at-build-time path to the fallback schema file. */ | ||
async function fallbackSchema( | ||
/** e.g. themes/section.json */ relativeUri) { | ||
return promises_1.default.readFile(node_path_1.default.resolve(dataRoot(), node_path_1.default.basename(relativeUri)), 'utf8'); | ||
/** e.g. themes/section.json */ | ||
relativeUri, log) { | ||
const sourcePath = node_path_1.default.resolve(dataRoot(), node_path_1.default.basename(relativeUri)); | ||
return promises_1.default | ||
.readFile(sourcePath, 'utf8') | ||
.then((0, utils_1.tap)(() => log(`Loaded fallback schema\n\t${relativeUri} from\n\t${sourcePath}`))); | ||
} | ||
//# sourceMappingURL=themeLiquidDocsManager.js.map |
@@ -0,2 +1,5 @@ | ||
export type Logger = (message: string) => void; | ||
export declare const noop: () => void; | ||
export declare const identity: <T>(x: T) => T; | ||
export declare const tap: <T>(tappingFunction: (x: T) => void) => (x: T) => T; | ||
/** Returns a cached version of a function. Only caches one result. */ | ||
@@ -3,0 +6,0 @@ export declare function memo<F extends (...args: any[]) => any>(fn: F): (...args: ArgumentTypes<F>) => ReturnType<F>; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.memoize = exports.memo = exports.noop = void 0; | ||
exports.memoize = exports.memo = exports.tap = exports.identity = exports.noop = void 0; | ||
const noop = () => { }; | ||
exports.noop = noop; | ||
const identity = (x) => x; | ||
exports.identity = identity; | ||
const tap = (tappingFunction) => { | ||
return (x) => { | ||
tappingFunction(x); | ||
return x; | ||
}; | ||
}; | ||
exports.tap = tap; | ||
/** Returns a cached version of a function. Only caches one result. */ | ||
@@ -7,0 +16,0 @@ function memo(fn) { |
{ | ||
"name": "@shopify/theme-check-docs-updater", | ||
"version": "2.5.0", | ||
"version": "2.5.1", | ||
"description": "Scripts to initialize theme-check data with assets from the theme-liquid-docs repo.", | ||
@@ -33,3 +33,3 @@ "main": "dist/index.js", | ||
"dependencies": { | ||
"@shopify/theme-check-common": "^2.5.0", | ||
"@shopify/theme-check-common": "^2.5.1", | ||
"env-paths": "^2.2.1", | ||
@@ -36,0 +36,0 @@ "node-fetch": "^2.6.11" |
@@ -5,6 +5,3 @@ #!/usr/bin/env node | ||
const fs = require('fs'); | ||
const { downloadThemeLiquidDocs, root } = require(path.resolve( | ||
__dirname, | ||
'../dist', | ||
)); | ||
const { downloadThemeLiquidDocs, root } = require(path.resolve(__dirname, '../dist')); | ||
@@ -36,3 +33,3 @@ // Get the command line arguments | ||
downloadThemeLiquidDocs(args[1]); | ||
downloadThemeLiquidDocs(args[1], console.error.bind(console)); | ||
@@ -39,0 +36,0 @@ break; |
@@ -6,2 +6,3 @@ import { Mode } from '@shopify/theme-check-common'; | ||
import path from 'node:path'; | ||
import { Logger, noop, tap } from './utils'; | ||
@@ -40,22 +41,41 @@ const paths = envPaths('theme-liquid-docs'); | ||
export async function downloadSchema(relativeUri: string, destination: string = root) { | ||
export async function downloadSchema( | ||
relativeUri: string, | ||
destination: string = root, | ||
log: Logger = noop, | ||
) { | ||
const remotePath = schemaUrl(relativeUri); | ||
const localPath = schemaPath(relativeUri, destination); | ||
const res = await fetch(remotePath); | ||
const text = await res.text(); | ||
fs.writeFile(localPath, text, 'utf8'); | ||
const text = await download(remotePath, log); | ||
await fs.writeFile(localPath, text, 'utf8'); | ||
return text; | ||
} | ||
export async function downloadResource(resource: Resource | 'latest', destination: string = root) { | ||
export async function downloadResource( | ||
resource: Resource | 'latest', | ||
destination: string = root, | ||
log: Logger = noop, | ||
) { | ||
const remotePath = resourceUrl(resource); | ||
const localPath = resourcePath(resource, destination); | ||
const res = await fetch(remotePath); | ||
const text = await res.text(); | ||
fs.writeFile(localPath, text, 'utf8'); | ||
const text = await download(remotePath, log); | ||
await fs.writeFile(localPath, text, 'utf8'); | ||
return text; | ||
} | ||
export async function download(path: string, log: Logger) { | ||
if (path.startsWith('file:')) { | ||
return await fs | ||
.readFile(path.replace(/^file:/, ''), 'utf8') | ||
.then(tap(() => log(`Using local file: ${path}`))) | ||
.catch((error) => { | ||
log(`Failed to read local file: ${path}`); | ||
throw error; | ||
}); | ||
} else { | ||
const res = await fetch(path); | ||
return res.text(); | ||
} | ||
} | ||
export function resourcePath(resource: Resource | 'latest', destination: string = root) { | ||
@@ -67,3 +87,6 @@ return path.join(destination, `${resource}.json`); | ||
const relativePath = THEME_LIQUID_DOCS[resource]; | ||
return `${ThemeLiquidDocsRoot}/${relativePath}`; | ||
const resourceRoot = process.env.SHOPIFY_TLD_ROOT | ||
? `file:${process.env.SHOPIFY_TLD_ROOT}` | ||
: ThemeLiquidDocsRoot; | ||
return `${resourceRoot}/${relativePath}`; | ||
} | ||
@@ -76,3 +99,6 @@ | ||
export function schemaUrl(relativeUri: string) { | ||
return `${ThemeLiquidDocsSchemaRoot}/${relativeUri}`; | ||
const schemaRoot = process.env.SHOPIFY_TLD_ROOT | ||
? `file:${process.env.SHOPIFY_TLD_ROOT}` | ||
: ThemeLiquidDocsRoot; | ||
return `${schemaRoot}/schemas/${relativeUri}`; | ||
} | ||
@@ -89,3 +115,3 @@ | ||
export async function downloadThemeLiquidDocs(destination = root) { | ||
export async function downloadThemeLiquidDocs(destination: string, log: Logger) { | ||
if (!(await exists(destination))) { | ||
@@ -95,6 +121,25 @@ await fs.mkdir(destination); | ||
const resources = ['latest'].concat(Resources); | ||
const resources = ['latest'].concat(Resources) as (Resource | 'latest')[]; | ||
const resourceContents = await Promise.all( | ||
resources.map((file) => { | ||
return downloadResource(file as Resource | 'latest', destination); | ||
return downloadResource(file, destination, log) | ||
.then( | ||
tap(() => | ||
log( | ||
`Successfully downloaded latest resource:\n\t${resourceUrl(file)}\n\t> ${resourcePath( | ||
file, | ||
destination, | ||
)}`, | ||
), | ||
), | ||
) | ||
.catch((error) => { | ||
log( | ||
`Failed to download latest resource:\n\t${resourceUrl(file)} to\n\t${resourcePath( | ||
file, | ||
destination, | ||
)}\n${error}`, | ||
); | ||
throw error; | ||
}); | ||
}), | ||
@@ -112,3 +157,21 @@ ); | ||
await Promise.all(unique(relativeUris).map((uri) => downloadSchema(uri, destination))); | ||
await Promise.all( | ||
unique(relativeUris).map((uri) => | ||
downloadSchema(uri, destination, log) | ||
.then( | ||
tap(() => | ||
log( | ||
`Successfully downloaded schema:\n\t${schemaUrl(uri)}\n\t> ${schemaPath( | ||
uri, | ||
destination, | ||
)}`, | ||
), | ||
), | ||
) | ||
.catch((error) => { | ||
log(`Failed to download schema: ${uri}, ${error}`); | ||
throw error; | ||
}), | ||
), | ||
); | ||
} | ||
@@ -115,0 +178,0 @@ |
import { expect, describe, it, beforeEach, afterEach, vi, assert } from 'vitest'; | ||
import { ThemeLiquidDocsManager } from './themeLiquidDocsManager'; | ||
import { downloadResource, Resources } from './themeLiquidDocsDownloader'; | ||
import { noop } from './utils'; | ||
@@ -61,3 +62,8 @@ vi.mock('./themeLiquidDocsDownloader', async (importOriginal) => { | ||
await Promise.all([manager.filters(), manager.objects(), manager.tags()]); | ||
expect(vi.mocked(downloadResource)).toHaveBeenNthCalledWith(1, 'latest'); | ||
expect(vi.mocked(downloadResource)).toHaveBeenNthCalledWith( | ||
1, | ||
'latest', | ||
'MOCKED_CACHE/theme-liquid-docs', | ||
noop, | ||
); | ||
expect(vi.mocked(downloadResource)).toHaveBeenCalledTimes(1); | ||
@@ -64,0 +70,0 @@ for (const resource of Resources) { |
@@ -24,6 +24,4 @@ import { | ||
} from './themeLiquidDocsDownloader'; | ||
import { memo, memoize, noop } from './utils'; | ||
import { Logger, identity, memo, memoize, noop, tap } from './utils'; | ||
type Logger = (message: string) => void; | ||
type JSONSchemaManifest = { schemas: { uri: string; fileMatch?: string[] }[] }; | ||
@@ -35,15 +33,20 @@ | ||
filters = memo(async (): Promise<FilterEntry[]> => { | ||
return findSuitableResource(this.loaders('filters'), JSON.parse, []); | ||
return findSuitableResource(this.loaders('filters'), JSON.parse, [], this.log); | ||
}); | ||
objects = memo(async (): Promise<ObjectEntry[]> => { | ||
return findSuitableResource(this.loaders('objects'), JSON.parse, []); | ||
return findSuitableResource(this.loaders('objects'), JSON.parse, [], this.log); | ||
}); | ||
tags = memo(async (): Promise<TagEntry[]> => { | ||
return findSuitableResource(this.loaders('tags'), JSON.parse, []); | ||
return findSuitableResource(this.loaders('tags'), JSON.parse, [], this.log); | ||
}); | ||
systemTranslations = memo(async (): Promise<Translations> => { | ||
return findSuitableResource(this.loaders('shopify_system_translations'), JSON.parse, {}); | ||
return findSuitableResource( | ||
this.loaders('shopify_system_translations'), | ||
JSON.parse, | ||
{}, | ||
this.log, | ||
); | ||
}); | ||
@@ -53,5 +56,10 @@ | ||
(mode: Mode) => | ||
findSuitableResource<JSONSchemaManifest>(this.loaders(Manifests[mode]), JSON.parse, { | ||
schemas: [], | ||
}).then((manifest) => { | ||
findSuitableResource<JSONSchemaManifest>( | ||
this.loaders(Manifests[mode]), | ||
JSON.parse, | ||
{ | ||
schemas: [], | ||
}, | ||
this.log, | ||
).then((manifest) => { | ||
return Promise.all( | ||
@@ -66,2 +74,3 @@ manifest.schemas.map( | ||
'', | ||
this.log, | ||
), | ||
@@ -89,9 +98,11 @@ }), | ||
const local = await this.latestRevision(); | ||
await downloadResource('latest'); | ||
await downloadResource('latest', root, this.log); | ||
const remote = await this.latestRevision(); | ||
if (local !== remote) { | ||
await downloadThemeLiquidDocs(); | ||
await downloadThemeLiquidDocs(root, this.log); | ||
} | ||
} catch (error) { | ||
if (error instanceof Error) this.log(error.message); | ||
if (error instanceof Error) { | ||
this.log(`Failed to setup with the following error: ${error.message}`); | ||
} | ||
return; | ||
@@ -102,3 +113,8 @@ } | ||
private async latestRevision(): Promise<string> { | ||
const latest = await findSuitableResource([() => this.load('latest')], JSON.parse, {}); | ||
const latest = await findSuitableResource( | ||
[loader(() => this.load('latest'), 'loadLatestRevision')], | ||
JSON.parse, | ||
{}, | ||
this.log, | ||
); | ||
return latest['revision'] ?? ''; | ||
@@ -111,3 +127,3 @@ } | ||
await this.setup(); | ||
return this.load(name); | ||
return this.load(name).then(tap(() => this.log(`Loaded resource from ${resourcePath(name)}`))); | ||
} | ||
@@ -120,16 +136,31 @@ | ||
private async loadSchema(relativeUri: string) { | ||
return fs.readFile(schemaPath(relativeUri), 'utf8'); | ||
return fs | ||
.readFile(schemaPath(relativeUri), 'utf8') | ||
.then(tap(() => this.log(`Loaded schema from ${schemaPath(relativeUri)}`))); | ||
} | ||
private loaders(name: Resource) { | ||
return [() => this.loadResource(name), () => fallbackResource(name)]; | ||
private loaders(name: Resource): Loader<string>[] { | ||
return [ | ||
loader(() => this.loadResource(name), `loadResource(${name})`), | ||
loader(() => fallbackResource(name, this.log), `fallbackResource(${name})`), | ||
]; | ||
} | ||
private schemaLoaders(relativeUri: string) { | ||
return [() => this.loadSchema(relativeUri), () => fallbackSchema(relativeUri)]; | ||
private schemaLoaders(relativeUri: string): Loader<string>[] { | ||
return [ | ||
loader(() => this.loadSchema(relativeUri), `loadSchema(${relativeUri})`), | ||
loader(() => fallbackSchema(relativeUri, this.log), `fallbackSchema(${relativeUri})`), | ||
]; | ||
} | ||
} | ||
const identity = <T>(x: T): T => x; | ||
interface Loader<A> { | ||
(): Promise<A>; | ||
loaderName: string; | ||
} | ||
function loader<A>(fn: () => Promise<A>, loaderName: string): Loader<A> { | ||
return Object.assign(fn, { loaderName }); | ||
} | ||
/** | ||
@@ -147,5 +178,6 @@ * Find the first resource that can be loaded and transformed. | ||
async function findSuitableResource<B, A = string>( | ||
dataLoaders: (() => Promise<A>)[], | ||
dataLoaders: Loader<A>[], | ||
transform: (x: A) => B, | ||
defaultValue: B, | ||
log: Logger, | ||
): Promise<B> { | ||
@@ -155,3 +187,9 @@ for (const loader of dataLoaders) { | ||
return transform(await loader()); | ||
} catch (_) { | ||
} catch (e) { | ||
log( | ||
`Failed to load or transform ${loader.loaderName} with the following error:\n${ | ||
e instanceof Error ? e.message : e | ||
}`, | ||
); | ||
continue; | ||
@@ -176,4 +214,7 @@ } | ||
/** Returns the at-build-time path to the fallback data file. */ | ||
async function fallbackResource(name: Resource): Promise<string> { | ||
return fs.readFile(path.resolve(dataRoot(), `${name}.json`), 'utf8'); | ||
async function fallbackResource(name: Resource, log: Logger): Promise<string> { | ||
const sourcePath = path.resolve(dataRoot(), `${name}.json`); | ||
return fs | ||
.readFile(sourcePath, 'utf8') | ||
.then(tap(() => log(`Loaded fallback resource\n\t${name} from\n\t${sourcePath}`))); | ||
} | ||
@@ -183,5 +224,10 @@ | ||
async function fallbackSchema( | ||
/** e.g. themes/section.json */ relativeUri: string, | ||
/** e.g. themes/section.json */ | ||
relativeUri: string, | ||
log: Logger, | ||
): Promise<string> { | ||
return fs.readFile(path.resolve(dataRoot(), path.basename(relativeUri)), 'utf8'); | ||
const sourcePath = path.resolve(dataRoot(), path.basename(relativeUri)); | ||
return fs | ||
.readFile(sourcePath, 'utf8') | ||
.then(tap(() => log(`Loaded fallback schema\n\t${relativeUri} from\n\t${sourcePath}`))); | ||
} |
import { Resource, downloadResource } from './themeLiquidDocsDownloader'; | ||
import { root } from './themeLiquidDocsDownloader'; | ||
export type Logger = (message: string) => void; | ||
export const noop = () => {}; | ||
export const identity = <T>(x: T): T => x; | ||
export const tap = <T>(tappingFunction: (x: T) => void) => { | ||
return (x: T) => { | ||
tappingFunction(x); | ||
return x; | ||
}; | ||
}; | ||
/** Returns a cached version of a function. Only caches one result. */ | ||
@@ -7,0 +16,0 @@ export function memo<F extends (...args: any[]) => any>( |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
1293724
41
31752
7