Skip to content

Commit de43c0d

Browse files
committed
Add LLM instructions file suggestion
1 parent 026da63 commit de43c0d

File tree

2 files changed

+120
-1
lines changed

2 files changed

+120
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
You are a very experienced Shopify theme developer. You are tasked with writing high-quality Liquid code and JSON files.
2+
3+
Remember the following important mindset when providing code, in the following order:
4+
- Adherance to conventions and patterns in the rest of the codebase
5+
- Simplicity
6+
- Readability
7+
8+
The theme folder structure is as follows:
9+
/assets
10+
/config
11+
/layout
12+
/locales
13+
/sections
14+
/snippets
15+
/templates
16+
/templates/customers
17+
/templates/metaobject
18+
Files can also be placed in the root directory. Subdirectories, other than the ones listed, aren't supported.
19+
20+
Liquid filters are used to modify Liquid output and are documented at https://shopify.dev/docs/api/liquid/filters.txt.
21+
22+
Liquid tags are used to define logic that tells templates what to do and are documented at https://shopify.dev/api/liquid/tags.txt.
23+
Liquid objects represent variables that you can use to build your theme and are documented at https://shopify.dev/api/liquid/objects.txt.
24+
25+
Some best practices from Shopify on theme development (more available at https://shopify.dev/docs/themes/best-practices.txt):
26+
* With the large majority of online store traffic happening on mobile, designing for mobile devices must be at the forefront throughout the theme build process.
27+
* To provide the best experience to a wide range of merchants and customers, themes must be built from the ground up with accessibility best practices in mind.
28+
* Themes should minimize the use of JavaScript and rely on modern and native web browser features for most functionality.
29+
* Use responsive images by using the `image_tag` filter. This filter returns a `srcset` for the image using a smart default set of widths. An example is `{{ product.featured_image | image_url: width: 2000 | image_tag }}`.
30+
31+

packages/vscode-extension/src/node/extension.ts

+89-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { FileStat, FileTuple, path as pathUtils } from '@shopify/theme-check-common';
22
import * as path from 'node:path';
3-
import { commands, ExtensionContext, languages, Uri, workspace } from 'vscode';
3+
import { commands, ExtensionContext, languages, Uri, workspace, window } from 'vscode';
44
import {
55
DocumentSelector,
66
LanguageClient,
@@ -16,9 +16,97 @@ const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms));
1616

1717
let client: LanguageClient | undefined;
1818

19+
async function isShopifyTheme(workspaceRoot: string): Promise<boolean> {
20+
try {
21+
// Check for typical Shopify theme folders
22+
const requiredFolders = ['sections', 'templates', 'assets', 'config'];
23+
for (const folder of requiredFolders) {
24+
const folderUri = Uri.file(path.join(workspaceRoot, folder));
25+
try {
26+
await workspace.fs.stat(folderUri);
27+
} catch {
28+
return false;
29+
}
30+
}
31+
return true;
32+
} catch {
33+
return false;
34+
}
35+
}
36+
37+
function isCursor(): boolean {
38+
// Check if we're running in Cursor's electron process
39+
const processTitle = process.title.toLowerCase();
40+
const isElectronCursor =
41+
processTitle.includes('cursor') && process.versions.electron !== undefined;
42+
43+
// Check for Cursor-specific environment variables that are set by Cursor itself
44+
const hasCursorEnv =
45+
process.env.CURSOR_CHANNEL !== undefined || process.env.CURSOR_VERSION !== undefined;
46+
47+
return isElectronCursor || hasCursorEnv;
48+
}
49+
50+
interface ConfigFile {
51+
path: string;
52+
templateName: string;
53+
prompt: string;
54+
}
55+
56+
async function getConfigFileDetails(workspaceRoot: string): Promise<ConfigFile> {
57+
if (isCursor()) {
58+
return {
59+
path: path.join(workspaceRoot, '.cursorrules'),
60+
templateName: 'llm_instructions.template',
61+
prompt:
62+
'Detected Shopify theme project in Cursor. Do you want a .cursorrules file to be created?',
63+
};
64+
}
65+
return {
66+
path: path.join(workspaceRoot, '.github', 'copilot-instructions.md'),
67+
templateName: 'llm_instructions.template',
68+
prompt:
69+
'Detected Shopify theme project in VSCode. Do you want a Copilot instructions file to be created?',
70+
};
71+
}
72+
1973
export async function activate(context: ExtensionContext) {
2074
const runChecksCommand = 'themeCheck/runChecks';
2175

76+
if (workspace.workspaceFolders?.length) {
77+
const workspaceRoot = workspace.workspaceFolders[0].uri.fsPath;
78+
const instructionsConfig = await getConfigFileDetails(workspaceRoot);
79+
80+
// Don't do anything if the file already exists
81+
try {
82+
await workspace.fs.stat(Uri.file(instructionsConfig.path));
83+
return;
84+
} catch {
85+
// File doesn't exist, continue
86+
}
87+
88+
if (await isShopifyTheme(workspaceRoot)) {
89+
const response = await window.showInformationMessage(instructionsConfig.prompt, 'Yes', 'No');
90+
91+
if (response === 'Yes') {
92+
// Create directory if it doesn't exist (needed for .github case)
93+
const dir = path.dirname(instructionsConfig.path);
94+
try {
95+
await workspace.fs.createDirectory(Uri.file(dir));
96+
} catch {
97+
// Directory might already exist, continue
98+
}
99+
100+
// Read the template file from the extension's resources
101+
const templateContent = await workspace.fs.readFile(
102+
Uri.file(context.asAbsolutePath(`resources/${instructionsConfig.templateName}`)),
103+
);
104+
await workspace.fs.writeFile(Uri.file(instructionsConfig.path), templateContent);
105+
console.log(`Wrote instructions file to ${instructionsConfig.path}`);
106+
}
107+
}
108+
}
109+
22110
context.subscriptions.push(
23111
commands.registerCommand('shopifyLiquid.restart', () => restartServer(context)),
24112
);

0 commit comments

Comments
 (0)