@cobberhq/cli 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 +67 -0
- package/dist/commands/dev.js +75 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/login.js +46 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/pull.js +95 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/push.js +83 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/index.js +79 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api.js +89 -0
- package/dist/lib/api.js.map +1 -0
- package/dist/lib/config.js +39 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/manifest.js +18 -0
- package/dist/lib/manifest.js.map +1 -0
- package/dist/lib/paths.js +36 -0
- package/dist/lib/paths.js.map +1 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# @cobberhq/cli
|
|
2
|
+
|
|
3
|
+
Local theme development for Cobber. Pull a draft theme to disk, edit in your editor of choice, sync changes live as you save.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @cobberhq/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Authenticate
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cobber login --api-base-url https://<subdomain>.cobberhq.com.au
|
|
15
|
+
# or set COBBER_API_URL
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
A browser window opens. Approve the device code and the CLI receives a long-lived token (stored at `~/.cobber/config.json`).
|
|
19
|
+
|
|
20
|
+
## Pull, edit, sync
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
cobber theme pull # prompts to pick a theme (or `--all`)
|
|
24
|
+
cobber theme pull standard # pulls just the "Standard" theme
|
|
25
|
+
cobber theme pull --all # pulls every theme
|
|
26
|
+
|
|
27
|
+
cd <subdomain>-themes/<slug>
|
|
28
|
+
cobber theme dev # watches for changes, opens preview URL
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Edits propagate to the draft theme via the API; the dashboard preview reflects them immediately. Production is untouched until somebody clicks **Publish** in `/dashboard/admin/themes/:id/draft`.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cobber theme push # bulk sync, useful if you edited offline
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Layout
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
<subdomain>-themes/
|
|
41
|
+
<slug>/
|
|
42
|
+
.cobber/manifest.json # checksums + draft id (don't edit)
|
|
43
|
+
page_layouts/*.liquid
|
|
44
|
+
site_layouts/*.liquid
|
|
45
|
+
snippets/*.liquid
|
|
46
|
+
widget_partials/*.liquid
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Each file maps 1:1 to a `ContentTemplate` on the server. Theme config (colours, fonts, custom CSS) is **not** synced — edit it in the dashboard.
|
|
50
|
+
|
|
51
|
+
## Development
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm install
|
|
55
|
+
npm run build # compile TS
|
|
56
|
+
npm run dev # tsc --watch
|
|
57
|
+
npm test # vitest
|
|
58
|
+
npm link # makes `cobber` resolve to the local build
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Point at a local Rails app:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
COBBER_API_URL=http://<subdomain>.localhost:3000 cobber login
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Releases publish to npm automatically: GitHub Actions watches `cli/**` and runs on every push to `master`. To cut a release, bump `version` in `cli/package.json` in the same PR as your changes. If the version is already on the registry, the workflow no-ops.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import chokidar from "chokidar";
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import open from "open";
|
|
5
|
+
import { CobberApi } from "../lib/api.js";
|
|
6
|
+
import { loadCredentials } from "../lib/config.js";
|
|
7
|
+
import { MANIFEST_DIR, MANIFEST_FILENAME, pathToTemplate, } from "../lib/paths.js";
|
|
8
|
+
import { checksum } from "../lib/manifest.js";
|
|
9
|
+
export async function dev(themeDir) {
|
|
10
|
+
const dir = themeDir ?? process.cwd();
|
|
11
|
+
const manifestPath = path.join(dir, MANIFEST_DIR, MANIFEST_FILENAME);
|
|
12
|
+
const manifest = JSON.parse(await fs.readFile(manifestPath, "utf8"));
|
|
13
|
+
const creds = await loadCredentials(manifest.subdomain);
|
|
14
|
+
const api = new CobberApi(manifest.apiBaseUrl, creds.accessToken);
|
|
15
|
+
// Open preview URL
|
|
16
|
+
try {
|
|
17
|
+
const preview = await api.request(`/api/v1/themes/${manifest.draftId}/preview_token`, { method: "POST" });
|
|
18
|
+
console.log(`Preview: ${preview.preview_url}`);
|
|
19
|
+
await open(preview.preview_url);
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
console.warn("Could not open preview URL:", err.message);
|
|
23
|
+
}
|
|
24
|
+
console.log(`Watching ${dir} for changes. Press Ctrl+C to stop.\n`);
|
|
25
|
+
const watcher = chokidar.watch([`${dir}/page_layouts`, `${dir}/site_layouts`, `${dir}/snippets`, `${dir}/widget_partials`], { ignoreInitial: true, awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 } });
|
|
26
|
+
const onChange = async (filepath) => {
|
|
27
|
+
const relPath = path.relative(dir, filepath).replace(/\\/g, "/");
|
|
28
|
+
const start = Date.now();
|
|
29
|
+
const tp = pathToTemplate(relPath);
|
|
30
|
+
if (!tp)
|
|
31
|
+
return;
|
|
32
|
+
try {
|
|
33
|
+
const content = await fs.readFile(filepath, "utf8");
|
|
34
|
+
const checksumValue = checksum(content);
|
|
35
|
+
const entry = manifest.templates.find((e) => e.category === tp.category && e.templateKey === tp.templateKey);
|
|
36
|
+
if (entry && entry.checksum === checksumValue)
|
|
37
|
+
return;
|
|
38
|
+
await api.request(`/api/v1/themes/${manifest.draftId}/templates/${tp.category}/${encodeURIComponent(tp.templateKey)}`, { method: "PUT", body: { html_content: content } });
|
|
39
|
+
if (entry) {
|
|
40
|
+
entry.checksum = checksumValue;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
manifest.templates.push({
|
|
44
|
+
category: tp.category,
|
|
45
|
+
templateKey: tp.templateKey,
|
|
46
|
+
checksum: checksumValue,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
50
|
+
console.log(`✓ ${relPath} → live (${Date.now() - start}ms)`);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
console.error(`✗ ${relPath}: ${err.message}`);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const onUnlink = async (filepath) => {
|
|
57
|
+
const relPath = path.relative(dir, filepath).replace(/\\/g, "/");
|
|
58
|
+
const tp = pathToTemplate(relPath);
|
|
59
|
+
if (!tp)
|
|
60
|
+
return;
|
|
61
|
+
try {
|
|
62
|
+
await api.request(`/api/v1/themes/${manifest.draftId}/templates/${tp.category}/${encodeURIComponent(tp.templateKey)}`, { method: "DELETE" });
|
|
63
|
+
manifest.templates = manifest.templates.filter((e) => !(e.category === tp.category && e.templateKey === tp.templateKey));
|
|
64
|
+
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
65
|
+
console.log(`- ${relPath} → removed`);
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
console.error(`✗ ${relPath}: ${err.message}`);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
watcher.on("add", onChange);
|
|
72
|
+
watcher.on("change", onChange);
|
|
73
|
+
watcher.on("unlink", onUnlink);
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=dev.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.js","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import open from "open";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import { CobberApi } from "../lib/api.js";
|
|
4
|
+
import { saveCredentials } from "../lib/config.js";
|
|
5
|
+
export async function login(opts) {
|
|
6
|
+
const api = new CobberApi(opts.apiBaseUrl);
|
|
7
|
+
const auth = await api.request("/api/v1/cli/device_authorize", {
|
|
8
|
+
method: "POST",
|
|
9
|
+
});
|
|
10
|
+
console.log("\n Visit this URL in your browser and enter the code below:\n");
|
|
11
|
+
console.log(` ${auth.verification_uri}?code=${auth.user_code}`);
|
|
12
|
+
console.log(` Code: ${auth.user_code}\n`);
|
|
13
|
+
try {
|
|
14
|
+
await open(`${auth.verification_uri}?code=${auth.user_code}`);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// ignore — user can copy/paste
|
|
18
|
+
}
|
|
19
|
+
const deviceName = opts.deviceName ?? `${os.hostname()}`;
|
|
20
|
+
const deadline = Date.now() + auth.expires_in * 1000;
|
|
21
|
+
while (Date.now() < deadline) {
|
|
22
|
+
await sleep(auth.poll_interval * 1000);
|
|
23
|
+
const res = await api.request("/api/v1/cli/device_token", {
|
|
24
|
+
method: "POST",
|
|
25
|
+
body: { device_code: auth.device_code, device_name: deviceName },
|
|
26
|
+
});
|
|
27
|
+
if (!("access_token" in res)) {
|
|
28
|
+
// 202 with `{ error: "authorization_pending" }` — keep polling.
|
|
29
|
+
process.stdout.write(".");
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
await saveCredentials(res.organization.subdomain, {
|
|
33
|
+
accessToken: res.access_token,
|
|
34
|
+
apiBaseUrl: opts.apiBaseUrl,
|
|
35
|
+
subdomain: res.organization.subdomain,
|
|
36
|
+
adminUserEmail: res.admin_user.email,
|
|
37
|
+
});
|
|
38
|
+
console.log(`\n✓ Logged in as ${res.admin_user.email} (${res.organization.subdomain}).`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
throw new Error("Login timed out. Run `cobber login` again.");
|
|
42
|
+
}
|
|
43
|
+
function sleep(ms) {
|
|
44
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAqBnD,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,IAAkB;IAC5C,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAE3C,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,OAAO,CAAqB,8BAA8B,EAAE;QACjF,MAAM,EAAE,MAAM;KACf,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,CAAC,gEAAgE,CAAC,CAAC;IAC9E,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,CAAC,gBAAgB,SAAS,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC;IAE7C,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,GAAG,IAAI,CAAC,gBAAgB,SAAS,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,+BAA+B;IACjC,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,GAAG,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC;IACzD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;IAErD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,MAAM,KAAK,CAAC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;QAEvC,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,OAAO,CAAoC,0BAA0B,EAAE;YAC3F,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE;SACjE,CAAC,CAAC;QAEH,IAAI,CAAC,CAAC,cAAc,IAAI,GAAG,CAAC,EAAE,CAAC;YAC7B,gEAAgE;YAChE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC1B,SAAS;QACX,CAAC;QAED,MAAM,eAAe,CAAC,GAAG,CAAC,YAAY,CAAC,SAAS,EAAE;YAChD,WAAW,EAAE,GAAG,CAAC,YAAY;YAC7B,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,SAAS,EAAE,GAAG,CAAC,YAAY,CAAC,SAAS;YACrC,cAAc,EAAE,GAAG,CAAC,UAAU,CAAC,KAAK;SACrC,CAAC,CAAC;QAEH,OAAO,CAAC,GAAG,CAAC,oBAAoB,GAAG,CAAC,UAAU,CAAC,KAAK,KAAK,GAAG,CAAC,YAAY,CAAC,SAAS,IAAI,CAAC,CAAC;QACzF,OAAO;IACT,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import readline from "node:readline/promises";
|
|
4
|
+
import { CobberApi } from "../lib/api.js";
|
|
5
|
+
import { loadCredentials } from "../lib/config.js";
|
|
6
|
+
import { templateToPath, MANIFEST_DIR, MANIFEST_FILENAME } from "../lib/paths.js";
|
|
7
|
+
import { checksum } from "../lib/manifest.js";
|
|
8
|
+
export async function pull(opts = {}) {
|
|
9
|
+
const creds = await loadCredentials();
|
|
10
|
+
const api = CobberApi.fromCredentials(creds);
|
|
11
|
+
const { themes } = await api.request("/api/v1/themes");
|
|
12
|
+
if (themes.length === 0) {
|
|
13
|
+
console.error("This organisation has no published themes.");
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const targets = opts.all
|
|
17
|
+
? themes
|
|
18
|
+
: [await chooseTheme(themes, opts.themeIdentifier)];
|
|
19
|
+
for (const target of targets) {
|
|
20
|
+
await pullOne(api, creds.subdomain, creds.apiBaseUrl, target);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function chooseTheme(themes, identifier) {
|
|
24
|
+
if (identifier) {
|
|
25
|
+
const match = themes.find((t) => t.slug === identifier ||
|
|
26
|
+
t.name.toLowerCase() === identifier.toLowerCase() ||
|
|
27
|
+
String(t.id) === identifier);
|
|
28
|
+
if (!match) {
|
|
29
|
+
throw new Error(`No theme matches "${identifier}". Available: ${themes.map((t) => t.slug).join(", ")}`);
|
|
30
|
+
}
|
|
31
|
+
return match;
|
|
32
|
+
}
|
|
33
|
+
if (themes.length === 1)
|
|
34
|
+
return themes[0];
|
|
35
|
+
if (!process.stdin.isTTY) {
|
|
36
|
+
throw new Error(`Multiple themes available; specify one. Available: ${themes.map((t) => t.slug).join(", ")} (or pass --all)`);
|
|
37
|
+
}
|
|
38
|
+
console.log("Available themes:");
|
|
39
|
+
themes.forEach((t, i) => {
|
|
40
|
+
console.log(` ${i + 1}. ${t.name}${t.active ? " (active)" : ""} [${t.slug}]`);
|
|
41
|
+
});
|
|
42
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
43
|
+
try {
|
|
44
|
+
const answer = (await rl.question("Pull which? (number or slug): ")).trim();
|
|
45
|
+
const byNumber = Number.parseInt(answer, 10);
|
|
46
|
+
if (Number.isInteger(byNumber) && byNumber >= 1 && byNumber <= themes.length) {
|
|
47
|
+
return themes[byNumber - 1];
|
|
48
|
+
}
|
|
49
|
+
const bySlug = themes.find((t) => t.slug === answer || t.name === answer);
|
|
50
|
+
if (bySlug)
|
|
51
|
+
return bySlug;
|
|
52
|
+
throw new Error(`No theme matches "${answer}".`);
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
rl.close();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function pullOne(api, subdomain, apiBaseUrl, published) {
|
|
59
|
+
console.log(`\nPulling "${published.name}"...`);
|
|
60
|
+
const draft = await api.request(`/api/v1/themes/${published.id}/draft`, {
|
|
61
|
+
method: "POST",
|
|
62
|
+
});
|
|
63
|
+
const listing = await api.request(`/api/v1/themes/${draft.id}/templates`);
|
|
64
|
+
const targetDir = path.resolve(process.cwd(), `${subdomain}-themes`, published.slug);
|
|
65
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
66
|
+
const manifestEntries = [];
|
|
67
|
+
for (const summary of listing.templates) {
|
|
68
|
+
const detail = await api.request(`/api/v1/themes/${draft.id}/templates/${summary.category}/${encodeURIComponent(summary.template_key)}`);
|
|
69
|
+
const relPath = templateToPath({ category: summary.category, templateKey: summary.template_key });
|
|
70
|
+
const absPath = path.join(targetDir, relPath);
|
|
71
|
+
await fs.mkdir(path.dirname(absPath), { recursive: true });
|
|
72
|
+
const content = detail.html_content ?? "";
|
|
73
|
+
await fs.writeFile(absPath, content);
|
|
74
|
+
manifestEntries.push({
|
|
75
|
+
category: summary.category,
|
|
76
|
+
templateKey: summary.template_key,
|
|
77
|
+
checksum: checksum(content),
|
|
78
|
+
});
|
|
79
|
+
console.log(` ${relPath}`);
|
|
80
|
+
}
|
|
81
|
+
const manifest = {
|
|
82
|
+
draftId: draft.id,
|
|
83
|
+
publishedThemeId: published.id,
|
|
84
|
+
themeName: published.name,
|
|
85
|
+
themeSlug: published.slug,
|
|
86
|
+
apiBaseUrl: apiBaseUrl,
|
|
87
|
+
subdomain,
|
|
88
|
+
pulledAt: new Date().toISOString(),
|
|
89
|
+
templates: manifestEntries,
|
|
90
|
+
};
|
|
91
|
+
await fs.mkdir(path.join(targetDir, MANIFEST_DIR), { recursive: true });
|
|
92
|
+
await fs.writeFile(path.join(targetDir, MANIFEST_DIR, MANIFEST_FILENAME), JSON.stringify(manifest, null, 2));
|
|
93
|
+
console.log(`✓ Pulled ${listing.templates.length} templates to ${targetDir}`);
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=pull.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pull.js","sourceRoot":"","sources":["../../src/commands/pull.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { loadCredentials } from "../lib/config.js";
|
|
4
|
+
import { MANIFEST_DIR, MANIFEST_FILENAME, pathToTemplate, templateToPath, } from "../lib/paths.js";
|
|
5
|
+
import { checksum } from "../lib/manifest.js";
|
|
6
|
+
export async function push(themeDir) {
|
|
7
|
+
const dir = themeDir ?? process.cwd();
|
|
8
|
+
const manifestPath = path.join(dir, MANIFEST_DIR, MANIFEST_FILENAME);
|
|
9
|
+
const manifestRaw = await fs.readFile(manifestPath, "utf8");
|
|
10
|
+
const manifest = JSON.parse(manifestRaw);
|
|
11
|
+
const creds = await loadCredentials(manifest.subdomain);
|
|
12
|
+
const api = new (await import("../lib/api.js")).CobberApi(manifest.apiBaseUrl, creds.accessToken);
|
|
13
|
+
const reviewUrl = manifest.publishedThemeId
|
|
14
|
+
? `${manifest.apiBaseUrl.replace(/\/$/, "")}/dashboard/admin/themes/${manifest.publishedThemeId}/draft`
|
|
15
|
+
: null;
|
|
16
|
+
const result = { changed: [], reviewUrl };
|
|
17
|
+
for (const entry of manifest.templates) {
|
|
18
|
+
const relPath = templateToPath({ category: entry.category, templateKey: entry.templateKey });
|
|
19
|
+
const absPath = path.join(dir, relPath);
|
|
20
|
+
let content;
|
|
21
|
+
try {
|
|
22
|
+
content = await fs.readFile(absPath, "utf8");
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
if (err.code === "ENOENT") {
|
|
26
|
+
// Local file deleted — remove from server
|
|
27
|
+
await api.request(`/api/v1/themes/${manifest.draftId}/templates/${entry.category}/${encodeURIComponent(entry.templateKey)}`, { method: "DELETE" });
|
|
28
|
+
result.changed.push(`- ${relPath}`);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
if (checksum(content) === entry.checksum)
|
|
34
|
+
continue;
|
|
35
|
+
await api.request(`/api/v1/themes/${manifest.draftId}/templates/${entry.category}/${encodeURIComponent(entry.templateKey)}`, { method: "PUT", body: { html_content: content } });
|
|
36
|
+
entry.checksum = checksum(content);
|
|
37
|
+
result.changed.push(`M ${relPath}`);
|
|
38
|
+
}
|
|
39
|
+
// Discover NEW files not yet in manifest
|
|
40
|
+
await collectNewFiles(dir, manifest, async (relPath, content) => {
|
|
41
|
+
const tp = pathToTemplate(relPath);
|
|
42
|
+
if (!tp)
|
|
43
|
+
return;
|
|
44
|
+
await api.request(`/api/v1/themes/${manifest.draftId}/templates/${tp.category}/${encodeURIComponent(tp.templateKey)}`, { method: "PUT", body: { html_content: content } });
|
|
45
|
+
manifest.templates.push({
|
|
46
|
+
category: tp.category,
|
|
47
|
+
templateKey: tp.templateKey,
|
|
48
|
+
checksum: checksum(content),
|
|
49
|
+
});
|
|
50
|
+
result.changed.push(`+ ${relPath}`);
|
|
51
|
+
});
|
|
52
|
+
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
async function collectNewFiles(dir, manifest, visit) {
|
|
56
|
+
const subdirs = ["page_layouts", "site_layouts", "snippets", "widget_partials"];
|
|
57
|
+
const knownKeys = new Set(manifest.templates.map((t) => `${t.category}/${t.templateKey}`));
|
|
58
|
+
for (const sd of subdirs) {
|
|
59
|
+
const subPath = path.join(dir, sd);
|
|
60
|
+
let entries;
|
|
61
|
+
try {
|
|
62
|
+
entries = await fs.readdir(subPath);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
if (err.code === "ENOENT")
|
|
66
|
+
continue;
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
if (!entry.endsWith(".liquid"))
|
|
71
|
+
continue;
|
|
72
|
+
const relPath = `${sd}/${entry}`;
|
|
73
|
+
const tp = pathToTemplate(relPath);
|
|
74
|
+
if (!tp)
|
|
75
|
+
continue;
|
|
76
|
+
if (knownKeys.has(`${tp.category}/${tp.templateKey}`))
|
|
77
|
+
continue;
|
|
78
|
+
const content = await fs.readFile(path.join(dir, relPath), "utf8");
|
|
79
|
+
await visit(relPath, content);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=push.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"push.js","sourceRoot":"","sources":["../../src/commands/push.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { login } from "./commands/login.js";
|
|
4
|
+
import { pull } from "./commands/pull.js";
|
|
5
|
+
import { push } from "./commands/push.js";
|
|
6
|
+
import { dev } from "./commands/dev.js";
|
|
7
|
+
const program = new Command();
|
|
8
|
+
program
|
|
9
|
+
.name("cobber")
|
|
10
|
+
.description("Local theme development for Cobber")
|
|
11
|
+
.version("0.1.0");
|
|
12
|
+
program
|
|
13
|
+
.command("login")
|
|
14
|
+
.description("Authenticate this device with a Cobber organisation")
|
|
15
|
+
.option("--api-base-url <url>", "Override the API base URL", process.env.COBBER_API_URL)
|
|
16
|
+
.action(async (opts) => {
|
|
17
|
+
if (!opts.apiBaseUrl) {
|
|
18
|
+
console.error("Provide --api-base-url or set COBBER_API_URL");
|
|
19
|
+
process.exitCode = 1;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
await login({ apiBaseUrl: opts.apiBaseUrl });
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
console.error(err.message);
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
const theme = program.command("theme").description("Theme commands");
|
|
31
|
+
theme
|
|
32
|
+
.command("pull [theme]")
|
|
33
|
+
.description("Download a draft theme. With no argument, prompts to pick one (or use --all).")
|
|
34
|
+
.option("--all", "Pull every published theme into <subdomain>-themes/<slug>/")
|
|
35
|
+
.action(async (themeIdentifier, opts) => {
|
|
36
|
+
try {
|
|
37
|
+
await pull({ themeIdentifier, all: opts.all });
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
console.error(err.message);
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
theme
|
|
45
|
+
.command("dev")
|
|
46
|
+
.description("Watch the local theme directory and sync changes on save")
|
|
47
|
+
.action(async () => {
|
|
48
|
+
try {
|
|
49
|
+
await dev();
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
console.error(err.message);
|
|
53
|
+
process.exitCode = 1;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
theme
|
|
57
|
+
.command("push")
|
|
58
|
+
.description("Push all local changes to the draft theme")
|
|
59
|
+
.action(async () => {
|
|
60
|
+
try {
|
|
61
|
+
const result = await push();
|
|
62
|
+
if (result.changed.length === 0) {
|
|
63
|
+
console.log("Nothing to push.");
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
result.changed.forEach((line) => console.log(` ${line}`));
|
|
67
|
+
console.log(`\n✓ Pushed ${result.changed.length} template change(s).`);
|
|
68
|
+
}
|
|
69
|
+
if (result.reviewUrl) {
|
|
70
|
+
console.log(`\nReview your draft here → ${result.reviewUrl}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
console.error(err.message);
|
|
75
|
+
process.exitCode = 1;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
program.parseAsync(process.argv);
|
|
79
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/api.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { request as httpRequest } from "node:http";
|
|
2
|
+
import { request as httpsRequest } from "node:https";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
const pkg = createRequire(import.meta.url)("../../package.json");
|
|
5
|
+
const CLIENT_ID = `${pkg.name}/${pkg.version}`;
|
|
6
|
+
export class ApiError extends Error {
|
|
7
|
+
status;
|
|
8
|
+
body;
|
|
9
|
+
constructor(status, message, body) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.status = status;
|
|
12
|
+
this.body = body;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class CobberApi {
|
|
16
|
+
baseUrl;
|
|
17
|
+
accessToken;
|
|
18
|
+
constructor(baseUrl, accessToken) {
|
|
19
|
+
this.baseUrl = baseUrl;
|
|
20
|
+
this.accessToken = accessToken;
|
|
21
|
+
}
|
|
22
|
+
static fromCredentials(creds) {
|
|
23
|
+
return new CobberApi(creds.apiBaseUrl, creds.accessToken);
|
|
24
|
+
}
|
|
25
|
+
async request(path, opts = {}) {
|
|
26
|
+
const url = new URL(`${this.baseUrl.replace(/\/$/, "")}${path}`);
|
|
27
|
+
const headers = {
|
|
28
|
+
"Accept": "application/json",
|
|
29
|
+
"User-Agent": CLIENT_ID,
|
|
30
|
+
"X-Cobber-Client": CLIENT_ID,
|
|
31
|
+
...(opts.headers ?? {}),
|
|
32
|
+
};
|
|
33
|
+
if (this.accessToken)
|
|
34
|
+
headers["Authorization"] = `Bearer ${this.accessToken}`;
|
|
35
|
+
if (opts.body !== undefined)
|
|
36
|
+
headers["Content-Type"] = "application/json";
|
|
37
|
+
// Node can't resolve `*.localhost` like browsers/curl can, so for those
|
|
38
|
+
// hosts we connect to 127.0.0.1 directly and pass the real Host header
|
|
39
|
+
// through. We use node:http because fetch silently strips the Host header.
|
|
40
|
+
const connectHost = url.hostname.endsWith(".localhost") && url.hostname !== "localhost"
|
|
41
|
+
? "127.0.0.1"
|
|
42
|
+
: url.hostname;
|
|
43
|
+
const body = opts.body !== undefined ? JSON.stringify(opts.body) : undefined;
|
|
44
|
+
if (body !== undefined)
|
|
45
|
+
headers["Content-Length"] = Buffer.byteLength(body).toString();
|
|
46
|
+
const { status, body: responseBody } = await rawRequest({
|
|
47
|
+
url,
|
|
48
|
+
connectHost,
|
|
49
|
+
method: opts.method ?? "GET",
|
|
50
|
+
headers,
|
|
51
|
+
body,
|
|
52
|
+
});
|
|
53
|
+
const parsed = responseBody
|
|
54
|
+
? (() => { try {
|
|
55
|
+
return JSON.parse(responseBody);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return responseBody;
|
|
59
|
+
} })()
|
|
60
|
+
: null;
|
|
61
|
+
if (status < 200 || status >= 300) {
|
|
62
|
+
throw new ApiError(status, `HTTP ${status}`, parsed);
|
|
63
|
+
}
|
|
64
|
+
return parsed;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function rawRequest(opts) {
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const lib = opts.url.protocol === "https:" ? httpsRequest : httpRequest;
|
|
70
|
+
const port = opts.url.port || (opts.url.protocol === "https:" ? 443 : 80);
|
|
71
|
+
const req = lib({
|
|
72
|
+
host: opts.connectHost,
|
|
73
|
+
port: Number(port),
|
|
74
|
+
path: `${opts.url.pathname}${opts.url.search}`,
|
|
75
|
+
method: opts.method,
|
|
76
|
+
headers: { ...opts.headers, Host: opts.url.host },
|
|
77
|
+
}, (res) => {
|
|
78
|
+
let data = "";
|
|
79
|
+
res.setEncoding("utf8");
|
|
80
|
+
res.on("data", (chunk) => (data += chunk));
|
|
81
|
+
res.on("end", () => resolve({ status: res.statusCode ?? 0, body: data }));
|
|
82
|
+
});
|
|
83
|
+
req.on("error", reject);
|
|
84
|
+
if (opts.body)
|
|
85
|
+
req.write(opts.body);
|
|
86
|
+
req.end();
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const CONFIG_DIR = path.join(homedir(), ".cobber");
|
|
5
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
6
|
+
export async function readConfig() {
|
|
7
|
+
try {
|
|
8
|
+
const raw = await fs.readFile(CONFIG_FILE, "utf8");
|
|
9
|
+
return JSON.parse(raw);
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
if (err.code === "ENOENT")
|
|
13
|
+
return { orgs: {} };
|
|
14
|
+
throw err;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function writeConfig(config) {
|
|
18
|
+
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
|
19
|
+
await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
20
|
+
}
|
|
21
|
+
export async function saveCredentials(subdomain, creds) {
|
|
22
|
+
const config = await readConfig();
|
|
23
|
+
config.orgs[subdomain] = creds;
|
|
24
|
+
config.default = config.default ?? subdomain;
|
|
25
|
+
await writeConfig(config);
|
|
26
|
+
}
|
|
27
|
+
export async function loadCredentials(subdomain) {
|
|
28
|
+
const config = await readConfig();
|
|
29
|
+
const target = subdomain ?? config.default;
|
|
30
|
+
if (!target) {
|
|
31
|
+
throw new Error("Not logged in. Run `cobber login` first.");
|
|
32
|
+
}
|
|
33
|
+
const creds = config.orgs[target];
|
|
34
|
+
if (!creds) {
|
|
35
|
+
throw new Error(`No credentials for "${target}". Run \`cobber login\` first.`);
|
|
36
|
+
}
|
|
37
|
+
return creds;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAc7B,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;AACnD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAEzD,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAiB,CAAC;IACzC,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;QAC/C,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAoB;IACpD,MAAM,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,MAAM,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACpF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,SAAiB,EAAE,KAAwB;IAC/E,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,KAAK,CAAC;IAC/B,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,SAAS,CAAC;IAC7C,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,SAAkB;IACtD,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,MAAM,MAAM,GAAG,SAAS,IAAI,MAAM,CAAC,OAAO,CAAC;IAC3C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC9D,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,uBAAuB,MAAM,gCAAgC,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
export function checksum(content) {
|
|
3
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
4
|
+
}
|
|
5
|
+
export function findEntry(manifest, template) {
|
|
6
|
+
return manifest.templates.find((e) => e.category === template.category && e.templateKey === template.templateKey);
|
|
7
|
+
}
|
|
8
|
+
export function upsertEntry(manifest, entry) {
|
|
9
|
+
const others = manifest.templates.filter((e) => !(e.category === entry.category && e.templateKey === entry.templateKey));
|
|
10
|
+
return { ...manifest, templates: [...others, entry] };
|
|
11
|
+
}
|
|
12
|
+
export function hasChanged(manifest, template) {
|
|
13
|
+
const entry = findEntry(manifest, template);
|
|
14
|
+
if (!entry)
|
|
15
|
+
return true;
|
|
16
|
+
return entry.checksum !== checksum(template.content);
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/lib/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAmBzC,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,QAAkB,EAClB,QAAmD;IAEnD,OAAO,QAAQ,CAAC,SAAS,CAAC,IAAI,CAC5B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,QAAQ,IAAI,CAAC,CAAC,WAAW,KAAK,QAAQ,CAAC,WAAW,CAClF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,QAAkB,EAAE,KAAoB;IAClE,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,MAAM,CACtC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ,IAAI,CAAC,CAAC,WAAW,KAAK,KAAK,CAAC,WAAW,CAAC,CAC/E,CAAC;IACF,OAAO,EAAE,GAAG,QAAQ,EAAE,SAAS,EAAE,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,QAAkB,EAClB,QAAoE;IAEpE,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AACvD,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Mapping between a server-side ContentTemplate (category + template_key) and
|
|
2
|
+
// the file path inside the local theme directory.
|
|
3
|
+
//
|
|
4
|
+
// page_layout → page_layouts/<template_key>.liquid
|
|
5
|
+
// site_layout → site_layouts/<template_key>.liquid
|
|
6
|
+
// snippet → snippets/<template_key>.liquid
|
|
7
|
+
// widget_partial → widget_partials/<template_key>.liquid
|
|
8
|
+
const CATEGORY_TO_DIR = {
|
|
9
|
+
page_layout: "page_layouts",
|
|
10
|
+
site_layout: "site_layouts",
|
|
11
|
+
snippet: "snippets",
|
|
12
|
+
widget_partial: "widget_partials",
|
|
13
|
+
};
|
|
14
|
+
const DIR_TO_CATEGORY = Object.fromEntries(Object.entries(CATEGORY_TO_DIR).map(([cat, dir]) => [dir, cat]));
|
|
15
|
+
export function templateToPath({ category, templateKey }) {
|
|
16
|
+
const dir = CATEGORY_TO_DIR[category];
|
|
17
|
+
if (!dir)
|
|
18
|
+
throw new Error(`Unknown template category: ${category}`);
|
|
19
|
+
return `${dir}/${templateKey}.liquid`;
|
|
20
|
+
}
|
|
21
|
+
export function pathToTemplate(relPath) {
|
|
22
|
+
// Normalise leading "./" or platform separator
|
|
23
|
+
const cleaned = relPath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
24
|
+
const [dir, filename] = cleaned.split("/", 2);
|
|
25
|
+
if (!dir || !filename)
|
|
26
|
+
return null;
|
|
27
|
+
const category = DIR_TO_CATEGORY[dir];
|
|
28
|
+
if (!category)
|
|
29
|
+
return null;
|
|
30
|
+
if (!filename.endsWith(".liquid"))
|
|
31
|
+
return null;
|
|
32
|
+
return { category, templateKey: filename.slice(0, -".liquid".length) };
|
|
33
|
+
}
|
|
34
|
+
export const MANIFEST_DIR = ".cobber";
|
|
35
|
+
export const MANIFEST_FILENAME = "manifest.json";
|
|
36
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,kDAAkD;AAClD,EAAE;AACF,wDAAwD;AACxD,wDAAwD;AACxD,oDAAoD;AACpD,2DAA2D;AAE3D,MAAM,eAAe,GAA2B;IAC9C,WAAW,EAAE,cAAc;IAC3B,WAAW,EAAE,cAAc;IAC3B,OAAO,EAAE,UAAU;IACnB,cAAc,EAAE,iBAAiB;CAClC,CAAC;AAEF,MAAM,eAAe,GAA2B,MAAM,CAAC,WAAW,CAChE,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAChE,CAAC;AAOF,MAAM,UAAU,cAAc,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAgB;IACpE,MAAM,GAAG,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IACtC,IAAI,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,QAAQ,EAAE,CAAC,CAAC;IACpE,OAAO,GAAG,GAAG,IAAI,WAAW,SAAS,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,+CAA+C;IAC/C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IACjE,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAC9C,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,QAAQ,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/C,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;AACzE,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,SAAS,CAAC;AACtC,MAAM,CAAC,MAAM,iBAAiB,GAAG,eAAe,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cobberhq/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Local theme development CLI for Cobber",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"cobber": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"dev": "tsc --watch",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"prepublishOnly": "npm run build && npm test"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=20"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"chokidar": "^3.6.0",
|
|
24
|
+
"commander": "^12.1.0",
|
|
25
|
+
"open": "^10.1.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^20.14.0",
|
|
29
|
+
"typescript": "^5.4.5",
|
|
30
|
+
"vitest": "^1.6.0"
|
|
31
|
+
}
|
|
32
|
+
}
|