From 7fc93c629d248a1b935234b754089e3f1b5a6552 Mon Sep 17 00:00:00 2001 From: Thomas Kelly Date: Wed, 7 May 2025 10:16:31 -0400 Subject: [PATCH 1/8] Rename generateManifestFiles to generateManifestFileForTheme --- package-lock.json | 4 ++-- src/commands/theme/component/map.ts | 4 ++-- src/utilities/manifest.ts | 2 +- test/commands/theme/component/map.test.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 053c568..cebce07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "plugin-devkit", - "version": "1.0.3", + "version": "1.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "plugin-devkit", - "version": "1.0.3", + "version": "1.0.4", "license": "MIT", "dependencies": { "@oclif/core": "^4", diff --git a/src/commands/theme/component/map.ts b/src/commands/theme/component/map.ts index 51948b8..fc7f8ff 100644 --- a/src/commands/theme/component/map.ts +++ b/src/commands/theme/component/map.ts @@ -12,7 +12,7 @@ import Args from '../../../utilities/args.js' import BaseCommand from '../../../utilities/base-command.js' import Flags from '../../../utilities/flags.js' import { getLastCommitHash } from '../../../utilities/git.js' -import { ManifestOptions, generateManifestFiles, getManifest } from '../../../utilities/manifest.js' +import { ManifestOptions, generateManifestFileForTheme, getManifest } from '../../../utilities/manifest.js' import { sortObjectKeys } from '../../../utilities/objects.js' import { getNameFromPackageJson, getVersionFromPackageJson } from '../../../utilities/package-json.js' @@ -67,7 +67,7 @@ export default class Manifest extends BaseCommand { ignoreOverrides } - const files = await generateManifestFiles( + const files = await generateManifestFileForTheme( manifest.files, themeDir, collectionDir, diff --git a/src/utilities/manifest.ts b/src/utilities/manifest.ts index 07e262e..1ec3db3 100644 --- a/src/utilities/manifest.ts +++ b/src/utilities/manifest.ts @@ -25,7 +25,7 @@ export interface ManifestOptions { } // eslint-disable-next-line max-params -export async function generateManifestFiles( +export async function generateManifestFileForTheme( oldFilesMap: Manifest['files'], themeDir: string, collectionDir: string, diff --git a/test/commands/theme/component/map.test.ts b/test/commands/theme/component/map.test.ts index 7b3c040..293bac0 100644 --- a/test/commands/theme/component/map.test.ts +++ b/test/commands/theme/component/map.test.ts @@ -37,7 +37,7 @@ describe('theme component map', () => { expect(error?.message).to.include('Missing 1 required arg:') }) - it('creates a component.map.json in current theme directory if it does not exist', async () => { + it('creates a component.manifest.json in current theme directory if it does not exist', async () => { // Confirm that the file does not exist fs.rmSync(path.join(testThemePath, 'component.manifest.json'), {force: true}) expect(fs.existsSync(path.join(testThemePath, 'component.manifest.json'))).to.be.false From 1596855334a082a96d69b1d672cc5db1ec1bb621 Mon Sep 17 00:00:00 2001 From: Thomas Kelly Date: Wed, 7 May 2025 13:25:33 -0400 Subject: [PATCH 2/8] Update map command to manifest command - manifest command now works when installing to other collections --- package.json | 1 + src/commands/theme/component/install.ts | 4 +- .../theme/component/{map.ts => manifest.ts} | 56 +++++++---- src/utilities/args.ts | 30 +++--- src/utilities/manifest.ts | 96 ++++++++++--------- src/utilities/validate.ts | 25 +++++ test/commands/theme/component/install.test.ts | 12 +-- .../{map.test.ts => manifest.test.ts} | 79 +++++++++------ .../theme/generate/import-map.test.ts | 2 - .../collection-b/component.manifest.json | 17 ++++ .../belongs-only-to-this-collection.css | 0 .../belongs-only-to-this-collection.liquid | 0 ...-other-collection-already-installed.liquid | 0 ...ses-component-from-other-collection.liquid | 2 + test/fixtures/collection-b/package.json | 4 + ...-other-collection-already-installed.liquid | 0 ...used-by-other-collection-not-installed.css | 0 ...d-by-other-collection-not-installed.liquid | 0 tsconfig.tsbuildinfo | 2 +- 19 files changed, 209 insertions(+), 121 deletions(-) rename src/commands/theme/component/{map.ts => manifest.ts} (54%) create mode 100644 src/utilities/validate.ts rename test/commands/theme/component/{map.test.ts => manifest.test.ts} (83%) create mode 100644 test/fixtures/collection-b/component.manifest.json create mode 100644 test/fixtures/collection-b/components/belongs-only-to-this-collection/assets/belongs-only-to-this-collection.css create mode 100644 test/fixtures/collection-b/components/belongs-only-to-this-collection/belongs-only-to-this-collection.liquid create mode 100644 test/fixtures/collection-b/components/used-by-other-collection-already-installed/used-by-other-collection-already-installed.liquid create mode 100644 test/fixtures/collection-b/components/uses-component-from-other-collection/uses-component-from-other-collection.liquid create mode 100644 test/fixtures/collection-b/package.json create mode 100644 test/fixtures/collection/components/used-by-other-collection-already-installed/used-by-other-collection-already-installed.liquid create mode 100644 test/fixtures/collection/components/used-by-other-collection-not-installed/assets/used-by-other-collection-not-installed.css create mode 100644 test/fixtures/collection/components/used-by-other-collection-not-installed/used-by-other-collection-not-installed.liquid diff --git a/package.json b/package.json index 4f0e5b6..68c63a6 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "build": "shx rm -rf dist && tsc -b", "watch": "tsc --watch", "lint": "eslint . --ext .ts", + "lint:fix": "eslint . --ext .ts --fix", "test": "mocha --forbid-only \"test/**/*.test.ts\"", "posttest": "npm run lint", "prepack": "oclif manifest && oclif readme", diff --git a/src/commands/theme/component/install.ts b/src/commands/theme/component/install.ts index f92ee2d..c8737f2 100644 --- a/src/commands/theme/component/install.ts +++ b/src/commands/theme/component/install.ts @@ -13,7 +13,7 @@ import Flags from '../../../utilities/flags.js' import GenerateImportMap from '../generate/import-map.js' import Clean from './clean.js' import Copy from './copy.js' -import Map from './map.js' +import Manifest from './manifest.js' export default class Install extends BaseCommand { static override args = Args.getDefinitions([ @@ -39,7 +39,7 @@ export default class Install extends BaseCommand { } public async run(): Promise { - await Map.run([this.args[Args.THEME_DIR]!]) + await Manifest.run([this.args[Args.THEME_DIR]!]) await Copy.run([this.args[Args.THEME_DIR]!]) await Clean.run([this.args[Args.THEME_DIR]!]) await GenerateImportMap.run([this.args[Args.THEME_DIR]!, '--quiet']) diff --git a/src/commands/theme/component/map.ts b/src/commands/theme/component/manifest.ts similarity index 54% rename from src/commands/theme/component/map.ts rename to src/commands/theme/component/manifest.ts index fc7f8ff..653325b 100644 --- a/src/commands/theme/component/map.ts +++ b/src/commands/theme/component/manifest.ts @@ -12,13 +12,16 @@ import Args from '../../../utilities/args.js' import BaseCommand from '../../../utilities/base-command.js' import Flags from '../../../utilities/flags.js' import { getLastCommitHash } from '../../../utilities/git.js' -import { ManifestOptions, generateManifestFileForTheme, getManifest } from '../../../utilities/manifest.js' +import { ManifestOptions, generateManifestFile, getManifest } from '../../../utilities/manifest.js' +import { getCollectionNodes, getThemeNodes } from '../../../utilities/nodes.js' import { sortObjectKeys } from '../../../utilities/objects.js' import { getNameFromPackageJson, getVersionFromPackageJson } from '../../../utilities/package-json.js' +import { LiquidNode } from '../../../utilities/types.js' +import { isComponentRepo, isThemeRepo } from '../../../utilities/validate.js' export default class Manifest extends BaseCommand { static override args = Args.getDefinitions([ - Args.THEME_DIR, + Args.DEST_DIR, Args.COMPONENT_SELECTOR ]) @@ -42,23 +45,20 @@ export default class Manifest extends BaseCommand { } public async run(): Promise { - const currentDir = process.cwd() - const hasPackageJson = fs.existsSync(path.join(currentDir, 'package.json')) - const hasComponentsDir = fs.existsSync(path.join(currentDir, 'components')) + const sourceDir = process.cwd() - if (!hasPackageJson || !hasComponentsDir) { - this.error('Warning: Current directory does not appear to be a component collection. Expected to find package.json and components directory.') + if (!isComponentRepo(sourceDir)) { + this.error('Warning: Current directory does not appear to be a component collection or theme repository. Expected to find package.json and components directory.') } - const themeDir = path.resolve(currentDir, this.args[Args.THEME_DIR]) - const collectionDir = currentDir - const collectionName = this.flags[Flags.COLLECTION_NAME] || getNameFromPackageJson(process.cwd()) - const collectionVersion = this.flags[Flags.COLLECTION_VERSION] || getVersionFromPackageJson(process.cwd()) + const destinationDir = path.resolve(sourceDir, this.args[Args.DEST_DIR]) + const sourceName = this.flags[Flags.COLLECTION_NAME] || getNameFromPackageJson(process.cwd()) + const sourceVersion = this.flags[Flags.COLLECTION_VERSION] || getVersionFromPackageJson(process.cwd()) const ignoreConflicts = this.flags[Flags.IGNORE_CONFLICTS] const ignoreOverrides = this.flags[Flags.IGNORE_OVERRIDES] const componentSelector = this.args[Args.COMPONENT_SELECTOR] - - const manifestPath = path.join(themeDir, 'component.manifest.json') + + const manifestPath = path.join(destinationDir, 'component.manifest.json') const manifest = getManifest(manifestPath); const options: ManifestOptions = { @@ -67,18 +67,34 @@ export default class Manifest extends BaseCommand { ignoreOverrides } - const files = await generateManifestFileForTheme( + const sourceNodes = await getCollectionNodes(sourceDir) + + let destinationNodes: LiquidNode[] + let destinationName: string + + if (isThemeRepo(destinationDir)) { + destinationNodes = await getThemeNodes(destinationDir) + destinationName = '@theme' + } else if (isComponentRepo(destinationDir)) { + destinationNodes = await getCollectionNodes(destinationDir) + destinationName = '@collection' + } else { + this.error('Warning: Destination directory does not appear to be a theme repository or component collection.') + } + + const files = await generateManifestFile( manifest.files, - themeDir, - collectionDir, - collectionName, + destinationNodes, + destinationName, + sourceNodes, + sourceName, options ) manifest.files = sortObjectKeys(files) - manifest.collections[collectionName] = { - commit: getLastCommitHash(collectionDir), - version: collectionVersion + manifest.collections[sourceName] = { + commit: getLastCommitHash(sourceDir), + version: sourceVersion } fs.writeFileSync(manifestPath, JSON.stringify(sortObjectKeys(manifest), null, 2)) diff --git a/src/utilities/args.ts b/src/utilities/args.ts index c944466..d3bc446 100644 --- a/src/utilities/args.ts +++ b/src/utilities/args.ts @@ -1,16 +1,15 @@ import {Args as OclifArgs} from '@oclif/core' import { ArgInput } from '@oclif/core/interfaces' import {glob} from 'glob' -import fs from 'node:fs' -import path from 'node:path' import logger from './logger.js' +import {isComponentRepo, isThemeRepo} from './validate.js' export default class Args { [key: string]: any // eslint-disable-line @typescript-eslint/no-explicit-any static readonly COMPONENT_SELECTOR = 'componentSelector' + static readonly DEST_DIR = 'destDir' static readonly THEME_DIR = 'themeDir' - constructor(args: Record>) { Object.assign(this, args) } @@ -62,20 +61,27 @@ export const argDefinitions: Record = { required: false }), + [Args.DEST_DIR]: OclifArgs.string({ + description: 'path to component install directory', + async parse(input: string): Promise { + logger.debug(`Parsing destination directory argument '${input}'`) + + if (!isComponentRepo(input) && !isThemeRepo(input)) { + logger.error(new Error(`The provided path ${input} does not appear to be a valid component collection or theme repository.`), {exit: 1}) + } + + logger.debug(`Destination directory ${input} appears to be valid`) + return input + }, + required: true + }), + [Args.THEME_DIR]: OclifArgs.string({ description: 'path to theme directory', async parse(input: string): Promise { logger.debug(`Parsing theme directory argument '${input}'`) - const requiredFolders = ['layout', 'templates', 'config'] - let isThemeDirectory = true - - for (const folder of requiredFolders) { - if (!fs.existsSync(path.join(input, folder))) { - isThemeDirectory = false - } - } - if (!isThemeDirectory) { + if (!isThemeRepo(input)) { logger.error(new Error(`The provided path ${input} does not appear to contain valid theme files.`), {exit: 1}) } diff --git a/src/utilities/manifest.ts b/src/utilities/manifest.ts index 1ec3db3..d222d5a 100644 --- a/src/utilities/manifest.ts +++ b/src/utilities/manifest.ts @@ -1,7 +1,6 @@ import * as fs from 'node:fs' import logger from './logger.js' -import { getCollectionNodes, getThemeNodes } from './nodes.js' import { LiquidNode, Manifest } from './types.js' export function getManifest(path: string): Manifest { @@ -25,40 +24,39 @@ export interface ManifestOptions { } // eslint-disable-next-line max-params -export async function generateManifestFileForTheme( +export async function generateManifestFile( oldFilesMap: Manifest['files'], - themeDir: string, - collectionDir: string, - collectionName: string, + destinationNodes: LiquidNode[], + destinationName: string, + sourceNodes: LiquidNode[], + sourceName: string, options: ManifestOptions ): Promise { - const collectionNodes = await getCollectionNodes(collectionDir) - const themeNodes = await getThemeNodes(themeDir) - const entryPointNodes = themeNodes.filter(node => node.type === 'entry') + const entryPointNodes = destinationNodes.filter(node => node.type === 'component' || node.type === 'entry') const newFilesMap: Manifest['files'] = { assets: {}, snippets: {} } - // Track collection nodes that are selected or children of selected components - const selectedCollectionNodes = new Set() + // Track source nodes that are selected or children of selected source nodes + const selectedSourceNodes = new Set() function addNodeAndChildren(nodeName: string, visited = new Set()) { if (visited.has(nodeName)) return // Prevent infinite recursion visited.add(nodeName) - const node = collectionNodes.find(n => n.name === nodeName) + const node = sourceNodes.find(n => n.name === nodeName) if (!node) return - selectedCollectionNodes.add(node) + selectedSourceNodes.add(node) // Add all child snippets and recursively check their children if (node.snippets) { for (const snippet of node.snippets) { - const snippetNode = collectionNodes.find(n => n.name === snippet) + const snippetNode = sourceNodes.find(n => n.name === snippet) if (snippetNode) { - selectedCollectionNodes.add(snippetNode) + selectedSourceNodes.add(snippetNode) addNodeAndChildren(snippet, visited) } } @@ -67,9 +65,9 @@ export async function generateManifestFileForTheme( // Add all child assets and recursively check their dependencies if (node.assets) { for (const asset of node.assets) { - const assetNode = collectionNodes.find(n => n.name === asset) + const assetNode = sourceNodes.find(n => n.name === asset) if (assetNode) { - selectedCollectionNodes.add(assetNode) + selectedSourceNodes.add(assetNode) addNodeAndChildren(asset, visited) } } @@ -80,36 +78,36 @@ export async function generateManifestFileForTheme( const selectedComponents = options.componentSelector.split(',') for (const component of selectedComponents) addNodeAndChildren(`${component}.liquid`) // Throw error if no components were matched - if (selectedCollectionNodes.size === 0) { + if (selectedSourceNodes.size === 0) { logger.error(`No components found matching selector: ${options.componentSelector}`) } } - for (const node of themeNodes) { - // Add theme nodes not present in the old import map + for (const node of destinationNodes) { + // Add destination nodes not present in the old import map // They have been added manually by the user since the last time the import map was generated - if ((node.type === 'snippet' || node.type === 'asset') && !oldFilesMap[node.themeFolder]?.[node.name]) { - const collectionNode = collectionNodes.find(n => n.themeFolder === node.themeFolder && n.name === node.name) + if ((node.type === 'snippet' || node.type === 'asset' || node.type === 'component') && !oldFilesMap[node.themeFolder]?.[node.name]) { + const destinationNode = sourceNodes.find(n => n.themeFolder === node.themeFolder && n.name === node.name) - if (collectionNode) { + if (destinationNode) { if (options.ignoreConflicts) { - // If the user has passed the --ignore-conflicts flag, skip the node so it can be logged later as a component entry + // If the user has passed the --ignore-conflicts flag, skip the node so it can be logged later as a source entry continue; } else { - // If the node also exists in the collection, warn the user of the potential conflict but keep as a @theme entry - newFilesMap[node.themeFolder][node.name] = '@theme' - logger.log(`Conflict Warning: Pre-existing file ${node.themeFolder}/${node.name} without mapping conflicts with file in ${collectionName}. Keeping the theme file.`) + // If the node also exists in the source, warn the user of the potential conflict but keep as a destination entry + newFilesMap[node.themeFolder][node.name] = destinationName + logger.log(`Conflict Warning: Pre-existing file ${node.themeFolder}/${node.name} without mapping conflicts with file in ${sourceName}. Keeping the file from ${destinationName}.`) } } else { - // If the node does not exist in the collection, add it to the new import map as a @theme entry - newFilesMap[node.themeFolder][node.name] = '@theme' + // If the node does not exist in the source, add it to the new import map as a destination entry + newFilesMap[node.themeFolder][node.name] = destinationName } } - // Persist prexisting asset entries from @theme or other collections + // Persist prexisting asset entries from destination if (node.type === 'asset') { const oldImportMapValue = oldFilesMap[node.themeFolder]?.[node.name] - if (oldImportMapValue !== collectionName && typeof oldImportMapValue === 'string') { + if (oldImportMapValue !== sourceName && typeof oldImportMapValue === 'string') { newFilesMap[node.themeFolder][node.name] = oldImportMapValue } } @@ -122,24 +120,24 @@ export async function generateManifestFileForTheme( // If the new import map value is already defined, we don't need to add it again if (newImportMapValue !== undefined) return - if (oldImportMapValue !== collectionName && typeof oldImportMapValue === 'string') { - // If the import map value is not our collection but is defined - let node = themeNodes.find(node => node.themeFolder === themeFolder && node.name === name) + if (oldImportMapValue !== sourceName && typeof oldImportMapValue === 'string') { + // If the import map value is not in our source but is defined + let node = destinationNodes.find(node => node.themeFolder === themeFolder && node.name === name) if (node) { - const collectionNode = collectionNodes.find(node => node.themeFolder === themeFolder && node.name === name) - if (collectionNode) { - // If the node also exists in the collection, it's considered an override + const destinationNode = sourceNodes.find(node => node.themeFolder === themeFolder && node.name === name) + if (destinationNode) { + // If the destination node also exists in our source, it's considered an override if (options.ignoreOverrides) { - // If the user has passed the --ignore-overrides, set the new import map value to the collection name - newFilesMap[node.themeFolder][node.name] = collectionName - node = collectionNode + // If the user has passed the --ignore-overrides, set the new import map value to the source name + newFilesMap[node.themeFolder][node.name] = sourceName + node = destinationNode } else { // If the user has not passed the --ignore-overrides flag, keep the override newFilesMap[node.themeFolder][node.name] = oldImportMapValue - logger.log(`Override Warning: ${node.themeFolder}/${node.name} is being overridden by the collection ${collectionName}.`) + logger.log(`Override Warning: ${node.themeFolder}/${node.name} is being overridden by the collection ${sourceName}.`) } } else { - // If the node does not exist in the collection, add it to the new import map + // If the node does not exist in the source, add it to the new import map newFilesMap[node.themeFolder][node.name] = oldImportMapValue } @@ -150,17 +148,17 @@ export async function generateManifestFileForTheme( } } } - } else if (oldImportMapValue === collectionName || oldImportMapValue === undefined) { + } else if (oldImportMapValue === sourceName || oldImportMapValue === undefined) { // Skip if we have a component selector (not '*') and this node is not selected or a child of a selected component - if (options.componentSelector && options.componentSelector !== '*' && ![...selectedCollectionNodes].some(node => node.name === name)) { + if (options.componentSelector && options.componentSelector !== '*' && ![...selectedSourceNodes].some(node => node.name === name)) { return } // If the import map value is set our collection or undefined - const node = collectionNodes.find(node => node.themeFolder === themeFolder && node.name === name) + const node = sourceNodes.find(node => node.themeFolder === themeFolder && node.name === name) if (node) { // If the node exists in the collection, add it to the new import map - newFilesMap[node.themeFolder][node.name] = collectionName + newFilesMap[node.themeFolder][node.name] = sourceName if (node.assets.length > 0) { // If the node is a component, add its assets to the new import map for (const asset of node.assets) addFilesMapEntry('assets', asset) @@ -178,8 +176,12 @@ export async function generateManifestFileForTheme( // Build out the import map for the theme and collection for (const node of entryPointNodes) { - for (const snippet of node.snippets) { - addFilesMapEntry('snippets', snippet) + if (node.type === 'component') { + addFilesMapEntry('snippets', node.name) + } else if (node.type === 'entry') { + for (const snippet of node.snippets) { + addFilesMapEntry('snippets', snippet) + } } } diff --git a/src/utilities/validate.ts b/src/utilities/validate.ts new file mode 100644 index 0000000..d5012eb --- /dev/null +++ b/src/utilities/validate.ts @@ -0,0 +1,25 @@ +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; + +/** + * Checks if a directory is a theme repository by looking for required theme directories + * @param directory The directory to check + * @returns boolean indicating if the directory is a theme repository + */ +export function isThemeRepo(directory: string): boolean { + const requiredDirs = ['sections', 'templates', 'layout', 'assets', 'config']; + + return requiredDirs.every(dir => + existsSync(join(directory, dir)) + ); +} + +/** + * Checks if a directory is a component repository by looking for package.json and components directory + * @param directory The directory to check + * @returns boolean indicating if the directory is a component repository + */ +export function isComponentRepo(directory: string): boolean { + return existsSync(join(directory, 'package.json')) && + existsSync(join(directory, 'components')); +} diff --git a/test/commands/theme/component/install.test.ts b/test/commands/theme/component/install.test.ts index 36ef1b6..c50032e 100644 --- a/test/commands/theme/component/install.test.ts +++ b/test/commands/theme/component/install.test.ts @@ -7,7 +7,7 @@ import sinon from 'sinon' import Clean from '../../../../src/commands/theme/component/clean.js' import Copy from '../../../../src/commands/theme/component/copy.js' import Install from '../../../../src/commands/theme/component/install.js' -import Map from '../../../../src/commands/theme/component/map.js' +import Manifest from '../../../../src/commands/theme/component/manifest.js' import GenerateImportMap from '../../../../src/commands/theme/generate/import-map.js' const __dirname = path.dirname(fileURLToPath(import.meta.url)) @@ -19,14 +19,14 @@ const testThemePath = path.join(fixturesPath, 'test-theme') describe('theme component install', () => { let sandbox: sinon.SinonSandbox - let mapRunStub: sinon.SinonStub + let manifestRunStub: sinon.SinonStub let copyRunStub: sinon.SinonStub let cleanRunStub: sinon.SinonStub let generateRunStub: sinon.SinonStub beforeEach(async () => { sandbox = sinon.createSandbox() - mapRunStub = sandbox.stub(Map.prototype, 'run').resolves() + manifestRunStub = sandbox.stub(Manifest.prototype, 'run').resolves() copyRunStub = sandbox.stub(Copy.prototype, 'run').resolves() cleanRunStub = sandbox.stub(Clean.prototype, 'run').resolves() generateRunStub = sandbox.stub(GenerateImportMap.prototype, 'run').resolves() @@ -42,9 +42,9 @@ describe('theme component install', () => { fs.rmSync(testThemePath, { force: true, recursive: true }) }) - it('runs the theme component map command', async () => { + it('runs the theme component manifest command', async () => { await Install.run([testThemePath]) - expect(mapRunStub.calledOnce).to.be.true + expect(manifestRunStub.calledOnce).to.be.true }) it('runs the theme component copy command', async () => { @@ -64,6 +64,6 @@ describe('theme component install', () => { it('runs sub-commands in correct order', async () => { await Install.run([testThemePath]) - sinon.assert.callOrder(mapRunStub, copyRunStub, cleanRunStub, generateRunStub) + sinon.assert.callOrder(manifestRunStub, copyRunStub, cleanRunStub, generateRunStub) }) }) diff --git a/test/commands/theme/component/map.test.ts b/test/commands/theme/component/manifest.test.ts similarity index 83% rename from test/commands/theme/component/map.test.ts rename to test/commands/theme/component/manifest.test.ts index 293bac0..96f18cb 100644 --- a/test/commands/theme/component/map.test.ts +++ b/test/commands/theme/component/manifest.test.ts @@ -8,31 +8,34 @@ import {fileURLToPath} from 'node:url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const fixturesPath = path.join(__dirname, '../../../fixtures') const collectionPath = path.join(__dirname, '../../../fixtures/collection') +const collectionBPath = path.join(__dirname, '../../../fixtures/collection-b') const themePath = path.join(__dirname, '../../../fixtures/theme') const testCollectionPath = path.join(fixturesPath, 'test-collection') +const testCollectionBPath = path.join(fixturesPath, 'test-collection-b') const testThemePath = path.join(fixturesPath, 'test-theme') - -describe('theme component map', () => { +describe('theme component manifest', () => { beforeEach(() => { fs.cpSync(collectionPath, testCollectionPath, {recursive: true}) + fs.cpSync(collectionBPath, testCollectionBPath, {recursive: true}) fs.cpSync(themePath, testThemePath, {recursive: true}) process.chdir(testCollectionPath) }) afterEach(() => { fs.rmSync(testCollectionPath, {force: true, recursive: true}) + fs.rmSync(testCollectionBPath, {force: true, recursive: true}) fs.rmSync(testThemePath, {force: true, recursive: true}) }) it('should throw an error if the cwd is not a component collection', async () => { process.chdir(testThemePath) - const {error} = await runCommand(['theme', 'component', 'map', testThemePath]) + const {error} = await runCommand(['theme', 'component', 'manifest', testThemePath]) expect(error).to.be.instanceOf(Error) - expect(error?.message).to.include('Warning: Current directory does not appear to be a component collection. Expected to find package.json and components directory.') + expect(error?.message).to.include('Warning: ') }) it('should throw an error if a theme directory is not provided', async () => { - const {error} = await runCommand(['theme', 'component', 'map']) + const {error} = await runCommand(['theme', 'component', 'manifest']) expect(error).to.be.instanceOf(Error) expect(error?.message).to.include('Missing 1 required arg:') }) @@ -42,7 +45,7 @@ describe('theme component map', () => { fs.rmSync(path.join(testThemePath, 'component.manifest.json'), {force: true}) expect(fs.existsSync(path.join(testThemePath, 'component.manifest.json'))).to.be.false - await runCommand(['theme', 'component', 'map', testThemePath]) + await runCommand(['theme', 'component', 'manifest', testThemePath]) // Check that the file was created expect(fs.existsSync(path.join(testThemePath, 'component.manifest.json'))).to.be.true @@ -52,7 +55,7 @@ describe('theme component map', () => { const beforeData = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(beforeData.collections['@archetype-themes/test-collection'].version).to.equal('1.0.0') - await runCommand(['theme', 'component', 'map', testThemePath]) + await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(data.collections['@archetype-themes/test-collection'].version).to.equal('1.0.1') @@ -64,7 +67,7 @@ describe('theme component map', () => { expect(beforeData.files.assets['missing.css']).to.be.undefined expect(beforeData.files.snippets['missing.liquid']).to.be.undefined - await runCommand(['theme', 'component', 'map', testThemePath]) + await runCommand(['theme', 'component', 'manifest', testThemePath]) // Check that missing entries are present in map const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) @@ -72,12 +75,26 @@ describe('theme component map', () => { expect(data.files.snippets['missing.liquid']).to.equal('@theme') }) - it('adds entries for newly referenced components from current collection', async () => { + it('adds entries for newly referenced components from current collection to another collection', async () => { + // Check that missing entries are not present in map + const beforeData = JSON.parse(fs.readFileSync(path.join(testCollectionBPath, 'component.manifest.json'), 'utf8')) + expect(beforeData.files.assets['used-by-other-collection-not-installed.css']).to.be.undefined + expect(beforeData.files.snippets['used-by-other-collection-not-installed.liquid']).to.be.undefined + + await runCommand(['theme', 'component', 'manifest', testCollectionBPath]) + + // Check that missing entries are present in map + const data = JSON.parse(fs.readFileSync(path.join(testCollectionBPath, 'component.manifest.json'), 'utf8')) + expect(data.files.assets['used-by-other-collection-not-installed.css']).to.equal('@archetype-themes/test-collection') + expect(data.files.snippets['used-by-other-collection-not-installed.liquid']).to.equal('@archetype-themes/test-collection') + }) + + it('adds entries for newly referenced components from current collection to a theme', async () => { const beforeData = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(beforeData.files.snippets['new.liquid']).to.be.undefined expect(beforeData.files.assets['new.css']).to.be.undefined - await runCommand(['theme', 'component', 'map', testThemePath]) + await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(data.files.snippets['new.liquid']).to.equal('@archetype-themes/test-collection') @@ -89,7 +106,7 @@ describe('theme component map', () => { expect(beforeData.files.snippets['parent.liquid']).to.be.undefined expect(beforeData.files.snippets['child.liquid']).to.be.undefined - await runCommand(['theme', 'component', 'map', testThemePath]) + await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(data.files.snippets['parent.liquid']).to.equal('@archetype-themes/test-collection') @@ -97,28 +114,28 @@ describe('theme component map', () => { }) it('throws a warning if there is a potential conflict with an entry in the current collection', async () => { - const {stdout} = await runCommand(['theme', 'component', 'map', testThemePath]) + const {stdout} = await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(stdout).to.include('Conflict Warning: Pre-existing file') expect(data.files.snippets['conflict.liquid']).to.equal('@theme') }) it('ignores conflicts if --ignore-conflicts flag is passed', async () => { - const {stdout} = await runCommand(['theme', 'component', 'map', testThemePath, '--ignore-conflicts']) + const {stdout} = await runCommand(['theme', 'component', 'manifest', testThemePath, '--ignore-conflicts']) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(stdout).to.not.include('Conflict Warning: Pre-existing file') expect(data.files.snippets['conflict.liquid']).to.equal('@archetype-themes/test-collection') }) it('throws a warning when an override is detected', async () => { - const {stdout} = await runCommand(['theme', 'component', 'map', testThemePath]) + const {stdout} = await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(stdout).to.include('Override Warning:') expect(data.files.snippets['override.liquid']).to.equal('@theme') }) it('overriden parent still references non-overridden child from collection', async () => { - const {stdout} = await runCommand(['theme', 'component', 'map', testThemePath]) + const {stdout} = await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(stdout).to.include('Override Warning:') expect(data.files.snippets['override-parent.liquid']).to.equal('@theme') @@ -127,7 +144,7 @@ describe('theme component map', () => { }) it('ignores overrides if --ignore-overrides flag is passed', async () => { - const {stdout} = await runCommand(['theme', 'component', 'map', testThemePath, '--ignore-overrides']) + const {stdout} = await runCommand(['theme', 'component', 'manifest', testThemePath, '--ignore-overrides']) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(stdout).to.not.include('Override Warning:') expect(data.files.snippets['override.liquid']).to.equal('@archetype-themes/test-collection') @@ -142,7 +159,7 @@ describe('theme component map', () => { const beforeData = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(beforeData.files.snippets['removed.liquid']).to.equal('@archetype-themes/test-collection') - await runCommand(['theme', 'component', 'map', testThemePath]) + await runCommand(['theme', 'component', 'manifest', testThemePath]) // Check that old entries are removed from map const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) @@ -150,7 +167,7 @@ describe('theme component map', () => { }) it('does not add entries for unreferenced components from current collection', async () => { - await runCommand(['theme', 'component', 'map', testThemePath]) + await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(data.files.snippets['unreferenced.liquid']).to.be.undefined expect(data.files.assets['unreferenced.css']).to.be.undefined @@ -172,7 +189,7 @@ describe('theme component map', () => { expect(beforeData.files.snippets['other-collection-component-removed.liquid']).to.equal('@other/collection') expect(beforeData.files.assets['other-collection-component-removed.css']).to.equal('@other/collection') - await runCommand(['theme', 'component', 'map', testThemePath]) + await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) @@ -190,7 +207,7 @@ describe('theme component map', () => { }) it('sorts the files and collections keys in the component.map.json file', async () => { - await runCommand(['theme', 'component', 'map', testThemePath]) + await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) // Check that the files keys are sorted alphabetically const filesKeys = Object.keys(data.files) @@ -210,7 +227,7 @@ describe('theme component map', () => { }) it('should only include specified components when using component selector', async () => { - await runCommand(['theme', 'component', 'map', testThemePath, 'new']) + await runCommand(['theme', 'component', 'manifest', testThemePath, 'new']) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) @@ -224,7 +241,7 @@ describe('theme component map', () => { }) it('should include multiple components when using comma-separated component selector', async () => { - await runCommand(['theme', 'component', 'map', testThemePath, 'new,parent']) + await runCommand(['theme', 'component', 'manifest', testThemePath, 'new,parent']) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) @@ -237,7 +254,7 @@ describe('theme component map', () => { it('should only match component type nodes when using component selector', async () => { // Try to select a snippet that's not a component - await runCommand(['theme', 'component', 'map', testThemePath, 'child']) + await runCommand(['theme', 'component', 'manifest', testThemePath, 'child']) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) @@ -248,7 +265,7 @@ describe('theme component map', () => { it('should include all components when using "*" as component selector', async () => { const beforeData = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) - await runCommand(['theme', 'component', 'map', testThemePath, '*']) + await runCommand(['theme', 'component', 'manifest', testThemePath, '*']) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) @@ -270,7 +287,7 @@ describe('theme component map', () => { it('should throw an error when no components match the selector', async () => { const beforeData = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) - const {error} = await runCommand(['theme', 'component', 'map', testThemePath, 'non-existent']) + const {error} = await runCommand(['theme', 'component', 'manifest', testThemePath, 'non-existent']) // Should throw an error expect(error).to.be.instanceOf(Error) @@ -283,7 +300,7 @@ describe('theme component map', () => { }) it('should include shared js assets referenced in other JS files in the manifest', async () => { - await runCommand(['theme', 'component', 'map', testThemePath]) + await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(data.files.assets['shared-script.js']).to.equal('@archetype-themes/test-collection') @@ -293,21 +310,21 @@ describe('theme component map', () => { }) it('should detect JS imports from script tags with {{ "filename" | asset_url }} filter', async () => { - await runCommand(['theme', 'component', 'map', testThemePath]) + await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(data.files.assets['script-with-filter.js']).to.equal('@archetype-themes/test-collection') }) it('should detect JS imports snippets inside components', async () => { - await runCommand(['theme', 'component', 'map', testThemePath]) + await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(data.files.assets['script-snippet-import.js']).to.equal('@archetype-themes/test-collection') }) it('should detect JS imports from script tags with import statements', async () => { - await runCommand(['theme', 'component', 'map', testThemePath]) + await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(data.files.assets['script-with-import.js']).to.equal('@archetype-themes/test-collection') @@ -315,7 +332,7 @@ describe('theme component map', () => { }) it('should not include commented out script imports', async () => { - await runCommand(['theme', 'component', 'map', testThemePath]) + await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(data.files.assets['commented-script.js']).to.be.undefined @@ -335,7 +352,7 @@ describe('theme component map', () => { encoding: 'utf8' }).trim() - await runCommand(['theme', 'component', 'map', testThemePath]) + await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(data.collections['@archetype-themes/test-collection'].commit).to.equal(expectedHash) diff --git a/test/commands/theme/generate/import-map.test.ts b/test/commands/theme/generate/import-map.test.ts index 85d89c2..664b503 100644 --- a/test/commands/theme/generate/import-map.test.ts +++ b/test/commands/theme/generate/import-map.test.ts @@ -77,14 +77,12 @@ describe('theme generate import-map', () => { fs.rmSync(path.join(testThemePath, 'assets'), {force: true, recursive: true}) const {error} = await runCommand(['theme:generate:import-map', testThemePath]) expect(error).to.be.instanceOf(Error) - expect(error?.message).to.include(`Assets directory not found. Please ensure ${path.resolve(testThemePath)} is a theme directory.`) }) it('handles missing snippets directory', async () => { fs.rmSync(path.join(testThemePath, 'snippets'), {force: true, recursive: true}) const {error} = await runCommand(['theme:generate:import-map', testThemePath]) expect(error).to.be.instanceOf(Error) - expect(error?.message).to.include(`Snippets directory not found. Please ensure ${path.resolve(testThemePath)} is a theme directory.`) }) it('updates existing import map', async () => { diff --git a/test/fixtures/collection-b/component.manifest.json b/test/fixtures/collection-b/component.manifest.json new file mode 100644 index 0000000..1e59de8 --- /dev/null +++ b/test/fixtures/collection-b/component.manifest.json @@ -0,0 +1,17 @@ +{ + "collections": { + "@archetype-themes/test-collection": { + "version": "1.0.0" + } + }, + "files": { + "snippets": { + "belongs-only-to-this-collection.liquid": "@collection", + "uses-component-from-other-collection.liquid": "@collection", + "used-by-other-collection-already-installed.liquid": "@archetype-themes/test-collection" + }, + "assets": { + "belongs-only-to-this-collection.css": "@collection" + } + } +} \ No newline at end of file diff --git a/test/fixtures/collection-b/components/belongs-only-to-this-collection/assets/belongs-only-to-this-collection.css b/test/fixtures/collection-b/components/belongs-only-to-this-collection/assets/belongs-only-to-this-collection.css new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/collection-b/components/belongs-only-to-this-collection/belongs-only-to-this-collection.liquid b/test/fixtures/collection-b/components/belongs-only-to-this-collection/belongs-only-to-this-collection.liquid new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/collection-b/components/used-by-other-collection-already-installed/used-by-other-collection-already-installed.liquid b/test/fixtures/collection-b/components/used-by-other-collection-already-installed/used-by-other-collection-already-installed.liquid new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/collection-b/components/uses-component-from-other-collection/uses-component-from-other-collection.liquid b/test/fixtures/collection-b/components/uses-component-from-other-collection/uses-component-from-other-collection.liquid new file mode 100644 index 0000000..a6ac2df --- /dev/null +++ b/test/fixtures/collection-b/components/uses-component-from-other-collection/uses-component-from-other-collection.liquid @@ -0,0 +1,2 @@ +{% render 'used-by-other-collection-already-installed' %} +{% render 'used-by-other-collection-not-installed' %} \ No newline at end of file diff --git a/test/fixtures/collection-b/package.json b/test/fixtures/collection-b/package.json new file mode 100644 index 0000000..337da59 --- /dev/null +++ b/test/fixtures/collection-b/package.json @@ -0,0 +1,4 @@ +{ + "name": "@archetype-themes/test-collection-b", + "version": "1.0.1" +} \ No newline at end of file diff --git a/test/fixtures/collection/components/used-by-other-collection-already-installed/used-by-other-collection-already-installed.liquid b/test/fixtures/collection/components/used-by-other-collection-already-installed/used-by-other-collection-already-installed.liquid new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/collection/components/used-by-other-collection-not-installed/assets/used-by-other-collection-not-installed.css b/test/fixtures/collection/components/used-by-other-collection-not-installed/assets/used-by-other-collection-not-installed.css new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/collection/components/used-by-other-collection-not-installed/used-by-other-collection-not-installed.liquid b/test/fixtures/collection/components/used-by-other-collection-not-installed/used-by-other-collection-not-installed.liquid new file mode 100644 index 0000000..e69de29 diff --git a/tsconfig.tsbuildinfo b/tsconfig.tsbuildinfo index 122e397..eedaaed 100644 --- a/tsconfig.tsbuildinfo +++ b/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/index.ts","./src/commands/theme/component/clean.ts","./src/commands/theme/component/copy.ts","./src/commands/theme/component/dev.ts","./src/commands/theme/component/index.ts","./src/commands/theme/component/install.ts","./src/commands/theme/component/map.ts","./src/commands/theme/generate/import-map.ts","./src/commands/theme/generate/template-map.ts","./src/commands/theme/locale/clean.ts","./src/commands/theme/locale/sync.ts","./src/utilities/args.ts","./src/utilities/base-command.ts","./src/utilities/files.ts","./src/utilities/flags.ts","./src/utilities/git.ts","./src/utilities/locales.ts","./src/utilities/logger.ts","./src/utilities/manifest.ts","./src/utilities/nodes.ts","./src/utilities/objects.ts","./src/utilities/package-json.ts","./src/utilities/setup.ts","./src/utilities/types.ts"],"version":"5.7.2"} \ No newline at end of file +{"root":["./src/index.ts","./src/commands/theme/component/clean.ts","./src/commands/theme/component/copy.ts","./src/commands/theme/component/dev.ts","./src/commands/theme/component/index.ts","./src/commands/theme/component/install.ts","./src/commands/theme/component/manifest.ts","./src/commands/theme/generate/import-map.ts","./src/commands/theme/generate/template-map.ts","./src/commands/theme/locale/clean.ts","./src/commands/theme/locale/sync.ts","./src/utilities/args.ts","./src/utilities/base-command.ts","./src/utilities/files.ts","./src/utilities/flags.ts","./src/utilities/git.ts","./src/utilities/locales.ts","./src/utilities/logger.ts","./src/utilities/manifest.ts","./src/utilities/nodes.ts","./src/utilities/objects.ts","./src/utilities/package-json.ts","./src/utilities/setup.ts","./src/utilities/types.ts","./src/utilities/validate.ts"],"version":"5.7.2"} \ No newline at end of file From 27e7259e539c7305d5dc92aaf435f734ae329664 Mon Sep 17 00:00:00 2001 From: Thomas Kelly Date: Wed, 7 May 2025 15:30:47 -0400 Subject: [PATCH 3/8] Update copy and clean commands --- src/commands/theme/component/clean.ts | 33 ++++++---- src/commands/theme/component/copy.ts | 61 +++++++++++++------ src/commands/theme/component/install.ts | 10 +-- src/commands/theme/generate/import-map.ts | 4 +- test/commands/theme/component/copy.test.ts | 24 +++++++- .../collection-b/component.manifest.json | 9 ++- .../used-by-other-collection-to-be-copied.css | 0 .../used-by-other-collection-to-be-copied.js | 1 + .../setup/sections/test-section.liquid | 0 ...her-collection-to-be-copied.snippet.liquid | 0 .../test/some-test-file.js | 0 ...ed-by-other-collection-to-be-copied.liquid | 1 + .../shared-script-used-by-other-collection.js | 0 13 files changed, 101 insertions(+), 42 deletions(-) create mode 100644 test/fixtures/collection/components/used-by-other-collection-to-be-copied/assets/used-by-other-collection-to-be-copied.css create mode 100644 test/fixtures/collection/components/used-by-other-collection-to-be-copied/assets/used-by-other-collection-to-be-copied.js create mode 100644 test/fixtures/collection/components/used-by-other-collection-to-be-copied/setup/sections/test-section.liquid create mode 100644 test/fixtures/collection/components/used-by-other-collection-to-be-copied/snippets/used-by-other-collection-to-be-copied.snippet.liquid create mode 100644 test/fixtures/collection/components/used-by-other-collection-to-be-copied/test/some-test-file.js create mode 100644 test/fixtures/collection/components/used-by-other-collection-to-be-copied/used-by-other-collection-to-be-copied.liquid create mode 100644 test/fixtures/collection/scripts/shared-script-used-by-other-collection.js diff --git a/src/commands/theme/component/clean.ts b/src/commands/theme/component/clean.ts index 5477fda..dc1ca9c 100644 --- a/src/commands/theme/component/clean.ts +++ b/src/commands/theme/component/clean.ts @@ -11,11 +11,13 @@ import path from 'node:path' import Args from '../../../utilities/args.js' import BaseCommand from '../../../utilities/base-command.js' import { getManifest } from '../../../utilities/manifest.js' -import { getThemeNodes } from '../../../utilities/nodes.js' +import { getCollectionNodes, getThemeNodes } from '../../../utilities/nodes.js' +import { LiquidNode } from '../../../utilities/types.js' +import { isComponentRepo, isThemeRepo } from '../../../utilities/validate.js' export default class Clean extends BaseCommand { static override args = Args.getDefinitions([ - Args.override(Args.THEME_DIR, { default: '.', required: false }) + Args.override(Args.DEST_DIR, { default: '.', required: false }) ]) static override description = 'Remove unused component files in a theme' @@ -29,21 +31,28 @@ export default class Clean extends BaseCommand { } public async run(): Promise { - const themeDir = path.resolve(process.cwd(), this.args[Args.THEME_DIR]) + const destinationDir = path.resolve(process.cwd(), this.args[Args.DEST_DIR]) - const manifest = getManifest(path.join(themeDir, 'component.manifest.json')) - const themeNodes = await getThemeNodes(themeDir) + const manifest = getManifest(path.join(destinationDir, 'component.manifest.json')) - // Remove files that are not in the component map - for (const node of themeNodes) { + let destinationNodes: LiquidNode[] + if (isThemeRepo(destinationDir)) { + destinationNodes = await getThemeNodes(destinationDir) + } else if (isComponentRepo(destinationDir)) { + destinationNodes = await getCollectionNodes(destinationDir) + } else { + this.error('Warning: Destination directory does not appear to be a theme or component collection.') + } + + // Remove files that are not in the component manifest + for (const node of destinationNodes) { if (node.type === 'snippet' || node.type === 'asset') { const collection = node.type === 'snippet' ? manifest.files.snippets : manifest.files.assets; - if (!collection[node.name]) { - const filePath = path.join(themeDir, node.themeFolder, node.name); - if (fs.existsSync(filePath)) { - fs.rmSync(filePath); - } + if (!collection[node.name] && fs.existsSync(node.file)) { + fs.rmSync(node.file); } + } else if (node.type === 'component' && !manifest.files.snippets[node.name] && fs.existsSync(node.file)) { + fs.rmSync(path.dirname(node.file), { recursive: true }); } } } diff --git a/src/commands/theme/component/copy.ts b/src/commands/theme/component/copy.ts index 757bcac..652f45a 100644 --- a/src/commands/theme/component/copy.ts +++ b/src/commands/theme/component/copy.ts @@ -15,10 +15,11 @@ import Flags from '../../../utilities/flags.js' import { getManifest } from '../../../utilities/manifest.js' import { getCollectionNodes } from '../../../utilities/nodes.js' import { getNameFromPackageJson, getVersionFromPackageJson } from '../../../utilities/package-json.js' +import { isComponentRepo, isThemeRepo } from '../../../utilities/validate.js' export default class Copy extends BaseCommand { static override args = Args.getDefinitions([ - Args.THEME_DIR + Args.DEST_DIR ]) static override description = 'Copy files from a component collection into a theme' @@ -38,38 +39,58 @@ export default class Copy extends BaseCommand { public async run(): Promise { const currentDir = process.cwd() - const hasPackageJson = fs.existsSync(path.join(currentDir, 'package.json')) - const hasComponentsDir = fs.existsSync(path.join(currentDir, 'components')) - if (!hasPackageJson || !hasComponentsDir) { + if (!isComponentRepo(currentDir)) { this.error('Warning: Current directory does not appear to be a component collection. Expected to find package.json and components directory.') } - const themeDir = path.resolve(currentDir, this.args[Args.THEME_DIR]) - const collectionName = this.flags[Flags.COLLECTION_NAME] || getNameFromPackageJson(process.cwd()) - const collectionVersion = this.flags[Flags.COLLECTION_VERSION] || getVersionFromPackageJson(process.cwd()) + const destinationDir = path.resolve(currentDir, this.args[Args.DEST_DIR]) + const sourceName = this.flags[Flags.COLLECTION_NAME] || getNameFromPackageJson(process.cwd()) + const sourceVersion = this.flags[Flags.COLLECTION_VERSION] || getVersionFromPackageJson(process.cwd()) - if (!fs.existsSync(path.join(themeDir, 'component.manifest.json'))) { - this.error('Error: component.manifest.json file not found in the theme directory. Run "shopify theme component map" to generate a component.manifest.json file.'); + if (!fs.existsSync(path.join(destinationDir, 'component.manifest.json'))) { + this.error('Error: component.manifest.json file not found in the destination directory. Run "shopify theme component map" to generate a component.manifest.json file.'); } - const manifest = getManifest(path.join(themeDir, 'component.manifest.json')) - const componentNodes = await getCollectionNodes(currentDir) + const manifest = getManifest(path.join(destinationDir, 'component.manifest.json')) + const sourceNodes = await getCollectionNodes(currentDir) - if (manifest.collections[collectionName].version !== collectionVersion) { - this.error(`Version mismatch: Expected ${collectionVersion} but found ${manifest.collections[collectionName].version}. Run "shopify theme component map" to update the component.manifest.json file.`); + if (manifest.collections[sourceName].version !== sourceVersion) { + this.error(`Version mismatch: Expected ${sourceVersion} but found ${manifest.collections[sourceName].version}. Run "shopify theme component map" to update the component.manifest.json file.`); } const copyManifestFiles = (fileType: 'assets' | 'snippets') => { for (const [fileName, fileCollection] of Object.entries(manifest.files[fileType])) { - if (fileCollection === collectionName) { - const node = componentNodes.find(node => node.name === fileName && node.themeFolder === fileType); - if (node) { - const src = node.file; - const dest = path.join(themeDir, fileType, fileName); - copyFileIfChanged(src, dest); + if (fileCollection !== sourceName) continue; + + const node = sourceNodes.find(node => node.name === fileName && node.themeFolder === fileType); + + if (!node) continue; + + if (isThemeRepo(destinationDir)) { + copyFileIfChanged(node.file, path.join(destinationDir, fileType, fileName)); + } else if (isComponentRepo(destinationDir)) { + const dest = node.file.replace(currentDir, destinationDir) + copyFileIfChanged(node.file, dest); + + if (node.type === 'component') { + // Copy setup and test folders if they exist + const setupSrcDir = path.join(path.dirname(node.file), 'setup'); + const setupDestDir = path.join(path.dirname(dest), 'setup'); + const testSrcDir = path.join(path.dirname(node.file), 'test'); + const testDestDir = path.join(path.dirname(dest), 'test'); + + if (fs.existsSync(setupSrcDir)) { + fs.mkdirSync(setupDestDir, { recursive: true }); + fs.cpSync(setupSrcDir, setupDestDir, { recursive: true }); + } + + if (fs.existsSync(testSrcDir)) { + fs.mkdirSync(testDestDir, { recursive: true }); + fs.cpSync(testSrcDir, testDestDir, { recursive: true }); + } } - } + } } }; diff --git a/src/commands/theme/component/install.ts b/src/commands/theme/component/install.ts index c8737f2..ba2a54e 100644 --- a/src/commands/theme/component/install.ts +++ b/src/commands/theme/component/install.ts @@ -17,7 +17,7 @@ import Manifest from './manifest.js' export default class Install extends BaseCommand { static override args = Args.getDefinitions([ - Args.THEME_DIR, + Args.DEST_DIR, Args.COMPONENT_SELECTOR ]) @@ -39,9 +39,9 @@ export default class Install extends BaseCommand { } public async run(): Promise { - await Manifest.run([this.args[Args.THEME_DIR]!]) - await Copy.run([this.args[Args.THEME_DIR]!]) - await Clean.run([this.args[Args.THEME_DIR]!]) - await GenerateImportMap.run([this.args[Args.THEME_DIR]!, '--quiet']) + await Manifest.run([this.args[Args.DEST_DIR]!]) + await Copy.run([this.args[Args.DEST_DIR]!]) + await Clean.run([this.args[Args.DEST_DIR]!]) + await GenerateImportMap.run([this.args[Args.DEST_DIR]!, '--quiet']) } } diff --git a/src/commands/theme/generate/import-map.ts b/src/commands/theme/generate/import-map.ts index bf60f66..d2a6f0c 100644 --- a/src/commands/theme/generate/import-map.ts +++ b/src/commands/theme/generate/import-map.ts @@ -14,13 +14,13 @@ import BaseCommand from '../../../utilities/base-command.js' import Flags from '../../../utilities/flags.js' export default class GenerateImportMap extends BaseCommand { static override args = Args.getDefinitions([ - Args.override(Args.THEME_DIR, { default: '.', required: false }) + Args.override(Args.DEST_DIR, { default: '.', required: false }) ]) static description = 'Generate an import map for JavaScript files in the assets directory' async run() { - const themeDir = path.resolve(process.cwd(), this.args[Args.THEME_DIR]) + const themeDir = path.resolve(process.cwd(), this.args[Args.DEST_DIR]) const assetsDir = path.join(themeDir, 'assets') const snippetsDir = path.join(themeDir, 'snippets') diff --git a/test/commands/theme/component/copy.test.ts b/test/commands/theme/component/copy.test.ts index 9d861a9..5658be6 100644 --- a/test/commands/theme/component/copy.test.ts +++ b/test/commands/theme/component/copy.test.ts @@ -7,19 +7,23 @@ import {fileURLToPath} from 'node:url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const fixturesPath = path.join(__dirname, '../../../fixtures') const collectionPath = path.join(__dirname, '../../../fixtures/collection') +const collectionBPath = path.join(__dirname, '../../../fixtures/collection-b') const themePath = path.join(__dirname, '../../../fixtures/theme') const testCollectionPath = path.join(fixturesPath, 'test-collection') +const testCollectionBPath = path.join(fixturesPath, 'test-collection-b') const testThemePath = path.join(fixturesPath, 'test-theme') describe('theme component copy', () => { beforeEach(() => { fs.cpSync(collectionPath, testCollectionPath, {recursive: true}) + fs.cpSync(collectionBPath, testCollectionBPath, {recursive: true}) fs.cpSync(themePath, testThemePath, {recursive: true}) process.chdir(testCollectionPath) }) afterEach(() => { fs.rmSync(testCollectionPath, {force: true, recursive: true}) + fs.rmSync(testCollectionBPath, {force: true, recursive: true}) fs.rmSync(testThemePath, {force: true, recursive: true}) }) @@ -35,7 +39,7 @@ describe('theme component copy', () => { fs.rmSync(path.join(testThemePath, 'component.manifest.json'), {force: true}) const {error} = await runCommand(['theme', 'component', 'copy', testThemePath]) expect(error).to.be.instanceOf(Error) - expect(error?.message).to.include('Error: component.manifest.json file not found in the theme directory.') + expect(error?.message).to.include('Error: component.manifest.json file not found in the destination directory.') }) it('throws an error if the version of the component collection does not match the version in the component.manifest.json file', async () => { @@ -59,4 +63,22 @@ describe('theme component copy', () => { expect(fs.existsSync(path.join(testThemePath, 'snippets', 'not-to-be-copied.liquid'))).to.be.false }) + + it('copies files from a component collection to another collection based on component.manifest.json', async () => { + const manifestPath = path.join(testCollectionBPath, 'component.manifest.json') + const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) + manifest.collections["@archetype-themes/test-collection"].version = "1.0.1" + fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2)) + await runCommand(['theme', 'component', 'copy', testCollectionBPath]) + + const componentPath = path.join(testCollectionBPath, 'components','used-by-other-collection-to-be-copied') + + expect(fs.existsSync(path.join(componentPath, 'used-by-other-collection-to-be-copied.liquid'))).to.be.true + expect(fs.existsSync(path.join(componentPath, 'snippets','used-by-other-collection-to-be-copied.snippet.liquid'))).to.be.true + expect(fs.existsSync(path.join(componentPath, 'assets','used-by-other-collection-to-be-copied.css'))).to.be.true + expect(fs.existsSync(path.join(componentPath, 'setup', 'sections', 'test-section.liquid'))).to.be.true + expect(fs.existsSync(path.join(componentPath, 'test', 'some-test-file.js'))).to.be.true + + expect(fs.existsSync(path.join(testCollectionBPath, 'scripts','shared-script-used-by-other-collection.js'))).to.be.true + }) }) diff --git a/test/fixtures/collection-b/component.manifest.json b/test/fixtures/collection-b/component.manifest.json index 1e59de8..3e75eb9 100644 --- a/test/fixtures/collection-b/component.manifest.json +++ b/test/fixtures/collection-b/component.manifest.json @@ -8,10 +8,15 @@ "snippets": { "belongs-only-to-this-collection.liquid": "@collection", "uses-component-from-other-collection.liquid": "@collection", - "used-by-other-collection-already-installed.liquid": "@archetype-themes/test-collection" + "used-by-other-collection-already-installed.liquid": "@archetype-themes/test-collection", + "used-by-other-collection-to-be-copied.liquid": "@archetype-themes/test-collection", + "used-by-other-collection-to-be-copied.snippet.liquid": "@archetype-themes/test-collection" }, "assets": { - "belongs-only-to-this-collection.css": "@collection" + "belongs-only-to-this-collection.css": "@collection", + "used-by-other-collection-already-installed.css": "@archetype-themes/test-collection", + "used-by-other-collection-to-be-copied.css": "@archetype-themes/test-collection", + "shared-script-used-by-other-collection.js": "@archetype-themes/test-collection" } } } \ No newline at end of file diff --git a/test/fixtures/collection/components/used-by-other-collection-to-be-copied/assets/used-by-other-collection-to-be-copied.css b/test/fixtures/collection/components/used-by-other-collection-to-be-copied/assets/used-by-other-collection-to-be-copied.css new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/collection/components/used-by-other-collection-to-be-copied/assets/used-by-other-collection-to-be-copied.js b/test/fixtures/collection/components/used-by-other-collection-to-be-copied/assets/used-by-other-collection-to-be-copied.js new file mode 100644 index 0000000..7f53f55 --- /dev/null +++ b/test/fixtures/collection/components/used-by-other-collection-to-be-copied/assets/used-by-other-collection-to-be-copied.js @@ -0,0 +1 @@ +import SharedScript from 'shared-script-used-by-other-collection' diff --git a/test/fixtures/collection/components/used-by-other-collection-to-be-copied/setup/sections/test-section.liquid b/test/fixtures/collection/components/used-by-other-collection-to-be-copied/setup/sections/test-section.liquid new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/collection/components/used-by-other-collection-to-be-copied/snippets/used-by-other-collection-to-be-copied.snippet.liquid b/test/fixtures/collection/components/used-by-other-collection-to-be-copied/snippets/used-by-other-collection-to-be-copied.snippet.liquid new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/collection/components/used-by-other-collection-to-be-copied/test/some-test-file.js b/test/fixtures/collection/components/used-by-other-collection-to-be-copied/test/some-test-file.js new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/collection/components/used-by-other-collection-to-be-copied/used-by-other-collection-to-be-copied.liquid b/test/fixtures/collection/components/used-by-other-collection-to-be-copied/used-by-other-collection-to-be-copied.liquid new file mode 100644 index 0000000..a22d241 --- /dev/null +++ b/test/fixtures/collection/components/used-by-other-collection-to-be-copied/used-by-other-collection-to-be-copied.liquid @@ -0,0 +1 @@ +{% render 'used-by-other-collection-to-be-copied.snippet' %} \ No newline at end of file diff --git a/test/fixtures/collection/scripts/shared-script-used-by-other-collection.js b/test/fixtures/collection/scripts/shared-script-used-by-other-collection.js new file mode 100644 index 0000000..e69de29 From b1870c1a57d6c421c06565e0259a05e8770dccb9 Mon Sep 17 00:00:00 2001 From: Thomas Kelly Date: Wed, 7 May 2025 15:48:38 -0400 Subject: [PATCH 4/8] Only run import-map within install if its a theme repo --- src/commands/theme/component/install.ts | 6 +++++- test/commands/theme/component/install.test.ts | 10 +++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/commands/theme/component/install.ts b/src/commands/theme/component/install.ts index ba2a54e..7feeed6 100644 --- a/src/commands/theme/component/install.ts +++ b/src/commands/theme/component/install.ts @@ -10,6 +10,7 @@ import Args from '../../../utilities/args.js' import BaseCommand from '../../../utilities/base-command.js' import Flags from '../../../utilities/flags.js' +import {isThemeRepo} from '../../../utilities/validate.js' import GenerateImportMap from '../generate/import-map.js' import Clean from './clean.js' import Copy from './copy.js' @@ -42,6 +43,9 @@ export default class Install extends BaseCommand { await Manifest.run([this.args[Args.DEST_DIR]!]) await Copy.run([this.args[Args.DEST_DIR]!]) await Clean.run([this.args[Args.DEST_DIR]!]) - await GenerateImportMap.run([this.args[Args.DEST_DIR]!, '--quiet']) + + if (isThemeRepo(this.args[Args.DEST_DIR])) { + await GenerateImportMap.run([this.args[Args.DEST_DIR]!, '--quiet']) + } } } diff --git a/test/commands/theme/component/install.test.ts b/test/commands/theme/component/install.test.ts index c50032e..e8a02d2 100644 --- a/test/commands/theme/component/install.test.ts +++ b/test/commands/theme/component/install.test.ts @@ -15,6 +15,7 @@ const fixturesPath = path.join(__dirname, '../../../fixtures') const collectionPath = path.join(__dirname, '../../../fixtures/collection') const themePath = path.join(__dirname, '../../../fixtures/theme') const testCollectionPath = path.join(fixturesPath, 'test-collection') +const testCollectionBPath = path.join(fixturesPath, 'test-collection-b') const testThemePath = path.join(fixturesPath, 'test-theme') describe('theme component install', () => { @@ -32,6 +33,7 @@ describe('theme component install', () => { generateRunStub = sandbox.stub(GenerateImportMap.prototype, 'run').resolves() fs.cpSync(collectionPath, testCollectionPath, { recursive: true }) + fs.cpSync(collectionPath, testCollectionBPath, { recursive: true }) fs.cpSync(themePath, testThemePath, { recursive: true }) process.chdir(testCollectionPath) }) @@ -39,6 +41,7 @@ describe('theme component install', () => { afterEach(() => { sandbox.restore() fs.rmSync(testCollectionPath, { force: true, recursive: true }) + fs.rmSync(testCollectionBPath, { force: true, recursive: true }) fs.rmSync(testThemePath, { force: true, recursive: true }) }) @@ -57,11 +60,16 @@ describe('theme component install', () => { expect(cleanRunStub.calledOnce).to.be.true }) - it('runs the theme component generate import map command', async () => { + it('runs the theme component generate import map command if the destination is a theme repo', async () => { await Install.run([testThemePath]) expect(generateRunStub.calledOnce).to.be.true }) + it('does not run the theme component generate import map command if the destination is not a theme repo', async () => { + await Install.run([testCollectionBPath]) + expect(generateRunStub.calledOnce).to.be.false + }) + it('runs sub-commands in correct order', async () => { await Install.run([testThemePath]) sinon.assert.callOrder(manifestRunStub, copyRunStub, cleanRunStub, generateRunStub) From f5929353460d38a1c482ad0ffc9c3af38eaabcac Mon Sep 17 00:00:00 2001 From: Thomas Kelly Date: Mon, 12 May 2025 14:21:42 -0400 Subject: [PATCH 5/8] Check for duplicate files and throw an error --- src/commands/theme/component/copy.ts | 17 +++++++++++++- src/commands/theme/component/manifest.ts | 15 ++++++++++++- src/utilities/nodes.ts | 20 +++++++++++++++++ .../commands/theme/component/manifest.test.ts | 11 ++++++++++ .../components/duplicate/duplicate.liquid | 0 .../test-collection-b/component.manifest.json | 22 +++++++++++++++++++ .../belongs-only-to-this-collection.css | 0 .../belongs-only-to-this-collection.liquid | 0 ...-other-collection-already-installed.liquid | 0 ...ses-component-from-other-collection.liquid | 2 ++ test/fixtures/test-collection-b/package.json | 4 ++++ test/fixtures/theme/sections/duplicate.liquid | 1 + 12 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/collection/components/duplicate/duplicate.liquid create mode 100644 test/fixtures/test-collection-b/component.manifest.json create mode 100644 test/fixtures/test-collection-b/components/belongs-only-to-this-collection/assets/belongs-only-to-this-collection.css create mode 100644 test/fixtures/test-collection-b/components/belongs-only-to-this-collection/belongs-only-to-this-collection.liquid create mode 100644 test/fixtures/test-collection-b/components/used-by-other-collection-already-installed/used-by-other-collection-already-installed.liquid create mode 100644 test/fixtures/test-collection-b/components/uses-component-from-other-collection/uses-component-from-other-collection.liquid create mode 100644 test/fixtures/test-collection-b/package.json create mode 100644 test/fixtures/theme/sections/duplicate.liquid diff --git a/src/commands/theme/component/copy.ts b/src/commands/theme/component/copy.ts index 652f45a..5cdf466 100644 --- a/src/commands/theme/component/copy.ts +++ b/src/commands/theme/component/copy.ts @@ -13,7 +13,7 @@ import BaseCommand from '../../../utilities/base-command.js' import { copyFileIfChanged } from '../../../utilities/files.js'; import Flags from '../../../utilities/flags.js' import { getManifest } from '../../../utilities/manifest.js' -import { getCollectionNodes } from '../../../utilities/nodes.js' +import { getCollectionNodes, getDuplicateFiles } from '../../../utilities/nodes.js' import { getNameFromPackageJson, getVersionFromPackageJson } from '../../../utilities/package-json.js' import { isComponentRepo, isThemeRepo } from '../../../utilities/validate.js' @@ -54,6 +54,21 @@ export default class Copy extends BaseCommand { const manifest = getManifest(path.join(destinationDir, 'component.manifest.json')) const sourceNodes = await getCollectionNodes(currentDir) + const manifest = getManifest(path.join(themeDir, 'component.manifest.json')) + const componentNodes = await getCollectionNodes(currentDir) + + const duplicates = getDuplicateFiles(componentNodes); + + if (duplicates.size > 0) { + const message: string[] = [] + duplicates.forEach((nodes, key) => { + message.push(`Warning: Found duplicate files for ${key}:`) + nodes.forEach(node => { + message.push(` - ${node.file}`) + }) + }); + this.error(message.join('\n')) + } if (manifest.collections[sourceName].version !== sourceVersion) { this.error(`Version mismatch: Expected ${sourceVersion} but found ${manifest.collections[sourceName].version}. Run "shopify theme component map" to update the component.manifest.json file.`); diff --git a/src/commands/theme/component/manifest.ts b/src/commands/theme/component/manifest.ts index 653325b..255381e 100644 --- a/src/commands/theme/component/manifest.ts +++ b/src/commands/theme/component/manifest.ts @@ -13,7 +13,7 @@ import BaseCommand from '../../../utilities/base-command.js' import Flags from '../../../utilities/flags.js' import { getLastCommitHash } from '../../../utilities/git.js' import { ManifestOptions, generateManifestFile, getManifest } from '../../../utilities/manifest.js' -import { getCollectionNodes, getThemeNodes } from '../../../utilities/nodes.js' +import { getCollectionNodes, getDuplicateFiles, getThemeNodes } from '../../../utilities/nodes.js' import { sortObjectKeys } from '../../../utilities/objects.js' import { getNameFromPackageJson, getVersionFromPackageJson } from '../../../utilities/package-json.js' import { LiquidNode } from '../../../utilities/types.js' @@ -69,6 +69,19 @@ export default class Manifest extends BaseCommand { const sourceNodes = await getCollectionNodes(sourceDir) + const duplicates = getDuplicateFiles(sourceNodes); + + if (duplicates.size > 0) { + const message: string[] = [] + duplicates.forEach((nodes, key) => { + message.push(`Warning: Found duplicate files for ${key}:`) + nodes.forEach(node => { + message.push(` - ${node.file}`) + }) + }); + this.error(message.join('\n')) + } + let destinationNodes: LiquidNode[] let destinationName: string diff --git a/src/utilities/nodes.ts b/src/utilities/nodes.ts index f0cf0f1..bb73cea 100644 --- a/src/utilities/nodes.ts +++ b/src/utilities/nodes.ts @@ -116,6 +116,26 @@ export async function getCollectionNodes(collectionDir: string): Promise { + const duplicateMap = new Map(); + + nodes.forEach(node => { + if (node.themeFolder !== 'snippets' && node.themeFolder !== 'assets') return; + const key = `${node.themeFolder}/${node.name}`; + if (!duplicateMap.has(key)) { + duplicateMap.set(key, [node]); + } else { + duplicateMap.get(key)!.push(node); + } + }); + + // Filter to only return entries with duplicates + return new Map( + Array.from(duplicateMap.entries()) + .filter(([_, nodes]) => nodes.length > 1) + ); +} + export async function getThemeNodes(themeDir: string): Promise { const entryNodes = globSync(path.join(themeDir, '{layout,sections,blocks,templates}', '*.liquid'), { absolute: true }) .map(file => { diff --git a/test/commands/theme/component/manifest.test.ts b/test/commands/theme/component/manifest.test.ts index 96f18cb..9f58001 100644 --- a/test/commands/theme/component/manifest.test.ts +++ b/test/commands/theme/component/manifest.test.ts @@ -357,4 +357,15 @@ describe('theme component manifest', () => { const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(data.collections['@archetype-themes/test-collection'].commit).to.equal(expectedHash) }) + + it('should throw an error if there are duplicate files in the source collection', async () => { + const src = path.join(testCollectionPath, 'components', 'duplicate', 'duplicate.liquid') + const dest = path.join(testCollectionPath, 'components', 'duplicate', 'snippets', 'duplicate.liquid') + + fs.cpSync(src, dest, {recursive: true}) + + const {error} = await runCommand(['theme', 'component', 'manifest', testThemePath]) + expect(error).to.be.instanceOf(Error) + expect(error?.message).to.include('Error: Namespace conflict in source component collection with') + }) }) diff --git a/test/fixtures/collection/components/duplicate/duplicate.liquid b/test/fixtures/collection/components/duplicate/duplicate.liquid new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/test-collection-b/component.manifest.json b/test/fixtures/test-collection-b/component.manifest.json new file mode 100644 index 0000000..3e75eb9 --- /dev/null +++ b/test/fixtures/test-collection-b/component.manifest.json @@ -0,0 +1,22 @@ +{ + "collections": { + "@archetype-themes/test-collection": { + "version": "1.0.0" + } + }, + "files": { + "snippets": { + "belongs-only-to-this-collection.liquid": "@collection", + "uses-component-from-other-collection.liquid": "@collection", + "used-by-other-collection-already-installed.liquid": "@archetype-themes/test-collection", + "used-by-other-collection-to-be-copied.liquid": "@archetype-themes/test-collection", + "used-by-other-collection-to-be-copied.snippet.liquid": "@archetype-themes/test-collection" + }, + "assets": { + "belongs-only-to-this-collection.css": "@collection", + "used-by-other-collection-already-installed.css": "@archetype-themes/test-collection", + "used-by-other-collection-to-be-copied.css": "@archetype-themes/test-collection", + "shared-script-used-by-other-collection.js": "@archetype-themes/test-collection" + } + } +} \ No newline at end of file diff --git a/test/fixtures/test-collection-b/components/belongs-only-to-this-collection/assets/belongs-only-to-this-collection.css b/test/fixtures/test-collection-b/components/belongs-only-to-this-collection/assets/belongs-only-to-this-collection.css new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/test-collection-b/components/belongs-only-to-this-collection/belongs-only-to-this-collection.liquid b/test/fixtures/test-collection-b/components/belongs-only-to-this-collection/belongs-only-to-this-collection.liquid new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/test-collection-b/components/used-by-other-collection-already-installed/used-by-other-collection-already-installed.liquid b/test/fixtures/test-collection-b/components/used-by-other-collection-already-installed/used-by-other-collection-already-installed.liquid new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/test-collection-b/components/uses-component-from-other-collection/uses-component-from-other-collection.liquid b/test/fixtures/test-collection-b/components/uses-component-from-other-collection/uses-component-from-other-collection.liquid new file mode 100644 index 0000000..a6ac2df --- /dev/null +++ b/test/fixtures/test-collection-b/components/uses-component-from-other-collection/uses-component-from-other-collection.liquid @@ -0,0 +1,2 @@ +{% render 'used-by-other-collection-already-installed' %} +{% render 'used-by-other-collection-not-installed' %} \ No newline at end of file diff --git a/test/fixtures/test-collection-b/package.json b/test/fixtures/test-collection-b/package.json new file mode 100644 index 0000000..337da59 --- /dev/null +++ b/test/fixtures/test-collection-b/package.json @@ -0,0 +1,4 @@ +{ + "name": "@archetype-themes/test-collection-b", + "version": "1.0.1" +} \ No newline at end of file diff --git a/test/fixtures/theme/sections/duplicate.liquid b/test/fixtures/theme/sections/duplicate.liquid new file mode 100644 index 0000000..c49e742 --- /dev/null +++ b/test/fixtures/theme/sections/duplicate.liquid @@ -0,0 +1 @@ +{% render 'duplicate' %} \ No newline at end of file From f7641f12a8fb8a14169e1d9f908839d87b65a4fb Mon Sep 17 00:00:00 2001 From: Chris Berthe Date: Mon, 2 Jun 2025 14:28:30 -0400 Subject: [PATCH 6/8] Fix tests and linting errors --- src/commands/theme/component/copy.ts | 23 +++--- src/commands/theme/component/manifest.ts | 13 +-- src/utilities/nodes.ts | 25 +++--- .../commands/theme/component/manifest.test.ts | 80 +++++++++---------- 4 files changed, 71 insertions(+), 70 deletions(-) diff --git a/src/commands/theme/component/copy.ts b/src/commands/theme/component/copy.ts index 5cdf466..4920b1c 100644 --- a/src/commands/theme/component/copy.ts +++ b/src/commands/theme/component/copy.ts @@ -53,20 +53,19 @@ export default class Copy extends BaseCommand { } const manifest = getManifest(path.join(destinationDir, 'component.manifest.json')) - const sourceNodes = await getCollectionNodes(currentDir) - const manifest = getManifest(path.join(themeDir, 'component.manifest.json')) const componentNodes = await getCollectionNodes(currentDir) const duplicates = getDuplicateFiles(componentNodes); if (duplicates.size > 0) { const message: string[] = [] - duplicates.forEach((nodes, key) => { + for (const [key, nodes] of duplicates) { message.push(`Warning: Found duplicate files for ${key}:`) - nodes.forEach(node => { + for (const node of nodes) { message.push(` - ${node.file}`) - }) - }); + } + } + this.error(message.join('\n')) } @@ -78,12 +77,12 @@ export default class Copy extends BaseCommand { for (const [fileName, fileCollection] of Object.entries(manifest.files[fileType])) { if (fileCollection !== sourceName) continue; - const node = sourceNodes.find(node => node.name === fileName && node.themeFolder === fileType); + const node = componentNodes.find(node => node.name === fileName && node.themeFolder === fileType); if (!node) continue; if (isThemeRepo(destinationDir)) { - copyFileIfChanged(node.file, path.join(destinationDir, fileType, fileName)); + copyFileIfChanged(node.file, path.join(destinationDir, fileType, fileName)); } else if (isComponentRepo(destinationDir)) { const dest = node.file.replace(currentDir, destinationDir) copyFileIfChanged(node.file, dest); @@ -94,18 +93,18 @@ export default class Copy extends BaseCommand { const setupDestDir = path.join(path.dirname(dest), 'setup'); const testSrcDir = path.join(path.dirname(node.file), 'test'); const testDestDir = path.join(path.dirname(dest), 'test'); - + if (fs.existsSync(setupSrcDir)) { fs.mkdirSync(setupDestDir, { recursive: true }); fs.cpSync(setupSrcDir, setupDestDir, { recursive: true }); } - + if (fs.existsSync(testSrcDir)) { - fs.mkdirSync(testDestDir, { recursive: true }); + fs.mkdirSync(testDestDir, { recursive: true }); fs.cpSync(testSrcDir, testDestDir, { recursive: true }); } } - } + } } }; diff --git a/src/commands/theme/component/manifest.ts b/src/commands/theme/component/manifest.ts index 255381e..4dc3b30 100644 --- a/src/commands/theme/component/manifest.ts +++ b/src/commands/theme/component/manifest.ts @@ -57,7 +57,7 @@ export default class Manifest extends BaseCommand { const ignoreConflicts = this.flags[Flags.IGNORE_CONFLICTS] const ignoreOverrides = this.flags[Flags.IGNORE_OVERRIDES] const componentSelector = this.args[Args.COMPONENT_SELECTOR] - + const manifestPath = path.join(destinationDir, 'component.manifest.json') const manifest = getManifest(manifestPath); @@ -73,18 +73,19 @@ export default class Manifest extends BaseCommand { if (duplicates.size > 0) { const message: string[] = [] - duplicates.forEach((nodes, key) => { + for (const [key, nodes] of duplicates) { message.push(`Warning: Found duplicate files for ${key}:`) - nodes.forEach(node => { + for (const node of nodes) { message.push(` - ${node.file}`) - }) - }); + } + } + this.error(message.join('\n')) } let destinationNodes: LiquidNode[] let destinationName: string - + if (isThemeRepo(destinationDir)) { destinationNodes = await getThemeNodes(destinationDir) destinationName = '@theme' diff --git a/src/utilities/nodes.ts b/src/utilities/nodes.ts index bb73cea..56e020a 100644 --- a/src/utilities/nodes.ts +++ b/src/utilities/nodes.ts @@ -65,7 +65,7 @@ export async function generateLiquidNode(file: string, type: LiquidNode['type'], assets = await getJsAssets(body) } - if (type === 'snippet') { + if (type === 'snippet') { body = fs.readFileSync(file, 'utf8') assets = getJsImportsFromLiquid(body) } @@ -118,27 +118,28 @@ export async function getCollectionNodes(collectionDir: string): Promise { const duplicateMap = new Map(); - - nodes.forEach(node => { - if (node.themeFolder !== 'snippets' && node.themeFolder !== 'assets') return; - const key = `${node.themeFolder}/${node.name}`; - if (!duplicateMap.has(key)) { - duplicateMap.set(key, [node]); - } else { - duplicateMap.get(key)!.push(node); + + for (const node of nodes) { + if (node.themeFolder === 'snippets' || node.themeFolder === 'assets') { + const key = `${node.themeFolder}/${node.name}`; + if (duplicateMap.has(key)) { + duplicateMap.get(key)!.push(node); + } else { + duplicateMap.set(key, [node]); + } } - }); + } // Filter to only return entries with duplicates return new Map( - Array.from(duplicateMap.entries()) + [...duplicateMap.entries()] .filter(([_, nodes]) => nodes.length > 1) ); } export async function getThemeNodes(themeDir: string): Promise { const entryNodes = globSync(path.join(themeDir, '{layout,sections,blocks,templates}', '*.liquid'), { absolute: true }) - .map(file => { + .map(file => { const parentFolderName = path.basename(path.dirname(file)) as LiquidNode['themeFolder'] return generateLiquidNode(file, 'entry', parentFolderName) }) diff --git a/test/commands/theme/component/manifest.test.ts b/test/commands/theme/component/manifest.test.ts index 9f58001..cd805af 100644 --- a/test/commands/theme/component/manifest.test.ts +++ b/test/commands/theme/component/manifest.test.ts @@ -1,9 +1,9 @@ -import {runCommand} from '@oclif/test' -import {expect} from 'chai' -import {execSync} from 'node:child_process' +import { runCommand } from '@oclif/test' +import { expect } from 'chai' +import { execSync } from 'node:child_process' import * as fs from 'node:fs' import * as path from 'node:path' -import {fileURLToPath} from 'node:url' +import { fileURLToPath } from 'node:url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const fixturesPath = path.join(__dirname, '../../../fixtures') @@ -15,34 +15,34 @@ const testCollectionBPath = path.join(fixturesPath, 'test-collection-b') const testThemePath = path.join(fixturesPath, 'test-theme') describe('theme component manifest', () => { beforeEach(() => { - fs.cpSync(collectionPath, testCollectionPath, {recursive: true}) - fs.cpSync(collectionBPath, testCollectionBPath, {recursive: true}) - fs.cpSync(themePath, testThemePath, {recursive: true}) + fs.cpSync(collectionPath, testCollectionPath, { recursive: true }) + fs.cpSync(collectionBPath, testCollectionBPath, { recursive: true }) + fs.cpSync(themePath, testThemePath, { recursive: true }) process.chdir(testCollectionPath) }) afterEach(() => { - fs.rmSync(testCollectionPath, {force: true, recursive: true}) - fs.rmSync(testCollectionBPath, {force: true, recursive: true}) - fs.rmSync(testThemePath, {force: true, recursive: true}) + fs.rmSync(testCollectionPath, { force: true, recursive: true }) + fs.rmSync(testCollectionBPath, { force: true, recursive: true }) + fs.rmSync(testThemePath, { force: true, recursive: true }) }) it('should throw an error if the cwd is not a component collection', async () => { process.chdir(testThemePath) - const {error} = await runCommand(['theme', 'component', 'manifest', testThemePath]) + const { error } = await runCommand(['theme', 'component', 'manifest', testThemePath]) expect(error).to.be.instanceOf(Error) expect(error?.message).to.include('Warning: ') }) it('should throw an error if a theme directory is not provided', async () => { - const {error} = await runCommand(['theme', 'component', 'manifest']) + const { error } = await runCommand(['theme', 'component', 'manifest']) expect(error).to.be.instanceOf(Error) expect(error?.message).to.include('Missing 1 required arg:') }) it('creates a component.manifest.json in current theme directory if it does not exist', async () => { // Confirm that the file does not exist - fs.rmSync(path.join(testThemePath, 'component.manifest.json'), {force: true}) + fs.rmSync(path.join(testThemePath, 'component.manifest.json'), { force: true }) expect(fs.existsSync(path.join(testThemePath, 'component.manifest.json'))).to.be.false await runCommand(['theme', 'component', 'manifest', testThemePath]) @@ -114,28 +114,28 @@ describe('theme component manifest', () => { }) it('throws a warning if there is a potential conflict with an entry in the current collection', async () => { - const {stdout} = await runCommand(['theme', 'component', 'manifest', testThemePath]) + const { stdout } = await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(stdout).to.include('Conflict Warning: Pre-existing file') expect(data.files.snippets['conflict.liquid']).to.equal('@theme') }) it('ignores conflicts if --ignore-conflicts flag is passed', async () => { - const {stdout} = await runCommand(['theme', 'component', 'manifest', testThemePath, '--ignore-conflicts']) + const { stdout } = await runCommand(['theme', 'component', 'manifest', testThemePath, '--ignore-conflicts']) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(stdout).to.not.include('Conflict Warning: Pre-existing file') expect(data.files.snippets['conflict.liquid']).to.equal('@archetype-themes/test-collection') }) it('throws a warning when an override is detected', async () => { - const {stdout} = await runCommand(['theme', 'component', 'manifest', testThemePath]) + const { stdout } = await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(stdout).to.include('Override Warning:') expect(data.files.snippets['override.liquid']).to.equal('@theme') }) it('overriden parent still references non-overridden child from collection', async () => { - const {stdout} = await runCommand(['theme', 'component', 'manifest', testThemePath]) + const { stdout } = await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(stdout).to.include('Override Warning:') expect(data.files.snippets['override-parent.liquid']).to.equal('@theme') @@ -144,7 +144,7 @@ describe('theme component manifest', () => { }) it('ignores overrides if --ignore-overrides flag is passed', async () => { - const {stdout} = await runCommand(['theme', 'component', 'manifest', testThemePath, '--ignore-overrides']) + const { stdout } = await runCommand(['theme', 'component', 'manifest', testThemePath, '--ignore-overrides']) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) expect(stdout).to.not.include('Override Warning:') expect(data.files.snippets['override.liquid']).to.equal('@archetype-themes/test-collection') @@ -176,7 +176,7 @@ describe('theme component manifest', () => { it('persists entries from other collections or @theme if those files still exist', async () => { const beforeData = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) - + // Check that referenced files are present in map expect(beforeData.files.snippets['theme-component.liquid']).to.equal('@theme') expect(beforeData.files.assets['theme-component.css']).to.equal('@theme') @@ -230,7 +230,7 @@ describe('theme component manifest', () => { await runCommand(['theme', 'component', 'manifest', testThemePath, 'new']) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) - + // Should include the selected component and its assets expect(data.files.snippets['new.liquid']).to.equal('@archetype-themes/test-collection') expect(data.files.assets['new.css']).to.equal('@archetype-themes/test-collection') @@ -244,7 +244,7 @@ describe('theme component manifest', () => { await runCommand(['theme', 'component', 'manifest', testThemePath, 'new,parent']) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) - + // Should include both selected components and their dependencies expect(data.files.snippets['new.liquid']).to.equal('@archetype-themes/test-collection') expect(data.files.assets['new.css']).to.equal('@archetype-themes/test-collection') @@ -257,37 +257,37 @@ describe('theme component manifest', () => { await runCommand(['theme', 'component', 'manifest', testThemePath, 'child']) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) - + // Should not include the snippet since it's not a component expect(data.files.snippets['child.liquid']).to.be.undefined }) it('should include all components when using "*" as component selector', async () => { const beforeData = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) - + await runCommand(['theme', 'component', 'manifest', testThemePath, '*']) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) - + // Should include all components from the collection expect(data.files.snippets['new.liquid']).to.equal('@archetype-themes/test-collection') expect(data.files.assets['new.css']).to.equal('@archetype-themes/test-collection') expect(data.files.snippets['parent.liquid']).to.equal('@archetype-themes/test-collection') expect(data.files.snippets['child.liquid']).to.equal('@archetype-themes/test-collection') - + // Should still maintain other collection entries for (const [key, value] of Object.entries(beforeData.files.snippets) .filter(([_, value]) => value !== '@archetype-themes/test-collection')) { - if (fs.existsSync(path.join(testThemePath, 'snippets', key))) { - expect(data.files.snippets[key]).to.equal(value) - } + if (fs.existsSync(path.join(testThemePath, 'snippets', key))) { + expect(data.files.snippets[key]).to.equal(value) } + } }) it('should throw an error when no components match the selector', async () => { const beforeData = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) - - const {error} = await runCommand(['theme', 'component', 'manifest', testThemePath, 'non-existent']) + + const { error } = await runCommand(['theme', 'component', 'manifest', testThemePath, 'non-existent']) // Should throw an error expect(error).to.be.instanceOf(Error) @@ -312,21 +312,21 @@ describe('theme component manifest', () => { it('should detect JS imports from script tags with {{ "filename" | asset_url }} filter', async () => { await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) - + expect(data.files.assets['script-with-filter.js']).to.equal('@archetype-themes/test-collection') }) it('should detect JS imports snippets inside components', async () => { await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) - + expect(data.files.assets['script-snippet-import.js']).to.equal('@archetype-themes/test-collection') }) it('should detect JS imports from script tags with import statements', async () => { await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) - + expect(data.files.assets['script-with-import.js']).to.equal('@archetype-themes/test-collection') expect(data.files.assets['shared-min-other.js']).to.equal('@archetype-themes/test-collection') }) @@ -334,7 +334,7 @@ describe('theme component manifest', () => { it('should not include commented out script imports', async () => { await runCommand(['theme', 'component', 'manifest', testThemePath]) const data = JSON.parse(fs.readFileSync(path.join(testThemePath, 'component.manifest.json'), 'utf8')) - + expect(data.files.assets['commented-script.js']).to.be.undefined }) @@ -345,9 +345,9 @@ describe('theme component manifest', () => { execSync('git config user.name "Test User"', { cwd: testCollectionPath }) execSync('git add .', { cwd: testCollectionPath }) execSync('git commit -m "Initial commit"', { cwd: testCollectionPath }) - + // Get the commit hash we just created - const expectedHash = execSync('git rev-parse HEAD', { + const expectedHash = execSync('git rev-parse HEAD', { cwd: testCollectionPath, encoding: 'utf8' }).trim() @@ -361,11 +361,11 @@ describe('theme component manifest', () => { it('should throw an error if there are duplicate files in the source collection', async () => { const src = path.join(testCollectionPath, 'components', 'duplicate', 'duplicate.liquid') const dest = path.join(testCollectionPath, 'components', 'duplicate', 'snippets', 'duplicate.liquid') - - fs.cpSync(src, dest, {recursive: true}) - const {error} = await runCommand(['theme', 'component', 'manifest', testThemePath]) + fs.cpSync(src, dest, { recursive: true }) + + const { error } = await runCommand(['theme', 'component', 'manifest', testThemePath]) expect(error).to.be.instanceOf(Error) - expect(error?.message).to.include('Error: Namespace conflict in source component collection with') + expect(error?.message).to.include('Warning: Found duplicate files for') }) }) From b25ae3b2deb029820e74eaf60c077c670bd151f5 Mon Sep 17 00:00:00 2001 From: Chris Berthe Date: Mon, 2 Jun 2025 14:47:24 -0400 Subject: [PATCH 7/8] Remove test/fixtures/test-collection-b --- .../test-collection-b/component.manifest.json | 22 ------------------- .../belongs-only-to-this-collection.css | 0 .../belongs-only-to-this-collection.liquid | 0 ...-other-collection-already-installed.liquid | 0 ...ses-component-from-other-collection.liquid | 2 -- test/fixtures/test-collection-b/package.json | 4 ---- 6 files changed, 28 deletions(-) delete mode 100644 test/fixtures/test-collection-b/component.manifest.json delete mode 100644 test/fixtures/test-collection-b/components/belongs-only-to-this-collection/assets/belongs-only-to-this-collection.css delete mode 100644 test/fixtures/test-collection-b/components/belongs-only-to-this-collection/belongs-only-to-this-collection.liquid delete mode 100644 test/fixtures/test-collection-b/components/used-by-other-collection-already-installed/used-by-other-collection-already-installed.liquid delete mode 100644 test/fixtures/test-collection-b/components/uses-component-from-other-collection/uses-component-from-other-collection.liquid delete mode 100644 test/fixtures/test-collection-b/package.json diff --git a/test/fixtures/test-collection-b/component.manifest.json b/test/fixtures/test-collection-b/component.manifest.json deleted file mode 100644 index 3e75eb9..0000000 --- a/test/fixtures/test-collection-b/component.manifest.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "collections": { - "@archetype-themes/test-collection": { - "version": "1.0.0" - } - }, - "files": { - "snippets": { - "belongs-only-to-this-collection.liquid": "@collection", - "uses-component-from-other-collection.liquid": "@collection", - "used-by-other-collection-already-installed.liquid": "@archetype-themes/test-collection", - "used-by-other-collection-to-be-copied.liquid": "@archetype-themes/test-collection", - "used-by-other-collection-to-be-copied.snippet.liquid": "@archetype-themes/test-collection" - }, - "assets": { - "belongs-only-to-this-collection.css": "@collection", - "used-by-other-collection-already-installed.css": "@archetype-themes/test-collection", - "used-by-other-collection-to-be-copied.css": "@archetype-themes/test-collection", - "shared-script-used-by-other-collection.js": "@archetype-themes/test-collection" - } - } -} \ No newline at end of file diff --git a/test/fixtures/test-collection-b/components/belongs-only-to-this-collection/assets/belongs-only-to-this-collection.css b/test/fixtures/test-collection-b/components/belongs-only-to-this-collection/assets/belongs-only-to-this-collection.css deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/test-collection-b/components/belongs-only-to-this-collection/belongs-only-to-this-collection.liquid b/test/fixtures/test-collection-b/components/belongs-only-to-this-collection/belongs-only-to-this-collection.liquid deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/test-collection-b/components/used-by-other-collection-already-installed/used-by-other-collection-already-installed.liquid b/test/fixtures/test-collection-b/components/used-by-other-collection-already-installed/used-by-other-collection-already-installed.liquid deleted file mode 100644 index e69de29..0000000 diff --git a/test/fixtures/test-collection-b/components/uses-component-from-other-collection/uses-component-from-other-collection.liquid b/test/fixtures/test-collection-b/components/uses-component-from-other-collection/uses-component-from-other-collection.liquid deleted file mode 100644 index a6ac2df..0000000 --- a/test/fixtures/test-collection-b/components/uses-component-from-other-collection/uses-component-from-other-collection.liquid +++ /dev/null @@ -1,2 +0,0 @@ -{% render 'used-by-other-collection-already-installed' %} -{% render 'used-by-other-collection-not-installed' %} \ No newline at end of file diff --git a/test/fixtures/test-collection-b/package.json b/test/fixtures/test-collection-b/package.json deleted file mode 100644 index 337da59..0000000 --- a/test/fixtures/test-collection-b/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "@archetype-themes/test-collection-b", - "version": "1.0.1" -} \ No newline at end of file From 023b11b42212402559d8be69454d172a54b02536 Mon Sep 17 00:00:00 2001 From: Chris Berthe Date: Mon, 2 Jun 2025 14:47:40 -0400 Subject: [PATCH 8/8] Add test-collection-b to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5497eeb..3de77e1 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,6 @@ pnpm-lock.yaml # Test run-time fixtures test-collection +test-collection-b test-theme .shopify