@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 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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"}
@@ -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
+ }