@semi-solid/compiler 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/LICENSE +21 -0
- package/dist/chunk-BDCUB2QP.js +28 -0
- package/dist/chunk-HSIWLJX2.js +41 -0
- package/dist/chunk-X2A6LNIZ.js +429 -0
- package/dist/cli/bin.d.ts +2 -0
- package/dist/cli/bin.js +9 -0
- package/dist/cli/config.d.ts +17 -0
- package/dist/cli/config.js +6 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +8 -0
- package/dist/index.d.ts +645 -0
- package/dist/index.js +2115 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 semi-solid contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// src/cli/config.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { pathToFileURL } from "url";
|
|
5
|
+
async function loadConfig(root) {
|
|
6
|
+
const configPath = path.join(root, "semi-solid.config.ts");
|
|
7
|
+
if (fs.existsSync(configPath)) {
|
|
8
|
+
const mod = await import(pathToFileURL(configPath).href);
|
|
9
|
+
return mod.default;
|
|
10
|
+
}
|
|
11
|
+
const brandsDir = path.join(root, "src", "brands");
|
|
12
|
+
const brands = {};
|
|
13
|
+
if (fs.existsSync(brandsDir)) {
|
|
14
|
+
for (const brand of fs.readdirSync(brandsDir)) {
|
|
15
|
+
const i18nDir = path.join(brandsDir, brand, "i18n");
|
|
16
|
+
if (!fs.existsSync(i18nDir)) continue;
|
|
17
|
+
const locales = fs.readdirSync(i18nDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(/\.json$/, ""));
|
|
18
|
+
if (locales.length > 0) {
|
|
19
|
+
brands[brand] = { locales };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return { brands };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
loadConfig
|
|
28
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// src/i18n.ts
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
var VIRTUAL_LOCALE_MODULE = "virtual:semi-solid/locale";
|
|
5
|
+
var RESOLVED_VIRTUAL_ID = "\0" + VIRTUAL_LOCALE_MODULE;
|
|
6
|
+
function resolveLocaleFiles(brand, locale, projectRoot, outDir, existsSync = fs.existsSync, readdirSync = (d) => fs.readdirSync(d)) {
|
|
7
|
+
const i18nDir = path.join(projectRoot, "src", "brands", brand, "i18n");
|
|
8
|
+
const localesOutDir = path.join(outDir, "locales");
|
|
9
|
+
if (!existsSync(i18nDir)) return [];
|
|
10
|
+
const files = readdirSync(i18nDir).filter((f) => f.endsWith(".json"));
|
|
11
|
+
return files.map((file) => {
|
|
12
|
+
const lang = file.replace(/\.json$/, "");
|
|
13
|
+
const destName = lang === locale ? `${locale}.default.json` : file;
|
|
14
|
+
return {
|
|
15
|
+
src: path.join(i18nDir, file),
|
|
16
|
+
dest: path.join(localesOutDir, destName)
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function resolveActiveLocalePath(brand, locale, projectRoot, existsSync = fs.existsSync) {
|
|
21
|
+
const candidate = path.join(
|
|
22
|
+
projectRoot,
|
|
23
|
+
"src",
|
|
24
|
+
"brands",
|
|
25
|
+
brand,
|
|
26
|
+
"i18n",
|
|
27
|
+
`${locale}.json`
|
|
28
|
+
);
|
|
29
|
+
return existsSync(candidate) ? candidate : null;
|
|
30
|
+
}
|
|
31
|
+
var virtualLocaleIds = {
|
|
32
|
+
external: VIRTUAL_LOCALE_MODULE,
|
|
33
|
+
internal: RESOLVED_VIRTUAL_ID
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
VIRTUAL_LOCALE_MODULE,
|
|
38
|
+
resolveLocaleFiles,
|
|
39
|
+
resolveActiveLocalePath,
|
|
40
|
+
virtualLocaleIds
|
|
41
|
+
};
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveLocaleFiles
|
|
3
|
+
} from "./chunk-HSIWLJX2.js";
|
|
4
|
+
import {
|
|
5
|
+
loadConfig
|
|
6
|
+
} from "./chunk-BDCUB2QP.js";
|
|
7
|
+
|
|
8
|
+
// src/cli/index.ts
|
|
9
|
+
import { defineCommand as defineCommand5, runMain as cittyRunMain } from "citty";
|
|
10
|
+
|
|
11
|
+
// src/cli/commands/build.ts
|
|
12
|
+
import { defineCommand } from "citty";
|
|
13
|
+
|
|
14
|
+
// src/cli/vite-bridge.ts
|
|
15
|
+
import { build } from "vite";
|
|
16
|
+
async function runBuild(opts) {
|
|
17
|
+
process.env.BRAND = opts.brand;
|
|
18
|
+
process.env.LOCALE = opts.locale;
|
|
19
|
+
await build();
|
|
20
|
+
}
|
|
21
|
+
async function runWatchBuild(opts) {
|
|
22
|
+
process.env.BRAND = opts.brand;
|
|
23
|
+
process.env.LOCALE = opts.locale;
|
|
24
|
+
await build({ build: { watch: {} } });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/cli/commands/build.ts
|
|
28
|
+
var buildCommand = defineCommand({
|
|
29
|
+
meta: {
|
|
30
|
+
name: "build",
|
|
31
|
+
description: "Build a single brand/locale combo"
|
|
32
|
+
},
|
|
33
|
+
args: {
|
|
34
|
+
brand: {
|
|
35
|
+
type: "string",
|
|
36
|
+
description: "Brand identifier (e.g. brand-a)",
|
|
37
|
+
required: true
|
|
38
|
+
},
|
|
39
|
+
locale: {
|
|
40
|
+
type: "string",
|
|
41
|
+
description: "Locale identifier (e.g. en)",
|
|
42
|
+
required: true
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
async run({ args }) {
|
|
46
|
+
console.log(`Building ${args.brand}/${args.locale}...`);
|
|
47
|
+
await runBuild({ brand: args.brand, locale: args.locale });
|
|
48
|
+
console.log(`Done: ${args.brand}/${args.locale}`);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// src/cli/commands/build-all.ts
|
|
53
|
+
import { defineCommand as defineCommand2 } from "citty";
|
|
54
|
+
var buildAllCommand = defineCommand2({
|
|
55
|
+
meta: {
|
|
56
|
+
name: "build-all",
|
|
57
|
+
description: "Build all brand/locale combos from config"
|
|
58
|
+
},
|
|
59
|
+
args: {
|
|
60
|
+
parallel: {
|
|
61
|
+
type: "boolean",
|
|
62
|
+
description: "Run builds in parallel",
|
|
63
|
+
default: false
|
|
64
|
+
},
|
|
65
|
+
concurrency: {
|
|
66
|
+
type: "string",
|
|
67
|
+
description: "Max parallel builds (requires --parallel)",
|
|
68
|
+
default: "3"
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
async run({ args }) {
|
|
72
|
+
const config = await loadConfig(process.cwd());
|
|
73
|
+
const pairs = [];
|
|
74
|
+
for (const [brand, brandConfig] of Object.entries(config.brands)) {
|
|
75
|
+
for (const locale of brandConfig.locales) {
|
|
76
|
+
pairs.push({ brand, locale });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (pairs.length === 0) {
|
|
80
|
+
console.log("No brand/locale pairs found in config.");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
console.log(`Building ${pairs.length} combo(s)...`);
|
|
84
|
+
if (args.parallel) {
|
|
85
|
+
const concurrency = parseInt(args.concurrency, 10) || 3;
|
|
86
|
+
const queue = [...pairs];
|
|
87
|
+
const running = [];
|
|
88
|
+
while (queue.length > 0 || running.length > 0) {
|
|
89
|
+
while (running.length < concurrency && queue.length > 0) {
|
|
90
|
+
const pair = queue.shift();
|
|
91
|
+
const task = (async () => {
|
|
92
|
+
console.log(` Building ${pair.brand}/${pair.locale}...`);
|
|
93
|
+
await runBuild(pair);
|
|
94
|
+
console.log(` Done: ${pair.brand}/${pair.locale}`);
|
|
95
|
+
})();
|
|
96
|
+
running.push(task);
|
|
97
|
+
}
|
|
98
|
+
if (running.length > 0) {
|
|
99
|
+
await Promise.race(running);
|
|
100
|
+
for (let i = running.length - 1; i >= 0; i--) {
|
|
101
|
+
const settled = await Promise.race([
|
|
102
|
+
running[i].then(() => true),
|
|
103
|
+
Promise.resolve(false)
|
|
104
|
+
]);
|
|
105
|
+
if (settled) running.splice(i, 1);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
for (const pair of pairs) {
|
|
111
|
+
console.log(` Building ${pair.brand}/${pair.locale}...`);
|
|
112
|
+
await runBuild(pair);
|
|
113
|
+
console.log(` Done: ${pair.brand}/${pair.locale}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
console.log(`All ${pairs.length} builds complete.`);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// src/cli/commands/dev.ts
|
|
121
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
122
|
+
import { spawn } from "child_process";
|
|
123
|
+
var devCommand = defineCommand3({
|
|
124
|
+
meta: {
|
|
125
|
+
name: "dev",
|
|
126
|
+
description: "Watch build + Shopify theme dev"
|
|
127
|
+
},
|
|
128
|
+
args: {
|
|
129
|
+
brand: {
|
|
130
|
+
type: "string",
|
|
131
|
+
description: "Brand identifier (e.g. brand-a)",
|
|
132
|
+
required: true
|
|
133
|
+
},
|
|
134
|
+
locale: {
|
|
135
|
+
type: "string",
|
|
136
|
+
description: "Locale identifier (e.g. en)",
|
|
137
|
+
required: true
|
|
138
|
+
},
|
|
139
|
+
store: {
|
|
140
|
+
type: "string",
|
|
141
|
+
description: "Shopify store URL (overrides config)"
|
|
142
|
+
},
|
|
143
|
+
"no-shopify": {
|
|
144
|
+
type: "boolean",
|
|
145
|
+
description: "Skip Shopify CLI (watch-only mode)",
|
|
146
|
+
default: false
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
async run({ args }) {
|
|
150
|
+
const brand = args.brand;
|
|
151
|
+
const locale = args.locale;
|
|
152
|
+
const noShopify = args["no-shopify"];
|
|
153
|
+
console.log(`Starting dev for ${brand}/${locale}...`);
|
|
154
|
+
const watchPromise = runWatchBuild({ brand, locale });
|
|
155
|
+
if (!noShopify) {
|
|
156
|
+
const config = await loadConfig(process.cwd());
|
|
157
|
+
const brandConfig = config.brands[brand];
|
|
158
|
+
const store = args.store || brandConfig?.store;
|
|
159
|
+
if (!store) {
|
|
160
|
+
console.error(
|
|
161
|
+
`No store URL for brand "${brand}". Pass --store or set it in semi-solid.config.ts.`
|
|
162
|
+
);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
const distPath = `./dist/${brand}/${locale}`;
|
|
166
|
+
const shopifyArgs = ["theme", "dev", "--path", distPath, "--store", store];
|
|
167
|
+
if (brandConfig?.storePassword) {
|
|
168
|
+
shopifyArgs.push("--store-password", brandConfig.storePassword);
|
|
169
|
+
}
|
|
170
|
+
console.log(`Starting Shopify CLI: shopify ${shopifyArgs.join(" ")}`);
|
|
171
|
+
const shopify = spawn("shopify", shopifyArgs, {
|
|
172
|
+
stdio: "inherit",
|
|
173
|
+
shell: true
|
|
174
|
+
});
|
|
175
|
+
shopify.on("error", (err) => {
|
|
176
|
+
console.error(`Shopify CLI error: ${err.message}`);
|
|
177
|
+
});
|
|
178
|
+
shopify.on("close", (code) => {
|
|
179
|
+
if (code !== 0) {
|
|
180
|
+
console.error(`Shopify CLI exited with code ${code}`);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
await watchPromise;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// src/cli/commands/backfill.ts
|
|
189
|
+
import { defineCommand as defineCommand4 } from "citty";
|
|
190
|
+
import fs from "fs";
|
|
191
|
+
import path from "path";
|
|
192
|
+
var REQUIRED_THEME_FILES = {
|
|
193
|
+
"layout/theme.liquid": [
|
|
194
|
+
"<!DOCTYPE html>",
|
|
195
|
+
'<html lang="{{ request.locale.iso_code }}">',
|
|
196
|
+
"<head>",
|
|
197
|
+
' <meta charset="utf-8">',
|
|
198
|
+
" {{ content_for_header }}",
|
|
199
|
+
"</head>",
|
|
200
|
+
"<body>",
|
|
201
|
+
" {{ content_for_layout }}",
|
|
202
|
+
"</body>",
|
|
203
|
+
"</html>",
|
|
204
|
+
""
|
|
205
|
+
].join("\n"),
|
|
206
|
+
"templates/gift_card.liquid": [
|
|
207
|
+
"<!DOCTYPE html>",
|
|
208
|
+
'<html lang="{{ request.locale.iso_code }}">',
|
|
209
|
+
"<head>",
|
|
210
|
+
' <meta charset="utf-8">',
|
|
211
|
+
' <meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
212
|
+
" <title>{{ 'gift_cards.issued.title' | t }} \u2014 {{ shop.name }}</title>",
|
|
213
|
+
" {{ content_for_header }}",
|
|
214
|
+
" {% render 'theme-assets' %}",
|
|
215
|
+
"</head>",
|
|
216
|
+
'<body class="gift-card-page">',
|
|
217
|
+
' <header class="text-center py-8">',
|
|
218
|
+
' <a href="{{ shop.url }}">',
|
|
219
|
+
" <h1>{{ shop.name }}</h1>",
|
|
220
|
+
" </a>",
|
|
221
|
+
" </header>",
|
|
222
|
+
' <main class="gift-card max-w-md mx-auto px-4 text-center">',
|
|
223
|
+
" <h2>{{ 'gift_cards.issued.subtext' | t }}</h2>",
|
|
224
|
+
" {% if gift_card.enabled %}",
|
|
225
|
+
' <p class="gift-card__amount text-4xl font-bold my-4">{{ gift_card.initial_value | money }}</p>',
|
|
226
|
+
" {% if gift_card.balance != gift_card.initial_value %}",
|
|
227
|
+
" <p>{{ 'gift_cards.issued.remaining_html' | t: balance: gift_card.balance | money }}</p>",
|
|
228
|
+
" {% endif %}",
|
|
229
|
+
' <div class="gift-card__code my-6">',
|
|
230
|
+
' <input type="text" value="{{ gift_card.code | format_code }}" class="text-center border rounded px-4 py-3 w-full text-lg tracking-widest" readonly onfocus="this.select();">',
|
|
231
|
+
" </div>",
|
|
232
|
+
" {% if gift_card.pass_url %}",
|
|
233
|
+
' <a href="{{ gift_card.pass_url }}" class="inline-block mb-4">',
|
|
234
|
+
` <img src="{{ 'gift-card/add-to-apple-wallet.svg' | shopify_asset_url }}" alt="{{ 'gift_cards.issued.add_to_apple_wallet' | t }}" width="120">`,
|
|
235
|
+
" </a>",
|
|
236
|
+
" {% endif %}",
|
|
237
|
+
` <p class="text-sm text-gray-500">{{ 'gift_cards.issued.expiry_html' | t: expires: gift_card.expires_on | date: "%B %d, %Y" }}</p>`,
|
|
238
|
+
" {% else %}",
|
|
239
|
+
" <p>{{ 'gift_cards.issued.disabled' | t }}</p>",
|
|
240
|
+
" {% endif %}",
|
|
241
|
+
` <a href="{{ shop.url }}" class="inline-block mt-8 bg-primary text-white px-8 py-3 rounded">{{ 'gift_cards.issued.shop_link' | t }}</a>`,
|
|
242
|
+
" </main>",
|
|
243
|
+
"</body>",
|
|
244
|
+
"</html>",
|
|
245
|
+
""
|
|
246
|
+
].join("\n"),
|
|
247
|
+
"config/settings_schema.json": JSON.stringify(
|
|
248
|
+
[
|
|
249
|
+
{
|
|
250
|
+
name: "theme_info",
|
|
251
|
+
theme_name: "Semi-Solid",
|
|
252
|
+
theme_version: "1.0.0",
|
|
253
|
+
theme_author: "Semi-Solid",
|
|
254
|
+
theme_documentation_url: "https://github.com/CarlR100/semi-solid",
|
|
255
|
+
theme_support_url: "https://github.com/CarlR100/semi-solid"
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: "Colors",
|
|
259
|
+
settings: [
|
|
260
|
+
{ type: "color", id: "color_primary", label: "Primary", default: "#111827" },
|
|
261
|
+
{ type: "color", id: "color_secondary", label: "Secondary", default: "#6b7280" },
|
|
262
|
+
{ type: "color", id: "color_background", label: "Background", default: "#ffffff" },
|
|
263
|
+
{ type: "color", id: "color_text", label: "Text", default: "#111827" }
|
|
264
|
+
]
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
name: "Typography",
|
|
268
|
+
settings: [
|
|
269
|
+
{ type: "font_picker", id: "heading_font", label: "Heading font", default: "assistant_n4" },
|
|
270
|
+
{ type: "font_picker", id: "body_font", label: "Body font", default: "assistant_n4" }
|
|
271
|
+
]
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
name: "Layout",
|
|
275
|
+
settings: [
|
|
276
|
+
{ type: "range", id: "page_width", label: "Page width", default: 1200, min: 1e3, max: 1600, step: 100, unit: "px" }
|
|
277
|
+
]
|
|
278
|
+
}
|
|
279
|
+
],
|
|
280
|
+
null,
|
|
281
|
+
2
|
|
282
|
+
) + "\n",
|
|
283
|
+
"config/settings_data.json": [
|
|
284
|
+
"// This file is auto-generated. Edit config/settings_schema.json for schema.",
|
|
285
|
+
'{ "current": {} }',
|
|
286
|
+
""
|
|
287
|
+
].join("\n")
|
|
288
|
+
};
|
|
289
|
+
var VALID_TARGETS = ["locales", "templates", "scaffold", "theme"];
|
|
290
|
+
var backfillCommand = defineCommand4({
|
|
291
|
+
meta: {
|
|
292
|
+
name: "backfill",
|
|
293
|
+
description: "Targeted regeneration of specific outputs without a full Vite build"
|
|
294
|
+
},
|
|
295
|
+
args: {
|
|
296
|
+
brand: {
|
|
297
|
+
type: "string",
|
|
298
|
+
description: "Brand identifier (e.g. brand-a)",
|
|
299
|
+
required: true
|
|
300
|
+
},
|
|
301
|
+
locale: {
|
|
302
|
+
type: "string",
|
|
303
|
+
description: "Locale identifier (e.g. en)",
|
|
304
|
+
required: true
|
|
305
|
+
},
|
|
306
|
+
target: {
|
|
307
|
+
type: "string",
|
|
308
|
+
description: "Comma-separated targets: locales, templates, scaffold, theme",
|
|
309
|
+
required: true
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
async run({ args }) {
|
|
313
|
+
const { brand, locale } = args;
|
|
314
|
+
const root = process.cwd();
|
|
315
|
+
const outDir = path.resolve(root, "dist", brand, locale);
|
|
316
|
+
const targets = args.target.split(",").map((t) => t.trim());
|
|
317
|
+
for (const t of targets) {
|
|
318
|
+
if (!VALID_TARGETS.includes(t)) {
|
|
319
|
+
console.error(`Invalid target: "${t}". Valid targets: ${VALID_TARGETS.join(", ")}`);
|
|
320
|
+
process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
console.log(`Backfilling ${targets.join(", ")} for ${brand}/${locale}...`);
|
|
324
|
+
if (targets.includes("locales")) {
|
|
325
|
+
backfillLocales(brand, locale, root, outDir);
|
|
326
|
+
}
|
|
327
|
+
if (targets.includes("templates")) {
|
|
328
|
+
backfillTemplates(brand, root, outDir);
|
|
329
|
+
}
|
|
330
|
+
if (targets.includes("scaffold")) {
|
|
331
|
+
backfillScaffold(outDir);
|
|
332
|
+
}
|
|
333
|
+
if (targets.includes("theme")) {
|
|
334
|
+
backfillTheme(brand, root, outDir);
|
|
335
|
+
}
|
|
336
|
+
console.log("Backfill complete.");
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
function backfillLocales(brand, locale, root, outDir) {
|
|
340
|
+
const pairs = resolveLocaleFiles(brand, locale, root, outDir);
|
|
341
|
+
if (pairs.length === 0) {
|
|
342
|
+
console.log(" No locale files found.");
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
for (const { src, dest } of pairs) {
|
|
346
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
347
|
+
fs.copyFileSync(src, dest);
|
|
348
|
+
console.log(` Copied ${path.basename(src)} \u2192 ${path.relative(root, dest)}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
function backfillTemplates(brand, root, outDir) {
|
|
352
|
+
const baseDir = path.join(root, "src", "templates");
|
|
353
|
+
const brandDir = path.join(root, "src", "brands", brand, "templates");
|
|
354
|
+
const destDir = path.join(outDir, "templates");
|
|
355
|
+
const templateMap = /* @__PURE__ */ new Map();
|
|
356
|
+
for (const dir of [baseDir, brandDir]) {
|
|
357
|
+
if (!fs.existsSync(dir)) continue;
|
|
358
|
+
for (const file of fs.readdirSync(dir)) {
|
|
359
|
+
if (!file.endsWith(".json")) continue;
|
|
360
|
+
templateMap.set(file, path.join(dir, file));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (templateMap.size === 0) {
|
|
364
|
+
console.log(" No template files found.");
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
368
|
+
for (const [file, src] of templateMap) {
|
|
369
|
+
fs.copyFileSync(src, path.join(destDir, file));
|
|
370
|
+
console.log(` Copied templates/${file}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
function backfillScaffold(outDir) {
|
|
374
|
+
for (const [relPath, content] of Object.entries(REQUIRED_THEME_FILES)) {
|
|
375
|
+
const fullPath = path.join(outDir, relPath);
|
|
376
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
377
|
+
fs.writeFileSync(fullPath, content, "utf-8");
|
|
378
|
+
console.log(` Wrote ${relPath}`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function backfillTheme(brand, root, outDir) {
|
|
382
|
+
const baseThemeDir = path.join(root, "src", "theme");
|
|
383
|
+
const brandThemeDir = path.join(root, "src", "brands", brand, "theme");
|
|
384
|
+
let copied = 0;
|
|
385
|
+
for (const themeDir of [baseThemeDir, brandThemeDir]) {
|
|
386
|
+
if (!fs.existsSync(themeDir)) continue;
|
|
387
|
+
const copyDir = (dir, relBase) => {
|
|
388
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
389
|
+
const srcPath = path.join(dir, entry.name);
|
|
390
|
+
const relPath = path.join(relBase, entry.name);
|
|
391
|
+
if (entry.isDirectory()) {
|
|
392
|
+
copyDir(srcPath, relPath);
|
|
393
|
+
} else {
|
|
394
|
+
const destPath = path.join(outDir, relPath);
|
|
395
|
+
if (fs.existsSync(destPath)) continue;
|
|
396
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
397
|
+
fs.copyFileSync(srcPath, destPath);
|
|
398
|
+
console.log(` Copied theme/${relPath}${themeDir === brandThemeDir ? ` (brand: ${brand})` : ""}`);
|
|
399
|
+
copied++;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
copyDir(themeDir, "");
|
|
404
|
+
}
|
|
405
|
+
if (copied === 0) {
|
|
406
|
+
console.log(" No theme files to copy.");
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// src/cli/index.ts
|
|
411
|
+
var main = defineCommand5({
|
|
412
|
+
meta: {
|
|
413
|
+
name: "semi-solid",
|
|
414
|
+
description: "Semi-Solid CLI \u2014 build Shopify themes from SolidJS components"
|
|
415
|
+
},
|
|
416
|
+
subCommands: {
|
|
417
|
+
build: buildCommand,
|
|
418
|
+
"build-all": buildAllCommand,
|
|
419
|
+
dev: devCommand,
|
|
420
|
+
backfill: backfillCommand
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
function runMain() {
|
|
424
|
+
cittyRunMain(main);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export {
|
|
428
|
+
runMain
|
|
429
|
+
};
|
package/dist/cli/bin.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface BrandConfig {
|
|
2
|
+
locales: string[];
|
|
3
|
+
store?: string;
|
|
4
|
+
storePassword?: string;
|
|
5
|
+
}
|
|
6
|
+
interface SemiSolidConfig {
|
|
7
|
+
brands: Record<string, BrandConfig>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Load the semi-solid config from the project root.
|
|
11
|
+
*
|
|
12
|
+
* Tries to import `semi-solid.config.ts` (via tsx runtime).
|
|
13
|
+
* Falls back to auto-discovery from `src/brands/* /i18n/*.json`.
|
|
14
|
+
*/
|
|
15
|
+
declare function loadConfig(root: string): Promise<SemiSolidConfig>;
|
|
16
|
+
|
|
17
|
+
export { type BrandConfig, type SemiSolidConfig, loadConfig };
|