goosetools-worker 0.2.5 → 0.2.6
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/package.json +1 -1
- package/src/render.js +3 -1
- package/src/state.js +55 -0
- package/worker/index.js +12 -3
- package/worker/style.js +6 -5
package/package.json
CHANGED
package/src/render.js
CHANGED
|
@@ -5,6 +5,7 @@ import yaml from "js-yaml";
|
|
|
5
5
|
import { chromium } from "playwright";
|
|
6
6
|
import { FONT_SPECS, customFontFaces, sanitizeFonts } from "./fonts.js";
|
|
7
7
|
import { assertFfmpeg, composite, posterFrame } from "./video.js";
|
|
8
|
+
import { styleDirFor } from "./state.js";
|
|
8
9
|
|
|
9
10
|
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
11
|
|
|
@@ -39,7 +40,8 @@ const spec = yaml.load(readFileSync(join(dir, "slides.yml"), "utf8"));
|
|
|
39
40
|
|
|
40
41
|
// resolve active style: slides.yml `style:` > brand.yml defaultStyle
|
|
41
42
|
const styleName = spec.style ?? brandBase.defaultStyle;
|
|
42
|
-
|
|
43
|
+
// Built-ins ship in the package; everything else is per-machine (state.js).
|
|
44
|
+
const styleDir = styleDirFor(styleName);
|
|
43
45
|
if (!existsSync(styleDir)) {
|
|
44
46
|
console.error(`Unknown style "${styleName}" — expected ${styleDir}`);
|
|
45
47
|
process.exit(1);
|
package/src/state.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Where the Carousel Maker's per-machine work lives.
|
|
2
|
+
//
|
|
3
|
+
// Generated styles and rendered decks used to be written inside the package
|
|
4
|
+
// folder (styles/<slug>, carousels/web-*). Installed from npm, that folder is
|
|
5
|
+
// ~/.goosetools/app/node_modules/goosetools-worker, which `update` deletes and
|
|
6
|
+
// reinstalls — so every update silently deleted every style the user had
|
|
7
|
+
// generated (it happened on 2026-09-26). They now live in ~/.goosetools/carousel,
|
|
8
|
+
// which nothing reinstalls. The built-in styles and the demo deck still ship
|
|
9
|
+
// in the package, because they ARE the package.
|
|
10
|
+
|
|
11
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { dirname, join, resolve } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
17
|
+
|
|
18
|
+
/** Styles that ship in the package (see package.json `files`). */
|
|
19
|
+
export const SHIPPED_STYLES = ["graph-paper", "polka-dot"];
|
|
20
|
+
|
|
21
|
+
export const STATE_DIR = process.env.GOOSETOOLS_CAROUSEL_DIR ?? join(homedir(), ".goosetools", "carousel");
|
|
22
|
+
export const STYLES_DIR = join(STATE_DIR, "styles");
|
|
23
|
+
/** Per-job work folders: the deck, its photos, its rendered slides. */
|
|
24
|
+
export const WORK_DIR = join(STATE_DIR, "carousels");
|
|
25
|
+
|
|
26
|
+
/** Where a style's folder is: in the package if it ships, otherwise in state. */
|
|
27
|
+
export function styleDirFor(slug) {
|
|
28
|
+
return SHIPPED_STYLES.includes(slug) ? join(ROOT, "styles", slug) : join(STYLES_DIR, slug);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Move any style that isn't a built-in out of the package folder.
|
|
33
|
+
*
|
|
34
|
+
* Only a checkout still has them there (an npm install lost them when it was
|
|
35
|
+
* replaced), but a checkout is exactly where a style made during development
|
|
36
|
+
* sits — so this runs on every start and is a no-op once done. A style
|
|
37
|
+
* already in state wins; the package copy is then just removed.
|
|
38
|
+
*/
|
|
39
|
+
export function migrateStylesOutOfPackage() {
|
|
40
|
+
const pkgStyles = join(ROOT, "styles");
|
|
41
|
+
if (!existsSync(pkgStyles)) return 0;
|
|
42
|
+
mkdirSync(STYLES_DIR, { recursive: true });
|
|
43
|
+
let moved = 0;
|
|
44
|
+
for (const d of readdirSync(pkgStyles, { withFileTypes: true })) {
|
|
45
|
+
if (!d.isDirectory() || SHIPPED_STYLES.includes(d.name)) continue;
|
|
46
|
+
const from = join(pkgStyles, d.name);
|
|
47
|
+
const to = join(STYLES_DIR, d.name);
|
|
48
|
+
if (!existsSync(to)) {
|
|
49
|
+
cpSync(from, to, { recursive: true });
|
|
50
|
+
moved++;
|
|
51
|
+
}
|
|
52
|
+
rmSync(from, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
return moved;
|
|
55
|
+
}
|
package/worker/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import { promisify } from "node:util";
|
|
|
13
13
|
import yaml from "js-yaml";
|
|
14
14
|
import { upload } from "./upload.js";
|
|
15
15
|
import { skillSection } from "./skill.js";
|
|
16
|
+
import { migrateStylesOutOfPackage, STYLES_DIR, styleDirFor, WORK_DIR } from "../src/state.js";
|
|
16
17
|
import { extFromUrl } from "./style.js";
|
|
17
18
|
import { FONT_SPECS, sanitizeFonts } from "../src/fonts.js";
|
|
18
19
|
import { assertFfmpeg, isVideoPath, posterFrame } from "../src/video.js";
|
|
@@ -120,7 +121,7 @@ const MAX_PROMPT_REFS = 6;
|
|
|
120
121
|
async function ensureStyleRefs(styleSlug, styleRefUrls) {
|
|
121
122
|
const urls = (styleRefUrls ?? []).slice(0, 20);
|
|
122
123
|
if (urls.length === 0) return [];
|
|
123
|
-
const refsDir = join(
|
|
124
|
+
const refsDir = join(styleDirFor(styleSlug), "refs");
|
|
124
125
|
const manifestPath = join(refsDir, "manifest.json");
|
|
125
126
|
|
|
126
127
|
const cachedPaths = () => {
|
|
@@ -485,10 +486,10 @@ async function draftSlides(job, styleDir, media, refPaths, brand) {
|
|
|
485
486
|
async function runJob(job) {
|
|
486
487
|
console.log(`▶ job ${job.id}: "${(job.prompt ?? "").slice(0, 60)}" [${job.styleSlug}]`);
|
|
487
488
|
|
|
488
|
-
const styleDir =
|
|
489
|
+
const styleDir = styleDirFor(job.styleSlug);
|
|
489
490
|
if (!existsSync(styleDir)) throw new Error(`Style "${job.styleSlug}" isn't installed on this computer yet`);
|
|
490
491
|
|
|
491
|
-
const dir = join(
|
|
492
|
+
const dir = join(WORK_DIR, `web-${job.id.slice(0, 8)}`);
|
|
492
493
|
mkdirSync(join(dir, "photos"), { recursive: true });
|
|
493
494
|
mkdirSync(join(dir, "videos"), { recursive: true });
|
|
494
495
|
// Older servers don't send these — default to applying the brand, matching
|
|
@@ -699,6 +700,14 @@ Do not rewrite any slide's text and do not switch templates.`;
|
|
|
699
700
|
|
|
700
701
|
// ── Main loop ────────────────────────────────────────────────────────────────
|
|
701
702
|
console.log(`Goose Tools worker connected to ${BASE_URL} — waiting for jobs (Ctrl+C to stop)`);
|
|
703
|
+
// Styles made from a checkout sat inside the package folder; move them to
|
|
704
|
+
// ~/.goosetools/carousel so an update can't delete them (src/state.js).
|
|
705
|
+
try {
|
|
706
|
+
const moved = migrateStylesOutOfPackage();
|
|
707
|
+
if (moved) console.log(`Moved ${moved} style(s) to ${STYLES_DIR}.`);
|
|
708
|
+
} catch (e) {
|
|
709
|
+
console.error(`Couldn't move styles out of the package: ${e.message}`);
|
|
710
|
+
}
|
|
702
711
|
let firstPoll = true;
|
|
703
712
|
|
|
704
713
|
while (true) {
|
package/worker/style.js
CHANGED
|
@@ -17,6 +17,7 @@ import { extname, join } from "node:path";
|
|
|
17
17
|
import yaml from "js-yaml";
|
|
18
18
|
import { upload } from "./upload.js";
|
|
19
19
|
import { skillSection } from "./skill.js";
|
|
20
|
+
import { styleDirFor, WORK_DIR } from "../src/state.js";
|
|
20
21
|
|
|
21
22
|
const RESERVED_SLUGS = new Set(["graph-paper", "polka-dot"]);
|
|
22
23
|
const REQUIRED_TEMPLATES = ["cover", "text", "photo", "cta"];
|
|
@@ -137,13 +138,13 @@ export async function runStyleJob(job, { api, execFileAsync, ROOT, TOKEN, BASE_U
|
|
|
137
138
|
console.log(`▶ style ${job.id}: "${name}" (${refUrls.length} refs) [${slug}]`);
|
|
138
139
|
|
|
139
140
|
if (RESERVED_SLUGS.has(slug)) throw new Error(`"${slug}" is a built-in style`);
|
|
140
|
-
const styleDir =
|
|
141
|
+
const styleDir = styleDirFor(slug);
|
|
141
142
|
const marker = join(styleDir, ".generated");
|
|
142
143
|
if (existsSync(styleDir) && !existsSync(marker)) {
|
|
143
|
-
throw new Error(`A hand-made style already exists at
|
|
144
|
+
throw new Error(`A hand-made style already exists at ${styleDir} on this computer`);
|
|
144
145
|
}
|
|
145
146
|
|
|
146
|
-
const workDir = join(
|
|
147
|
+
const workDir = join(WORK_DIR, `style-${job.id.slice(0, 8)}`);
|
|
147
148
|
const refsDir = join(workDir, "refs");
|
|
148
149
|
mkdirSync(refsDir, { recursive: true });
|
|
149
150
|
|
|
@@ -157,7 +158,7 @@ export async function runStyleJob(job, { api, execFileAsync, ROOT, TOKEN, BASE_U
|
|
|
157
158
|
refPaths.push(p);
|
|
158
159
|
}
|
|
159
160
|
|
|
160
|
-
const exemplarDir =
|
|
161
|
+
const exemplarDir = styleDirFor("graph-paper");
|
|
161
162
|
const exemplar = {
|
|
162
163
|
styleYml: readFileSync(join(exemplarDir, "style.yml"), "utf8"),
|
|
163
164
|
cover: readFileSync(join(exemplarDir, "templates", "cover.html"), "utf8"),
|
|
@@ -207,7 +208,7 @@ export async function runStyleJob(job, { api, execFileAsync, ROOT, TOKEN, BASE_U
|
|
|
207
208
|
join(styleDir, "refs", "manifest.json"),
|
|
208
209
|
JSON.stringify({ urls: refUrls.slice(0, 20) }, null, 2),
|
|
209
210
|
);
|
|
210
|
-
console.log(`
|
|
211
|
+
console.log(` ${styleDir} written (${Object.keys(files).length} files + ${refPaths.length} refs)`);
|
|
211
212
|
|
|
212
213
|
// Preview: render the new style's cover + text slide with the user's brand.
|
|
213
214
|
const previewDir = join(workDir, "preview");
|