@shopify/theme-check-docs-updater
Advanced tools
Comparing version 2.4.0 to 2.5.0
# @shopify/theme-check-docs-updater | ||
## 2.5.0 | ||
### Minor Changes | ||
- 03b41e1: Breaking: `jsonValidationSet`'s schemas public API change | ||
Now takes a function of the following signature: | ||
```ts | ||
interface JsonValidationSet = { | ||
schemas: (context: 'theme' | 'app') => Promise<SchemaDefinition[]> | ||
} | ||
``` | ||
Reason being we want to support `fileMatch` overloading of `blocks/*.liquid` files and we needed a way to identify which context you're in. | ||
Unfortunately, the JSON schema for `blocks/*.liquid` files in theme app extensions isn't the same one we have in themes. There doesn't seem to be a way to unify them either. | ||
- 03b41e1: Add support for the schemas manifest on Shopify/theme-liquid-docs | ||
Shopify/theme-liquid-docs now supports composable JSON schemas (with relative paths). To solve the `blocks/*.liquid` file match JSON schema overload depending on the context (`app` or `theme`), we defined two manifests that describe the schemas required by your solution and define the fileMatch rules: | ||
- [manifest_theme.json](https://github.com/Shopify/theme-liquid-docs/blob/main/schemas/manifest_theme.json) | ||
- [manifest_theme_app_extension.json](https://github.com/Shopify/theme-liquid-docs/blob/main/schemas/manifest_theme.json) | ||
`@shopify/theme-check-docs-updater` now reads those manifests and downloads the tree of dependency that they require. We will no longer need to make new theme-tools releases whenever we add new schemas. We'll be able to dev them and their file associations directly from Shopify/theme-liquid-docs and have downstream consumers updated automatically (the same way docs are automatically updated). | ||
### Patch Changes | ||
- Updated dependencies [03b41e1] | ||
- Updated dependencies [03b41e1] | ||
- Updated dependencies [03b41e1] | ||
- @shopify/theme-check-common@2.5.0 | ||
## 2.4.0 | ||
@@ -4,0 +38,0 @@ |
@@ -1,2 +0,2 @@ | ||
{ "revision": "acc94f2e9681a885bc24e40f72501e77a689b0f6" } | ||
{ "revision": "0fa0bcaf791e40fa3770dff923c9578424201a88" } | ||
export { ThemeLiquidDocsManager } from './themeLiquidDocsManager'; | ||
export { Resource, Resources, downloadFile, downloadThemeLiquidDocs, } from './themeLiquidDocsDownloader'; | ||
export { root } from './utils'; | ||
export { Resource, Resources, downloadResource, downloadThemeLiquidDocs, root, } from './themeLiquidDocsDownloader'; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.root = exports.downloadThemeLiquidDocs = exports.downloadFile = exports.Resources = exports.ThemeLiquidDocsManager = void 0; | ||
exports.root = exports.downloadThemeLiquidDocs = exports.downloadResource = exports.Resources = exports.ThemeLiquidDocsManager = void 0; | ||
var themeLiquidDocsManager_1 = require("./themeLiquidDocsManager"); | ||
@@ -8,6 +8,5 @@ Object.defineProperty(exports, "ThemeLiquidDocsManager", { enumerable: true, get: function () { return themeLiquidDocsManager_1.ThemeLiquidDocsManager; } }); | ||
Object.defineProperty(exports, "Resources", { enumerable: true, get: function () { return themeLiquidDocsDownloader_1.Resources; } }); | ||
Object.defineProperty(exports, "downloadFile", { enumerable: true, get: function () { return themeLiquidDocsDownloader_1.downloadFile; } }); | ||
Object.defineProperty(exports, "downloadResource", { enumerable: true, get: function () { return themeLiquidDocsDownloader_1.downloadResource; } }); | ||
Object.defineProperty(exports, "downloadThemeLiquidDocs", { enumerable: true, get: function () { return themeLiquidDocsDownloader_1.downloadThemeLiquidDocs; } }); | ||
var utils_1 = require("./utils"); | ||
Object.defineProperty(exports, "root", { enumerable: true, get: function () { return utils_1.root; } }); | ||
Object.defineProperty(exports, "root", { enumerable: true, get: function () { return themeLiquidDocsDownloader_1.root; } }); | ||
//# sourceMappingURL=index.js.map |
@@ -0,5 +1,17 @@ | ||
export declare const root: string; | ||
export declare const ThemeLiquidDocsRoot = "https://raw.githubusercontent.com/Shopify/theme-liquid-docs/main"; | ||
export declare const ThemeLiquidDocsSchemaRoot: string; | ||
export type Resource = (typeof Resources)[number]; | ||
export declare const Resources: readonly ["filters", "objects", "tags", "section_schema", "translations_schema", "shopify_system_translations"]; | ||
export declare function downloadFile(file: Resource | 'latest', destination: string): Promise<void>; | ||
export declare const Resources: readonly ["filters", "objects", "tags", "shopify_system_translations", "manifest_theme", "manifest_theme_app_extension"]; | ||
export declare const Manifests: { | ||
readonly app: "manifest_theme_app_extension"; | ||
readonly theme: "manifest_theme"; | ||
}; | ||
export declare function downloadSchema(relativeUri: string, destination?: string): Promise<string>; | ||
export declare function downloadResource(resource: Resource | 'latest', destination?: string): Promise<string>; | ||
export declare function resourcePath(resource: Resource | 'latest', destination?: string): string; | ||
export declare function resourceUrl(resource: Resource | 'latest'): string; | ||
export declare function schemaPath(relativeUri: string, destination?: string): string; | ||
export declare function schemaUrl(relativeUri: string): string; | ||
export declare function exists(path: string): Promise<boolean>; | ||
export declare function downloadThemeLiquidDocs(outputDir: string): Promise<void>; | ||
export declare function downloadThemeLiquidDocs(destination?: string): Promise<void>; |
@@ -6,6 +6,11 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.downloadThemeLiquidDocs = exports.exists = exports.downloadFile = exports.Resources = void 0; | ||
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; | ||
const env_paths_1 = __importDefault(require("env-paths")); | ||
const node_fetch_1 = __importDefault(require("node-fetch")); | ||
const promises_1 = __importDefault(require("node:fs/promises")); | ||
const node_fetch_1 = __importDefault(require("node-fetch")); | ||
const node_path_1 = __importDefault(require("node:path")); | ||
const paths = (0, env_paths_1.default)('theme-liquid-docs'); | ||
exports.root = paths.cache; | ||
exports.ThemeLiquidDocsRoot = 'https://raw.githubusercontent.com/Shopify/theme-liquid-docs/main'; | ||
exports.ThemeLiquidDocsSchemaRoot = `${exports.ThemeLiquidDocsRoot}/schemas`; | ||
exports.Resources = [ | ||
@@ -15,6 +20,10 @@ 'filters', | ||
'tags', | ||
'section_schema', | ||
'translations_schema', | ||
'shopify_system_translations', | ||
'manifest_theme', | ||
'manifest_theme_app_extension', | ||
]; | ||
exports.Manifests = { | ||
app: 'manifest_theme_app_extension', | ||
theme: 'manifest_theme', | ||
}; | ||
const THEME_LIQUID_DOCS = { | ||
@@ -25,21 +34,41 @@ filters: 'data/filters.json', | ||
latest: 'data/latest.json', | ||
section_schema: 'schemas/theme/section_schema.json', | ||
translations_schema: 'schemas/theme/translations_schema.json', | ||
shopify_system_translations: 'data/shopify_system_translations.json', | ||
manifest_theme: 'schemas/manifest_theme.json', | ||
manifest_theme_app_extension: 'schemas/manifest_theme_app_extension.json', | ||
}; | ||
async function downloadFile(file, destination) { | ||
const remotePath = buildRemotePath(file); | ||
const localPath = buildLocalPath(file, destination); | ||
async function downloadSchema(relativeUri, destination = exports.root) { | ||
const remotePath = schemaUrl(relativeUri); | ||
const localPath = schemaPath(relativeUri, destination); | ||
const res = await (0, node_fetch_1.default)(remotePath); | ||
const text = await res.text(); | ||
return promises_1.default.writeFile(localPath, text, 'utf8'); | ||
promises_1.default.writeFile(localPath, text, 'utf8'); | ||
return text; | ||
} | ||
exports.downloadFile = downloadFile; | ||
function buildRemotePath(file) { | ||
const relativePath = THEME_LIQUID_DOCS[file]; | ||
return `https://raw.githubusercontent.com/Shopify/theme-liquid-docs/main/${relativePath}`; | ||
exports.downloadSchema = downloadSchema; | ||
async function downloadResource(resource, destination = exports.root) { | ||
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'); | ||
return text; | ||
} | ||
function buildLocalPath(file, destination) { | ||
return node_path_1.default.join(destination, `${file}.json`); | ||
exports.downloadResource = downloadResource; | ||
function resourcePath(resource, destination = exports.root) { | ||
return node_path_1.default.join(destination, `${resource}.json`); | ||
} | ||
exports.resourcePath = resourcePath; | ||
function resourceUrl(resource) { | ||
const relativePath = THEME_LIQUID_DOCS[resource]; | ||
return `${exports.ThemeLiquidDocsRoot}/${relativePath}`; | ||
} | ||
exports.resourceUrl = resourceUrl; | ||
function schemaPath(relativeUri, destination = exports.root) { | ||
return node_path_1.default.resolve(destination, node_path_1.default.basename(relativeUri)); | ||
} | ||
exports.schemaPath = schemaPath; | ||
function schemaUrl(relativeUri) { | ||
return `${exports.ThemeLiquidDocsSchemaRoot}/${relativeUri}`; | ||
} | ||
exports.schemaUrl = schemaUrl; | ||
async function exists(path) { | ||
@@ -55,12 +84,21 @@ try { | ||
exports.exists = exists; | ||
async function downloadThemeLiquidDocs(outputDir) { | ||
if (!(await exists(outputDir))) { | ||
await promises_1.default.mkdir(outputDir); | ||
async function downloadThemeLiquidDocs(destination = exports.root) { | ||
if (!(await exists(destination))) { | ||
await promises_1.default.mkdir(destination); | ||
} | ||
const promises = ['latest'].concat(exports.Resources).map((file) => { | ||
return downloadFile(file, outputDir); | ||
}); | ||
await Promise.all(promises); | ||
const resources = ['latest'].concat(exports.Resources); | ||
const resourceContents = await Promise.all(resources.map((file) => { | ||
return downloadResource(file, destination); | ||
})); | ||
const manifests = Object.values(exports.Manifests) | ||
.map((resource) => resources.indexOf(resource)) | ||
.map((index) => resourceContents[index]) | ||
.map((manifest) => JSON.parse(manifest)); | ||
const relativeUris = manifests.flatMap((manifest) => manifest.schemas.map((schema) => schema.uri)); | ||
await Promise.all(unique(relativeUris).map((uri) => downloadSchema(uri, destination))); | ||
} | ||
exports.downloadThemeLiquidDocs = downloadThemeLiquidDocs; | ||
function unique(array) { | ||
return [...new Set(array).values()]; | ||
} | ||
//# sourceMappingURL=themeLiquidDocsDownloader.js.map |
@@ -1,2 +0,2 @@ | ||
import { FilterEntry, JsonValidationSet, ObjectEntry, TagEntry, ThemeDocset, Translations } from '@shopify/theme-check-common'; | ||
import { FilterEntry, JsonValidationSet, ObjectEntry, SchemaDefinition, TagEntry, ThemeDocset, Translations } from '@shopify/theme-check-common'; | ||
type Logger = (message: string) => void; | ||
@@ -10,7 +10,3 @@ export declare class ThemeLiquidDocsManager implements ThemeDocset, JsonValidationSet { | ||
systemTranslations: () => Promise<Translations>; | ||
schemas: { | ||
uri: string; | ||
fileMatch: string[]; | ||
schema: Promise<string>; | ||
}[]; | ||
schemas: (arg: "theme" | "app") => Promise<SchemaDefinition[]>; | ||
/** | ||
@@ -27,4 +23,6 @@ * The setup method checks that the latest revision matches the one from | ||
private load; | ||
private loadSchema; | ||
private loaders; | ||
private schemaLoaders; | ||
} | ||
export {}; |
@@ -11,4 +11,2 @@ "use strict"; | ||
const utils_1 = require("./utils"); | ||
const SectionSchemaURI = 'https://raw.githubusercontent.com/Shopify/theme-liquid-docs/main/schemas/theme/section_schema.json'; | ||
const TranslationFileURI = 'https://raw.githubusercontent.com/Shopify/theme-liquid-docs/main/schemas/theme/translations_schema.json'; | ||
class ThemeLiquidDocsManager { | ||
@@ -29,19 +27,11 @@ constructor(log = utils_1.noop) { | ||
}); | ||
this.schemas = [ | ||
{ | ||
uri: SectionSchemaURI, | ||
fileMatch: ['**/sections/*.liquid'], | ||
schema: findSuitableResource(this.loaders('section_schema'), identity, '{}'), | ||
}, | ||
{ | ||
uri: TranslationFileURI, | ||
fileMatch: [ | ||
'**/locales/*.json', | ||
'**/locales/*.default.json', | ||
'**/locales/*.schema.json', | ||
'**/locales/*.default.schema.json', | ||
], | ||
schema: findSuitableResource(this.loaders('translations_schema'), identity, '{}'), | ||
}, | ||
]; | ||
this.schemas = (0, utils_1.memoize)((mode) => findSuitableResource(this.loaders(themeLiquidDocsDownloader_1.Manifests[mode]), JSON.parse, { | ||
schemas: [], | ||
}).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, ''), | ||
}))); | ||
}), (identity)); | ||
/** | ||
@@ -56,10 +46,10 @@ * The setup method checks that the latest revision matches the one from | ||
try { | ||
if (!(await (0, themeLiquidDocsDownloader_1.exists)(utils_1.root))) { | ||
await promises_1.default.mkdir(utils_1.root, { recursive: true }); | ||
if (!(await (0, themeLiquidDocsDownloader_1.exists)(themeLiquidDocsDownloader_1.root))) { | ||
await promises_1.default.mkdir(themeLiquidDocsDownloader_1.root, { recursive: true }); | ||
} | ||
const local = await this.latestRevision(); | ||
await (0, utils_1.download)('latest'); | ||
await (0, themeLiquidDocsDownloader_1.downloadResource)('latest'); | ||
const remote = await this.latestRevision(); | ||
if (local !== remote) { | ||
await Promise.all(themeLiquidDocsDownloader_1.Resources.map((resource) => (0, utils_1.download)(resource))); | ||
await (0, themeLiquidDocsDownloader_1.downloadThemeLiquidDocs)(); | ||
} | ||
@@ -86,7 +76,13 @@ } | ||
async load(name) { | ||
return promises_1.default.readFile((0, utils_1.filePath)(name), 'utf8'); | ||
return promises_1.default.readFile((0, themeLiquidDocsDownloader_1.resourcePath)(name), 'utf8'); | ||
} | ||
async loadSchema(relativeUri) { | ||
return promises_1.default.readFile((0, themeLiquidDocsDownloader_1.schemaPath)(relativeUri), 'utf8'); | ||
} | ||
loaders(name) { | ||
return [() => this.loadResource(name), () => fallback(name)]; | ||
return [() => this.loadResource(name), () => fallbackResource(name)]; | ||
} | ||
schemaLoaders(relativeUri) { | ||
return [() => this.loadSchema(relativeUri), () => fallbackSchema(relativeUri)]; | ||
} | ||
} | ||
@@ -130,5 +126,10 @@ exports.ThemeLiquidDocsManager = ThemeLiquidDocsManager; | ||
/** Returns the at-build-time path to the fallback data file. */ | ||
async function fallback(name) { | ||
async function fallbackResource(name) { | ||
return promises_1.default.readFile(node_path_1.default.resolve(dataRoot(), `${name}.json`), 'utf8'); | ||
} | ||
/** 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'); | ||
} | ||
//# sourceMappingURL=themeLiquidDocsManager.js.map |
@@ -1,8 +0,5 @@ | ||
import { Resource } from './themeLiquidDocsDownloader'; | ||
export declare const noop: () => void; | ||
export declare const root: string; | ||
export declare function download(file: Resource | 'latest', destination?: string): Promise<void>; | ||
export declare function filePath(file: Resource | 'latest', destination?: string): string; | ||
/** Returns a cached version of a function. Only caches one result. */ | ||
export declare function memo<F extends (...args: any[]) => any>(fn: F): (...args: ArgumentTypes<F>) => ReturnType<F>; | ||
export declare function memoize<AT, F extends (arg: AT) => any, RT extends ReturnType<F>>(fn: F, keyFn: (arg: AT) => string): (arg: AT) => RT; | ||
/** | ||
@@ -9,0 +6,0 @@ * ArgumentTypes extracts the type of the arguments of a function. |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.memo = exports.filePath = exports.download = exports.root = exports.noop = void 0; | ||
const env_paths_1 = __importDefault(require("env-paths")); | ||
const node_path_1 = __importDefault(require("node:path")); | ||
const themeLiquidDocsDownloader_1 = require("./themeLiquidDocsDownloader"); | ||
exports.memoize = exports.memo = exports.noop = void 0; | ||
const noop = () => { }; | ||
exports.noop = noop; | ||
const paths = (0, env_paths_1.default)('theme-liquid-docs'); | ||
exports.root = paths.cache; | ||
function download(file, destination = exports.root) { | ||
return (0, themeLiquidDocsDownloader_1.downloadFile)(file, destination); | ||
} | ||
exports.download = download; | ||
function filePath(file, destination = exports.root) { | ||
return node_path_1.default.join(destination, `${file}.json`); | ||
} | ||
exports.filePath = filePath; | ||
/** Returns a cached version of a function. Only caches one result. */ | ||
@@ -33,2 +17,13 @@ function memo(fn) { | ||
exports.memo = memo; | ||
function memoize(fn, keyFn) { | ||
const cache = {}; | ||
return (arg) => { | ||
const key = keyFn(arg); | ||
if (!cache[key]) { | ||
cache[key] = fn(arg); | ||
} | ||
return cache[key]; | ||
}; | ||
} | ||
exports.memoize = memoize; | ||
//# sourceMappingURL=utils.js.map |
{ | ||
"name": "@shopify/theme-check-docs-updater", | ||
"version": "2.4.0", | ||
"version": "2.5.0", | ||
"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.4.0", | ||
"@shopify/theme-check-common": "^2.5.0", | ||
"env-paths": "^2.2.1", | ||
@@ -36,0 +36,0 @@ "node-fetch": "^2.6.11" |
@@ -5,5 +5,5 @@ export { ThemeLiquidDocsManager } from './themeLiquidDocsManager'; | ||
Resources, | ||
downloadFile, | ||
downloadResource, | ||
downloadThemeLiquidDocs, | ||
root, | ||
} from './themeLiquidDocsDownloader'; | ||
export { root } from './utils'; |
@@ -0,5 +1,14 @@ | ||
import { Mode } from '@shopify/theme-check-common'; | ||
import envPaths from 'env-paths'; | ||
import fetch from 'node-fetch'; | ||
import fs from 'node:fs/promises'; | ||
import fetch from 'node-fetch'; | ||
import path from 'node:path'; | ||
const paths = envPaths('theme-liquid-docs'); | ||
export const root = paths.cache; | ||
export const ThemeLiquidDocsRoot = | ||
'https://raw.githubusercontent.com/Shopify/theme-liquid-docs/main'; | ||
export const ThemeLiquidDocsSchemaRoot = `${ThemeLiquidDocsRoot}/schemas`; | ||
export type Resource = (typeof Resources)[number]; | ||
@@ -10,7 +19,12 @@ export const Resources = [ | ||
'tags', | ||
'section_schema', | ||
'translations_schema', | ||
'shopify_system_translations', | ||
'manifest_theme', | ||
'manifest_theme_app_extension', | ||
] as const; | ||
export const Manifests = { | ||
app: 'manifest_theme_app_extension', | ||
theme: 'manifest_theme', | ||
} as const satisfies Record<Mode, Resource>; | ||
const THEME_LIQUID_DOCS: Record<Resource | 'latest', string> = { | ||
@@ -21,26 +35,44 @@ filters: 'data/filters.json', | ||
latest: 'data/latest.json', | ||
section_schema: 'schemas/theme/section_schema.json', | ||
translations_schema: 'schemas/theme/translations_schema.json', | ||
shopify_system_translations: 'data/shopify_system_translations.json', | ||
manifest_theme: 'schemas/manifest_theme.json', | ||
manifest_theme_app_extension: 'schemas/manifest_theme_app_extension.json', | ||
}; | ||
export async function downloadFile(file: Resource | 'latest', destination: string) { | ||
const remotePath = buildRemotePath(file); | ||
const localPath = buildLocalPath(file, destination); | ||
export async function downloadSchema(relativeUri: string, destination: string = root) { | ||
const remotePath = schemaUrl(relativeUri); | ||
const localPath = schemaPath(relativeUri, destination); | ||
const res = await fetch(remotePath); | ||
const text = await res.text(); | ||
fs.writeFile(localPath, text, 'utf8'); | ||
return text; | ||
} | ||
export async function downloadResource(resource: Resource | 'latest', destination: string = root) { | ||
const remotePath = resourceUrl(resource); | ||
const localPath = resourcePath(resource, destination); | ||
const res = await fetch(remotePath); | ||
const text = await res.text(); | ||
return fs.writeFile(localPath, text, 'utf8'); | ||
fs.writeFile(localPath, text, 'utf8'); | ||
return text; | ||
} | ||
function buildRemotePath(file: Resource | 'latest') { | ||
const relativePath = THEME_LIQUID_DOCS[file]; | ||
return `https://raw.githubusercontent.com/Shopify/theme-liquid-docs/main/${relativePath}`; | ||
export function resourcePath(resource: Resource | 'latest', destination: string = root) { | ||
return path.join(destination, `${resource}.json`); | ||
} | ||
function buildLocalPath(file: string, destination: string) { | ||
return path.join(destination, `${file}.json`); | ||
export function resourceUrl(resource: Resource | 'latest') { | ||
const relativePath = THEME_LIQUID_DOCS[resource]; | ||
return `${ThemeLiquidDocsRoot}/${relativePath}`; | ||
} | ||
export function schemaPath(relativeUri: string, destination: string = root) { | ||
return path.resolve(destination, path.basename(relativeUri)); | ||
} | ||
export function schemaUrl(relativeUri: string) { | ||
return `${ThemeLiquidDocsSchemaRoot}/${relativeUri}`; | ||
} | ||
export async function exists(path: string) { | ||
@@ -55,12 +87,28 @@ try { | ||
export async function downloadThemeLiquidDocs(outputDir: string) { | ||
if (!(await exists(outputDir))) { | ||
await fs.mkdir(outputDir); | ||
export async function downloadThemeLiquidDocs(destination = root) { | ||
if (!(await exists(destination))) { | ||
await fs.mkdir(destination); | ||
} | ||
const promises = ['latest'].concat(Resources).map((file) => { | ||
return downloadFile(file as Resource | 'latest', outputDir); | ||
}); | ||
const resources = ['latest'].concat(Resources); | ||
const resourceContents = await Promise.all( | ||
resources.map((file) => { | ||
return downloadResource(file as Resource | 'latest', destination); | ||
}), | ||
); | ||
await Promise.all(promises); | ||
const manifests = Object.values(Manifests) | ||
.map((resource) => resources.indexOf(resource)) | ||
.map((index) => resourceContents[index]) | ||
.map((manifest) => JSON.parse(manifest)); | ||
const relativeUris = manifests.flatMap((manifest) => | ||
manifest.schemas.map((schema: { uri: string }) => schema.uri), | ||
); | ||
await Promise.all(unique(relativeUris).map((uri) => downloadSchema(uri, destination))); | ||
} | ||
function unique<T>(array: T[]): T[] { | ||
return [...new Set(array).values()]; | ||
} |
import { expect, describe, it, beforeEach, afterEach, vi, assert } from 'vitest'; | ||
import { ThemeLiquidDocsManager } from './themeLiquidDocsManager'; | ||
import { downloadFile, Resources } from './themeLiquidDocsDownloader'; | ||
import { downloadResource, Resources } from './themeLiquidDocsDownloader'; | ||
@@ -9,3 +9,3 @@ vi.mock('./themeLiquidDocsDownloader', async (importOriginal) => { | ||
...actual, | ||
downloadFile: vi.fn(), | ||
downloadResource: vi.fn(), | ||
}; | ||
@@ -62,6 +62,6 @@ }); | ||
await Promise.all([manager.filters(), manager.objects(), manager.tags()]); | ||
expect(vi.mocked(downloadFile)).toHaveBeenNthCalledWith(1, 'latest', expect.any(String)); | ||
expect(vi.mocked(downloadFile)).toHaveBeenCalledTimes(1); | ||
expect(vi.mocked(downloadResource)).toHaveBeenNthCalledWith(1, 'latest'); | ||
expect(vi.mocked(downloadResource)).toHaveBeenCalledTimes(1); | ||
for (const resource of Resources) { | ||
expect(vi.mocked(downloadFile)).not.toHaveBeenCalledWith(resource, expect.any(String)); | ||
expect(vi.mocked(downloadResource)).not.toHaveBeenCalledWith(resource, expect.any(String)); | ||
} | ||
@@ -68,0 +68,0 @@ }); |
import { | ||
FilterEntry, | ||
JsonValidationSet, | ||
Mode, | ||
ObjectEntry, | ||
SchemaDefinition, | ||
TagEntry, | ||
ThemeDocset, | ||
Translations, | ||
indexBy, | ||
} from '@shopify/theme-check-common'; | ||
import fs from 'node:fs/promises'; | ||
import path from 'node:path'; | ||
import { Resource, Resources, exists } from './themeLiquidDocsDownloader'; | ||
import { download, filePath, memo, noop, root } from './utils'; | ||
import { | ||
Manifests, | ||
Resource, | ||
ThemeLiquidDocsSchemaRoot, | ||
downloadResource, | ||
downloadThemeLiquidDocs, | ||
exists, | ||
resourcePath, | ||
root, | ||
schemaPath, | ||
} from './themeLiquidDocsDownloader'; | ||
import { memo, memoize, noop } from './utils'; | ||
type Logger = (message: string) => void; | ||
const SectionSchemaURI = | ||
'https://raw.githubusercontent.com/Shopify/theme-liquid-docs/main/schemas/theme/section_schema.json'; | ||
type JSONSchemaManifest = { schemas: { uri: string; fileMatch?: string[] }[] }; | ||
const TranslationFileURI = | ||
'https://raw.githubusercontent.com/Shopify/theme-liquid-docs/main/schemas/theme/translations_schema.json'; | ||
export class ThemeLiquidDocsManager implements ThemeDocset, JsonValidationSet { | ||
@@ -42,19 +49,23 @@ constructor(private log: Logger = noop) {} | ||
schemas = [ | ||
{ | ||
uri: SectionSchemaURI, | ||
fileMatch: ['**/sections/*.liquid'], | ||
schema: findSuitableResource(this.loaders('section_schema'), identity, '{}'), | ||
}, | ||
{ | ||
uri: TranslationFileURI, | ||
fileMatch: [ | ||
'**/locales/*.json', | ||
'**/locales/*.default.json', | ||
'**/locales/*.schema.json', | ||
'**/locales/*.default.schema.json', | ||
], | ||
schema: findSuitableResource(this.loaders('translations_schema'), identity, '{}'), | ||
}, | ||
]; | ||
schemas = memoize( | ||
(mode: Mode) => | ||
findSuitableResource<JSONSchemaManifest>(this.loaders(Manifests[mode]), JSON.parse, { | ||
schemas: [], | ||
}).then((manifest) => { | ||
return Promise.all( | ||
manifest.schemas.map( | ||
async (schemaDefinition): Promise<SchemaDefinition> => ({ | ||
uri: `${ThemeLiquidDocsSchemaRoot}/${schemaDefinition.uri}`, | ||
fileMatch: schemaDefinition.fileMatch, | ||
schema: await findSuitableResource( | ||
this.schemaLoaders(schemaDefinition.uri), | ||
identity, | ||
'', | ||
), | ||
}), | ||
), | ||
); | ||
}), | ||
identity<Mode>, | ||
); | ||
@@ -75,6 +86,6 @@ /** | ||
const local = await this.latestRevision(); | ||
await download('latest'); | ||
await downloadResource('latest'); | ||
const remote = await this.latestRevision(); | ||
if (local !== remote) { | ||
await Promise.all(Resources.map((resource) => download(resource))); | ||
await downloadThemeLiquidDocs(); | ||
} | ||
@@ -100,8 +111,16 @@ } catch (error) { | ||
private async load(name: Resource | 'latest') { | ||
return fs.readFile(filePath(name), 'utf8'); | ||
return fs.readFile(resourcePath(name), 'utf8'); | ||
} | ||
private async loadSchema(relativeUri: string) { | ||
return fs.readFile(schemaPath(relativeUri), 'utf8'); | ||
} | ||
private loaders(name: Resource) { | ||
return [() => this.loadResource(name), () => fallback(name)]; | ||
return [() => this.loadResource(name), () => fallbackResource(name)]; | ||
} | ||
private schemaLoaders(relativeUri: string) { | ||
return [() => this.loadSchema(relativeUri), () => fallbackSchema(relativeUri)]; | ||
} | ||
} | ||
@@ -150,4 +169,11 @@ | ||
/** Returns the at-build-time path to the fallback data file. */ | ||
async function fallback(name: Resource): Promise<string> { | ||
async function fallbackResource(name: Resource): Promise<string> { | ||
return fs.readFile(path.resolve(dataRoot(), `${name}.json`), 'utf8'); | ||
} | ||
/** Returns the at-build-time path to the fallback schema file. */ | ||
async function fallbackSchema( | ||
/** e.g. themes/section.json */ relativeUri: string, | ||
): Promise<string> { | ||
return fs.readFile(path.resolve(dataRoot(), path.basename(relativeUri)), 'utf8'); | ||
} |
@@ -1,18 +0,6 @@ | ||
import envPaths from 'env-paths'; | ||
import path from 'node:path'; | ||
import { Resource, downloadFile } from './themeLiquidDocsDownloader'; | ||
import { Resource, downloadResource } from './themeLiquidDocsDownloader'; | ||
import { root } from './themeLiquidDocsDownloader'; | ||
export const noop = () => {}; | ||
const paths = envPaths('theme-liquid-docs'); | ||
export const root = paths.cache; | ||
export function download(file: Resource | 'latest', destination: string = root) { | ||
return downloadFile(file, destination); | ||
} | ||
export function filePath(file: Resource | 'latest', destination: string = root) { | ||
return path.join(destination, `${file}.json`); | ||
} | ||
/** Returns a cached version of a function. Only caches one result. */ | ||
@@ -32,2 +20,19 @@ export function memo<F extends (...args: any[]) => any>( | ||
export function memoize<AT, F extends (arg: AT) => any, RT extends ReturnType<F>>( | ||
fn: F, | ||
keyFn: (arg: AT) => string, | ||
) { | ||
const cache: Record<string, RT> = {}; | ||
return (arg: AT): RT => { | ||
const key = keyFn(arg); | ||
if (!cache[key]) { | ||
cache[key] = fn(arg); | ||
} | ||
return cache[key]; | ||
}; | ||
} | ||
/** | ||
@@ -34,0 +39,0 @@ * ArgumentTypes extracts the type of the arguments of a function. |
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
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1230204
33
30631