@akqa-denmark/shopify-theme-build 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +258 -0
- package/bin/shopify-build.js +5 -0
- package/dist/chunk-IHCJ6PUT.js +81 -0
- package/dist/chunk-JXQLZXJ2.js +768 -0
- package/dist/cli.js +26 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.js +18 -0
- package/dist/manifest-L2MJQDVK.js +20 -0
- package/dist/prepare-APTEPBMX.js +701 -0
- package/dist/vite.d.ts +5 -0
- package/dist/vite.js +116 -0
- package/package.json +73 -0
package/README.md
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# @akqa-denmark/shopify-theme-build
|
|
2
|
+
|
|
3
|
+
Internal build pipeline for AKQA Denmark Shopify themes. Handles schema generation, locale merging, section group scaffolding, and Vite config for multi-store repositories.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
Shopify themes require a specific set of JSON and Liquid files to be generated from TypeScript schema sources before a build or deploy. This package encapsulates that pipeline so it can be shared across projects without duplicating the build directory.
|
|
10
|
+
|
|
11
|
+
Specifically, it:
|
|
12
|
+
|
|
13
|
+
- Injects `{% schema %}` blocks into Liquid files for sections, blocks, and snippets
|
|
14
|
+
- Generates and maintains `theme/config/settings_schema.json` from TS config schemas
|
|
15
|
+
- Generates `theme/sections/{name}-group.json` files for section groups (preserving merchant-managed `sections` and `order` data)
|
|
16
|
+
- Extracts translatable strings from all schema types and merges them into `theme/locales/en.default.schema.json`
|
|
17
|
+
- Provides a Vite config factory (`createViteConfig`) that wires up vite-plugin-shopify, full-reload, tailwind, and a schema watcher plugin — all config-aware
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
- Node >= 22.0.0
|
|
24
|
+
- Vite >= 8.0.0
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install @akqa-denmark/shopify-theme-build
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Peer dependencies (install as needed):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install --save-dev vite vite-plugin-shopify vite-plugin-full-reload
|
|
38
|
+
npm install --save-dev @tailwindcss/vite # optional
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Setup
|
|
44
|
+
|
|
45
|
+
### 1. Create `shopify-build.config.ts` at the repo root
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { defineConfig } from '@akqa-denmark/shopify-theme-build';
|
|
49
|
+
|
|
50
|
+
export default defineConfig({
|
|
51
|
+
stores: [
|
|
52
|
+
{ slug: 'my-store', name: 'My Store' },
|
|
53
|
+
{ slug: 'base', name: 'Base', foundation: true },
|
|
54
|
+
],
|
|
55
|
+
defaultStore: 'my-store',
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`foundation: true` marks a store as non-deployable (excluded from `shopify-build manifest`).
|
|
60
|
+
|
|
61
|
+
Full config options:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
interface BuildConfig {
|
|
65
|
+
stores: { slug: string; name: string; foundation?: boolean }[];
|
|
66
|
+
defaultStore: string;
|
|
67
|
+
storesDirectory?: string; // default: 'stores'
|
|
68
|
+
sharedDirectory?: string; // default: 'shared'
|
|
69
|
+
build?: {
|
|
70
|
+
parallel?: boolean; // default: true
|
|
71
|
+
};
|
|
72
|
+
vite?: {
|
|
73
|
+
port?: number; // default: 3000
|
|
74
|
+
bundledDev?: boolean; // default: false
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 2. Update `vite.config.ts`
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { createViteConfig } from '@akqa-denmark/shopify-theme-build/vite';
|
|
83
|
+
|
|
84
|
+
export default createViteConfig();
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Pass overrides to merge with the base config:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
export default createViteConfig({
|
|
91
|
+
resolve: {
|
|
92
|
+
alias: [
|
|
93
|
+
{ find: '@', replacement: new URL('./stores/my-store/src', import.meta.url).pathname },
|
|
94
|
+
{ find: /^@shared\/(.*)/, replacement: new URL('./shared/$1', import.meta.url).pathname },
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 3. Update `package.json` scripts
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"scripts": {
|
|
105
|
+
"build:prepare": "shopify-build prepare",
|
|
106
|
+
"build:full": "shopify-build prepare && vite build"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
To target a specific store, use `--store` or set `CURRENT_STORE`:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
shopify-build prepare --store my-store
|
|
115
|
+
CURRENT_STORE=my-store shopify-build prepare
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Directory conventions
|
|
121
|
+
|
|
122
|
+
The pipeline expects this structure inside each store directory:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
stores/{slug}/
|
|
126
|
+
├── src/
|
|
127
|
+
│ └── schemas/
|
|
128
|
+
│ ├── settings/ → locale data only
|
|
129
|
+
│ ├── configs/ → settings_schema.json entries
|
|
130
|
+
│ ├── sections/ → theme/sections/{name}.liquid schema injection
|
|
131
|
+
│ ├── blocks/ → theme/blocks/{name}.liquid schema injection
|
|
132
|
+
│ ├── section-blocks/ → theme/snippets/{name}.liquid schema injection
|
|
133
|
+
│ └── section-groups/ → theme/sections/{name}-group.json
|
|
134
|
+
└── theme/
|
|
135
|
+
├── config/
|
|
136
|
+
│ └── settings_schema.json (generated)
|
|
137
|
+
├── locales/
|
|
138
|
+
│ └── en.default.schema.json (generated)
|
|
139
|
+
├── sections/
|
|
140
|
+
├── blocks/
|
|
141
|
+
└── snippets/
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## CLI
|
|
147
|
+
|
|
148
|
+
### `shopify-build prepare`
|
|
149
|
+
|
|
150
|
+
Runs the full schema pipeline for a store.
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
shopify-build prepare
|
|
154
|
+
shopify-build prepare --store georg-jensen
|
|
155
|
+
shopify-build prepare --skip-schemas
|
|
156
|
+
shopify-build prepare --skip-locales
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### `shopify-build manifest`
|
|
160
|
+
|
|
161
|
+
Outputs a JSON manifest of all deployable stores to stdout. Useful for CI matrix generation.
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
shopify-build manifest
|
|
165
|
+
# {
|
|
166
|
+
# "stores": [
|
|
167
|
+
# { "slug": "my-store", "name": "My Store", "deployable": true },
|
|
168
|
+
# { "slug": "base", "name": "Base", "deployable": false }
|
|
169
|
+
# ]
|
|
170
|
+
# }
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## API
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { defineConfig, resolveConfig, resolveStore, getStorePaths, orchestrate } from '@akqa-denmark/shopify-theme-build';
|
|
179
|
+
import { createViteConfig } from '@akqa-denmark/shopify-theme-build/vite';
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
| Export | Description |
|
|
183
|
+
|---|---|
|
|
184
|
+
| `defineConfig(config)` | Type-safe config helper |
|
|
185
|
+
| `resolveConfig(cwd?)` | Loads and validates `shopify-build.config.ts` |
|
|
186
|
+
| `resolveStore(config, explicit?)` | Resolves store from arg, `CURRENT_STORE` env, or `defaultStore` |
|
|
187
|
+
| `getStorePaths(config, store)` | Returns resolved path object for a store |
|
|
188
|
+
| `orchestrate(options)` | Runs the full pipeline programmatically |
|
|
189
|
+
| `createViteConfig(overrides?)` | Returns a Vite `UserConfig` |
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Schema file conventions
|
|
194
|
+
|
|
195
|
+
### Sections and blocks
|
|
196
|
+
|
|
197
|
+
Export a default object or a function returning an object:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// stores/my-store/src/schemas/sections/hero.ts
|
|
201
|
+
export default {
|
|
202
|
+
name: 'Hero',
|
|
203
|
+
settings: [
|
|
204
|
+
{ type: 'text', id: 'heading', label: 'Heading', defaultLabel: 'Heading' },
|
|
205
|
+
],
|
|
206
|
+
};
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Configs
|
|
210
|
+
|
|
211
|
+
Export a named function in the form `{PascalName}Config`:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// stores/my-store/src/schemas/configs/brand.ts
|
|
215
|
+
export function BrandConfig() {
|
|
216
|
+
return {
|
|
217
|
+
name: 'Brand',
|
|
218
|
+
settings: [
|
|
219
|
+
{ type: 'color', id: 'primary_color', label: 'Primary colour' },
|
|
220
|
+
],
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`theme-info.ts` is handled separately — the pipeline injects `theme_version` from git tags and `theme_name` from the store's `name` in config.
|
|
226
|
+
|
|
227
|
+
### Section groups
|
|
228
|
+
|
|
229
|
+
Export a default object with `type` and `name`. The `sections` and `order` fields are managed by the Shopify theme editor and are preserved from any existing file.
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
// stores/my-store/src/schemas/section-groups/header.ts
|
|
233
|
+
export default {
|
|
234
|
+
type: 'header',
|
|
235
|
+
name: 'Header group',
|
|
236
|
+
};
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Version resolution
|
|
242
|
+
|
|
243
|
+
`theme_version` in `settings_schema.json` is resolved at build time:
|
|
244
|
+
|
|
245
|
+
1. `RELEASE_TAG` env var (CI deploy — e.g. `v1.18.0` → `1.18.0`)
|
|
246
|
+
2. CI without `RELEASE_TAG` → latest git tag, no suffix
|
|
247
|
+
3. Local dev → latest git tag + `-dev` suffix (e.g. `1.18.0-dev`)
|
|
248
|
+
4. Fallback: `dev`
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Development
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
npm run build # compile to dist/
|
|
256
|
+
npm run dev # watch mode
|
|
257
|
+
npm run type-check # tsc --noEmit
|
|
258
|
+
```
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/config/resolve.ts
|
|
4
|
+
import { existsSync } from "fs";
|
|
5
|
+
import { resolve, join } from "path";
|
|
6
|
+
var CONFIG_FILE_NAMES = [
|
|
7
|
+
"shopify-build.config.ts",
|
|
8
|
+
"shopify-build.config.js",
|
|
9
|
+
"shopify-build.config.mjs"
|
|
10
|
+
];
|
|
11
|
+
async function resolveConfig(cwd) {
|
|
12
|
+
const rootDir = cwd || process.cwd();
|
|
13
|
+
const configPath = findConfigFile(rootDir);
|
|
14
|
+
if (!configPath) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`No config file found. Create one of: ${CONFIG_FILE_NAMES.join(", ")}`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
const { createJiti } = await import("jiti");
|
|
20
|
+
const jiti = createJiti(rootDir);
|
|
21
|
+
const rawModule = await jiti.import(configPath);
|
|
22
|
+
const raw = rawModule.default || rawModule;
|
|
23
|
+
if (!raw.stores || raw.stores.length === 0) {
|
|
24
|
+
throw new Error("Config must define at least one store");
|
|
25
|
+
}
|
|
26
|
+
if (!raw.defaultStore) {
|
|
27
|
+
throw new Error("Config must define a defaultStore");
|
|
28
|
+
}
|
|
29
|
+
const defaultExists = raw.stores.some((s) => s.slug === raw.defaultStore);
|
|
30
|
+
if (!defaultExists) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`defaultStore "${raw.defaultStore}" not found in stores array`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
stores: raw.stores,
|
|
37
|
+
defaultStore: raw.defaultStore,
|
|
38
|
+
storesDirectory: raw.storesDirectory || "stores",
|
|
39
|
+
sharedDirectory: raw.sharedDirectory || "shared",
|
|
40
|
+
rootDir,
|
|
41
|
+
build: {
|
|
42
|
+
parallel: raw.build?.parallel ?? true
|
|
43
|
+
},
|
|
44
|
+
vite: {
|
|
45
|
+
port: raw.vite?.port ?? 3e3,
|
|
46
|
+
bundledDev: raw.vite?.bundledDev ?? false
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function findConfigFile(rootDir) {
|
|
51
|
+
for (const name of CONFIG_FILE_NAMES) {
|
|
52
|
+
const fullPath = resolve(rootDir, name);
|
|
53
|
+
if (existsSync(fullPath)) return fullPath;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
function resolveStore(config, explicit) {
|
|
58
|
+
const store = explicit || process.env.CURRENT_STORE || config.defaultStore;
|
|
59
|
+
const exists = config.stores.some((s) => s.slug === store);
|
|
60
|
+
if (!exists) {
|
|
61
|
+
const valid = config.stores.map((s) => s.slug).join(", ");
|
|
62
|
+
throw new Error(`Store "${store}" not found. Valid: ${valid}`);
|
|
63
|
+
}
|
|
64
|
+
return store;
|
|
65
|
+
}
|
|
66
|
+
function getStorePaths(config, store) {
|
|
67
|
+
const storeDir = join(config.rootDir, config.storesDirectory, store);
|
|
68
|
+
const themeDir = join(storeDir, "theme");
|
|
69
|
+
const srcDir = join(storeDir, "src");
|
|
70
|
+
const schemasDir = join(srcDir, "schemas");
|
|
71
|
+
const entrypointsDir = join(srcDir, "assets/scripts/entrypoints");
|
|
72
|
+
const localesDir = join(themeDir, "locales");
|
|
73
|
+
const configDir = join(themeDir, "config");
|
|
74
|
+
return { storeDir, themeDir, srcDir, schemasDir, entrypointsDir, localesDir, configDir };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export {
|
|
78
|
+
resolveConfig,
|
|
79
|
+
resolveStore,
|
|
80
|
+
getStorePaths
|
|
81
|
+
};
|