storyshelf 0.3.2 → 0.5.4
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 +2 -1
- package/dist/client.js +20 -5
- package/dist/commands/build.js +31 -0
- package/dist/commands/connection.js +29 -0
- package/dist/commands/create.js +1 -1
- package/dist/commands/doctor.js +150 -0
- package/dist/commands/init.js +2 -2
- package/dist/commands/purge.js +1 -1
- package/dist/commands/retry.js +1 -1
- package/dist/commands/server/docker.js +76 -11
- package/dist/commands/server/init.js +495 -45
- package/dist/commands/server/prompts.js +410 -35
- package/dist/commands/server/serve.js +123 -0
- package/dist/commands/server/terraform-aws.js +458 -0
- package/dist/commands/server/terraform-azure.js +462 -0
- package/dist/commands/server/terraform-gcp.js +475 -0
- package/dist/commands/shared/detect-adapters.js +104 -0
- package/dist/commands/storybook-build.js +62 -0
- package/dist/commands/upload.js +106 -50
- package/dist/commands/whoami.js +22 -0
- package/dist/commands/worker/init.js +254 -0
- package/dist/commands/worker/serve.js +105 -0
- package/dist/config.js +396 -23
- package/dist/index.js +49 -17
- package/dist/output.js +10 -0
- package/package.json +21 -13
- package/schema/storyshelf-config.json +58 -0
package/README.md
CHANGED
|
@@ -99,7 +99,8 @@ jobs:
|
|
|
99
99
|
with: { node-version: 20 }
|
|
100
100
|
- run: nub install && nub run build
|
|
101
101
|
- run: nubx storybook build -o storybook-static
|
|
102
|
-
- run:
|
|
102
|
+
- run:
|
|
103
|
+
nubx storyshelf upload \
|
|
103
104
|
--token "${{ secrets.STORYSHELF_TOKEN }}" \
|
|
104
105
|
--sha "${{ github.sha }}" \
|
|
105
106
|
--branch "${{ github.ref_name }}"
|
package/dist/client.js
CHANGED
|
@@ -3,6 +3,21 @@ import "node:path";
|
|
|
3
3
|
import "node:url";
|
|
4
4
|
import.meta.url;
|
|
5
5
|
//#region src/client.ts
|
|
6
|
+
/**
|
|
7
|
+
* Create a typed API client for a StoryShelf server.
|
|
8
|
+
*
|
|
9
|
+
* Wraps `fetch` with bearer auth and JSON helpers so commands can call
|
|
10
|
+
* `client.projects.builds.createJson(...)` without hand-rolling URLs or headers.
|
|
11
|
+
*
|
|
12
|
+
* @param baseUrl - Server origin, e.g. `https://shelf.example.com`
|
|
13
|
+
* @param token - Optional bearer token (CI or admin token); sent as `Authorization: Bearer <token>`
|
|
14
|
+
* @returns Namespaced client with `projects`, `builds`, `tokens`, and `admin` helpers
|
|
15
|
+
*/
|
|
16
|
+
function createClient(baseUrl, token) {
|
|
17
|
+
const authHeaders = buildAuthHeaders(token);
|
|
18
|
+
const requestHeaders = (contentType) => buildRequestHeaders(authHeaders, contentType);
|
|
19
|
+
return { projects: createProjectsApi(baseUrl, authHeaders, requestHeaders) };
|
|
20
|
+
}
|
|
6
21
|
function buildAuthHeaders(token) {
|
|
7
22
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
8
23
|
}
|
|
@@ -48,6 +63,11 @@ function createBuildsApi(baseUrl, authHeaders, requestHeaders) {
|
|
|
48
63
|
method: "POST",
|
|
49
64
|
headers: requestHeaders("application/json"),
|
|
50
65
|
body: JSON.stringify({})
|
|
66
|
+
}),
|
|
67
|
+
postAffected: async (slug, buildId, json) => await fetchJson(`${baseUrl}/api/v1/projects/${slug}/builds/${buildId}/affected`, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: requestHeaders("application/json"),
|
|
70
|
+
body: JSON.stringify(json)
|
|
51
71
|
})
|
|
52
72
|
};
|
|
53
73
|
}
|
|
@@ -77,10 +97,5 @@ function createProjectsApi(baseUrl, authHeaders, requestHeaders) {
|
|
|
77
97
|
admin: createAdminApi(baseUrl, requestHeaders)
|
|
78
98
|
};
|
|
79
99
|
}
|
|
80
|
-
function createClient(baseUrl, token) {
|
|
81
|
-
const authHeaders = buildAuthHeaders(token);
|
|
82
|
-
const requestHeaders = (contentType) => buildRequestHeaders(authHeaders, contentType);
|
|
83
|
-
return { projects: createProjectsApi(baseUrl, authHeaders, requestHeaders) };
|
|
84
|
-
}
|
|
85
100
|
//#endregion
|
|
86
101
|
export { createClient };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "node:path";
|
|
3
|
+
import "node:url";
|
|
4
|
+
import.meta.url;
|
|
5
|
+
import { loadStorybookConfig } from "../config.js";
|
|
6
|
+
import { printLine } from "../output.js";
|
|
7
|
+
import { assertBuildOutput, ensureBuildDir } from "./storybook-build.js";
|
|
8
|
+
import { resolve } from "node:path";
|
|
9
|
+
//#region src/commands/build.ts
|
|
10
|
+
/**
|
|
11
|
+
* Build a Storybook directory without uploading, using the same
|
|
12
|
+
* buildDir/buildCommand/buildScriptName resolution as `upload`.
|
|
13
|
+
*
|
|
14
|
+
* @param options - Build command options.
|
|
15
|
+
*/
|
|
16
|
+
async function runBuild(options) {
|
|
17
|
+
const cwd = options.cwd ?? process.cwd();
|
|
18
|
+
const cfg = await loadStorybookConfig(cwd, options.config);
|
|
19
|
+
const buildDir = options.buildDir ?? cfg?.buildDir ?? "storybook-static";
|
|
20
|
+
await ensureBuildDir({
|
|
21
|
+
cwd,
|
|
22
|
+
buildDir,
|
|
23
|
+
buildCommand: options.buildCommand ?? cfg?.buildCommand,
|
|
24
|
+
buildScriptName: options.buildScriptName ?? cfg?.buildScriptName,
|
|
25
|
+
force: options.forceBuild
|
|
26
|
+
});
|
|
27
|
+
await assertBuildOutput(cwd, buildDir);
|
|
28
|
+
printLine(`Build ready: ${resolve(cwd, buildDir)}`);
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { runBuild };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "node:path";
|
|
3
|
+
import "node:url";
|
|
4
|
+
import.meta.url;
|
|
5
|
+
import { loadStorybookConfig } from "../config.js";
|
|
6
|
+
//#region src/commands/connection.ts
|
|
7
|
+
/**
|
|
8
|
+
* Resolve url/slug/token as flags > config file > env, throwing on the first
|
|
9
|
+
* missing value (same messages as `upload`).
|
|
10
|
+
*/
|
|
11
|
+
async function resolveConnection(options) {
|
|
12
|
+
const cwd = options.cwd ?? process.cwd();
|
|
13
|
+
const cfg = await loadStorybookConfig(cwd, options.config);
|
|
14
|
+
const fields = {
|
|
15
|
+
url: options.url ?? cfg?.url ?? process.env["STORYSHELF_URL"],
|
|
16
|
+
slug: options.slug ?? cfg?.slug ?? process.env["STORYSHELF_SLUG"],
|
|
17
|
+
token: options.token ?? process.env["STORYSHELF_TOKEN"] ?? process.env["SHELF_TOKEN"]
|
|
18
|
+
};
|
|
19
|
+
assertConnectionFields(fields);
|
|
20
|
+
return fields;
|
|
21
|
+
}
|
|
22
|
+
/** Throw on the first missing connection field. */
|
|
23
|
+
function assertConnectionFields(fields) {
|
|
24
|
+
if (!fields.url) throw new Error("--url is required (or .storybook/storyshelf.json / STORYSHELF_URL)");
|
|
25
|
+
if (!fields.slug) throw new Error("--slug is required (or .storybook/storyshelf.json / STORYSHELF_SLUG)");
|
|
26
|
+
if (!fields.token) throw new Error("--token is required (or STORYSHELF_TOKEN env)");
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { resolveConnection };
|
package/dist/commands/create.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import "node:path";
|
|
3
3
|
import "node:url";
|
|
4
4
|
import.meta.url;
|
|
5
|
-
import { createClient } from "../client.js";
|
|
6
5
|
import { assertStorybookMain, detectGitDefaultBranch, detectGitRepository, detectPackageName, detectStorybookMeta, writeStorybookConfig } from "../config.js";
|
|
7
6
|
import { printError, printLine } from "../output.js";
|
|
7
|
+
import { createClient } from "../client.js";
|
|
8
8
|
//#region src/commands/create.ts
|
|
9
9
|
async function readCreateInputs(options, cwd) {
|
|
10
10
|
const name = (options.name === "" ? void 0 : options.name) ?? await detectPackageName(cwd);
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "node:path";
|
|
3
|
+
import "node:url";
|
|
4
|
+
import.meta.url;
|
|
5
|
+
import { findStorybookMain, loadStorybookConfig } from "../config.js";
|
|
6
|
+
import { printLine } from "../output.js";
|
|
7
|
+
import { needsBuild, resolveBuildCommand } from "./storybook-build.js";
|
|
8
|
+
import { createClient } from "../client.js";
|
|
9
|
+
import { resolveConnection } from "./connection.js";
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
import { access, readdir } from "node:fs/promises";
|
|
12
|
+
import { gitRepoStatus } from "@storyshelf/affected";
|
|
13
|
+
//#region src/commands/doctor.ts
|
|
14
|
+
function mark(status) {
|
|
15
|
+
if (status === "pass") return "✓";
|
|
16
|
+
return status === "warn" ? "!" : "✗";
|
|
17
|
+
}
|
|
18
|
+
async function checkSetup(cwd) {
|
|
19
|
+
const main = await findStorybookMain(cwd);
|
|
20
|
+
if (!main) return {
|
|
21
|
+
status: "fail",
|
|
22
|
+
message: "No .storybook/main.* found"
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
status: "pass",
|
|
26
|
+
message: `Storybook setup found (${main})`
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function checkConfigFile(cfg) {
|
|
30
|
+
if (!cfg) return {
|
|
31
|
+
status: "warn",
|
|
32
|
+
message: "No config file — url/slug/token must come from flags or env"
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
status: "pass",
|
|
36
|
+
message: `Config loaded (slug "${cfg.slug}")`
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function serverErrorMessage(error) {
|
|
40
|
+
if (error instanceof TypeError) return "cannot reach server — check --url and network";
|
|
41
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
42
|
+
if (message.includes("(401)")) return "authentication failed (401) — check STORYSHELF_TOKEN";
|
|
43
|
+
if (message.includes("(403)")) return "forbidden (403) — token lacks access to this project";
|
|
44
|
+
if (message.includes("(404)")) return "project not found (404) — check --slug";
|
|
45
|
+
return message;
|
|
46
|
+
}
|
|
47
|
+
async function checkServer(connection) {
|
|
48
|
+
try {
|
|
49
|
+
const project = await createClient(connection.url, connection.token).projects.get(connection.slug);
|
|
50
|
+
return {
|
|
51
|
+
status: "pass",
|
|
52
|
+
message: `Server reachable, project "${typeof project.name === "string" && project.name.length > 0 ? project.name : connection.slug}" readable`
|
|
53
|
+
};
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return {
|
|
56
|
+
status: "fail",
|
|
57
|
+
message: `Server check failed: ${serverErrorMessage(error)}`
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function checkBuildOutput(cwd, inputs) {
|
|
62
|
+
const full = resolve(cwd, inputs.buildDir);
|
|
63
|
+
if (await needsBuild(full)) return {
|
|
64
|
+
status: "warn",
|
|
65
|
+
message: `Build output missing — upload would run: ${await resolveBuildCommand(cwd, inputs.buildDir, inputs.buildCommand, inputs.buildScriptName)}`
|
|
66
|
+
};
|
|
67
|
+
try {
|
|
68
|
+
await access(resolve(full, "index.json"));
|
|
69
|
+
} catch {
|
|
70
|
+
return {
|
|
71
|
+
status: "fail",
|
|
72
|
+
message: `Build output incomplete: no index.json in ${inputs.buildDir} (upload would zip it as-is)`
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const entries = await readdir(full);
|
|
76
|
+
return {
|
|
77
|
+
status: "pass",
|
|
78
|
+
message: `Build output ready: ${inputs.buildDir} (${entries.length} top-level entries)`
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function resolveBuildInputs(options, cfg) {
|
|
82
|
+
return {
|
|
83
|
+
buildDir: options.buildDir ?? cfg?.buildDir ?? "storybook-static",
|
|
84
|
+
buildCommand: options.buildCommand ?? cfg?.buildCommand,
|
|
85
|
+
buildScriptName: options.buildScriptName ?? cfg?.buildScriptName
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Diagnose whether an upload would succeed, without uploading anything.
|
|
90
|
+
* Prints one line per check; sets exit code 1 when any check fails.
|
|
91
|
+
*
|
|
92
|
+
* @param options - Connection and build options (flags, config, or env).
|
|
93
|
+
*/
|
|
94
|
+
async function runDoctor(options) {
|
|
95
|
+
const cwd = options.cwd ?? process.cwd();
|
|
96
|
+
const cfg = await loadStorybookConfig(cwd, options.config);
|
|
97
|
+
const checks = [await checkSetup(cwd), checkConfigFile(cfg)];
|
|
98
|
+
const connection = await tryResolveConnection(options, checks);
|
|
99
|
+
if (connection) checks.push(await checkServer(connection));
|
|
100
|
+
checks.push(await checkBuildOutput(cwd, resolveBuildInputs(options, cfg)), await checkAffected(cwd, resolveBuildInputs(options, cfg).buildDir));
|
|
101
|
+
reportChecks(checks);
|
|
102
|
+
}
|
|
103
|
+
/** Warn when affected capture would silently fall back to a full render. */
|
|
104
|
+
async function checkAffected(cwd, buildDir) {
|
|
105
|
+
const repo = gitRepoStatus(cwd);
|
|
106
|
+
if (repo === "no-git") return {
|
|
107
|
+
status: "warn",
|
|
108
|
+
message: "No git repository — upload uses a synthetic local identity and affected capture renders all stories"
|
|
109
|
+
};
|
|
110
|
+
if (repo === "shallow") return {
|
|
111
|
+
status: "warn",
|
|
112
|
+
message: "Shallow clone detected — affected capture renders all stories (use fetch-depth: 0)"
|
|
113
|
+
};
|
|
114
|
+
try {
|
|
115
|
+
await access(resolve(cwd, buildDir, "preview-stats.json"));
|
|
116
|
+
} catch {
|
|
117
|
+
return {
|
|
118
|
+
status: "warn",
|
|
119
|
+
message: `No preview-stats.json in ${buildDir} — affected capture renders all stories`
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
status: "pass",
|
|
124
|
+
message: "Affected capture ready (history and stats available)"
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/** Resolve the connection, recording success or failure as a check. */
|
|
128
|
+
async function tryResolveConnection(options, checks) {
|
|
129
|
+
try {
|
|
130
|
+
const connection = await resolveConnection(options);
|
|
131
|
+
checks.push({
|
|
132
|
+
status: "pass",
|
|
133
|
+
message: `Connection: ${connection.url} / ${connection.slug} (token present)`
|
|
134
|
+
});
|
|
135
|
+
return connection;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
checks.push({
|
|
138
|
+
status: "fail",
|
|
139
|
+
message: error instanceof Error ? error.message : String(error)
|
|
140
|
+
});
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/** Print all checks; fail the process when any check failed. */
|
|
145
|
+
function reportChecks(checks) {
|
|
146
|
+
for (const check of checks) printLine(`${mark(check.status)} ${check.message}`);
|
|
147
|
+
if (checks.some((check) => check.status === "fail")) process.exitCode = 1;
|
|
148
|
+
}
|
|
149
|
+
//#endregion
|
|
150
|
+
export { runDoctor };
|
package/dist/commands/init.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import "node:path";
|
|
3
3
|
import "node:url";
|
|
4
4
|
import.meta.url;
|
|
5
|
-
import { createClient } from "../client.js";
|
|
6
5
|
import { assertStorybookMain, detectStorybookMeta, loadStorybookConfig, writeStorybookConfig } from "../config.js";
|
|
7
6
|
import { printError, printLine } from "../output.js";
|
|
7
|
+
import { createClient } from "../client.js";
|
|
8
8
|
import prompts from "prompts";
|
|
9
9
|
//#region src/commands/init.ts
|
|
10
10
|
/** Human description of the detected Storybook (framework, addons, path). */
|
|
@@ -96,7 +96,7 @@ function buildInitConfig(answers, slug) {
|
|
|
96
96
|
/** Persist the resolved answers to .storybook/storyshelf.json. */
|
|
97
97
|
async function persistInitConfig(answers, slug, cwd, configPath) {
|
|
98
98
|
try {
|
|
99
|
-
const written = await writeStorybookConfig(buildInitConfig(answers, slug), cwd, configPath);
|
|
99
|
+
const written = await writeStorybookConfig(buildInitConfig(answers, slug), cwd, configPath, { stampSchema: true });
|
|
100
100
|
printLine(`Wrote ${written}`);
|
|
101
101
|
if (answers.detected) printLine(`Detected ${answers.detected}`);
|
|
102
102
|
} catch (error) {
|
package/dist/commands/purge.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import "node:path";
|
|
3
3
|
import "node:url";
|
|
4
4
|
import.meta.url;
|
|
5
|
-
import { createClient } from "../client.js";
|
|
6
5
|
import { printLine } from "../output.js";
|
|
6
|
+
import { createClient } from "../client.js";
|
|
7
7
|
//#region src/commands/purge.ts
|
|
8
8
|
/**
|
|
9
9
|
* Purge expired builds on a StoryShelf server.
|
package/dist/commands/retry.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import "node:path";
|
|
3
3
|
import "node:url";
|
|
4
4
|
import.meta.url;
|
|
5
|
-
import { createClient } from "../client.js";
|
|
6
5
|
import { printLine } from "../output.js";
|
|
6
|
+
import { createClient } from "../client.js";
|
|
7
7
|
//#region src/commands/retry.ts
|
|
8
8
|
/**
|
|
9
9
|
* Retry a failed StoryShelf build.
|
|
@@ -30,7 +30,7 @@ const DOCKERIGNORE_LINES = [
|
|
|
30
30
|
".env*",
|
|
31
31
|
"data/"
|
|
32
32
|
];
|
|
33
|
-
const
|
|
33
|
+
const COMPOSE_BASE_LINES = [
|
|
34
34
|
"services:",
|
|
35
35
|
" storyshelf:",
|
|
36
36
|
" build: .",
|
|
@@ -41,22 +41,87 @@ const COMPOSE_LINES = [
|
|
|
41
41
|
" environment:",
|
|
42
42
|
" - SECRET=change-me",
|
|
43
43
|
" - PORT=3000",
|
|
44
|
-
" - DATA_DIR=/data"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
44
|
+
" - DATA_DIR=/data"
|
|
45
|
+
];
|
|
46
|
+
const COMPOSE_TURSO_LINES = [" # - TURSO_DATABASE_URL=libsql://...", " # - TURSO_AUTH_TOKEN=..."];
|
|
47
|
+
const COMPOSE_POSTGRES_LINES = [
|
|
48
|
+
" - DATABASE_URL=postgres://shelf:shelf@postgres:5432/shelf",
|
|
49
|
+
" depends_on:",
|
|
50
|
+
" postgres:",
|
|
51
|
+
" condition: service_healthy",
|
|
52
|
+
" postgres:",
|
|
53
|
+
" image: postgres:16-alpine",
|
|
54
|
+
" environment:",
|
|
55
|
+
" - POSTGRES_DB=shelf",
|
|
56
|
+
" - POSTGRES_USER=shelf",
|
|
57
|
+
" - POSTGRES_PASSWORD=shelf",
|
|
58
|
+
" volumes:",
|
|
59
|
+
" - postgres-data:/var/lib/postgresql/data",
|
|
60
|
+
" healthcheck:",
|
|
61
|
+
" test: [\"CMD-SHELL\", \"pg_isready -U shelf -d shelf\"]",
|
|
62
|
+
" interval: 5s",
|
|
63
|
+
" timeout: 5s",
|
|
64
|
+
" retries: 5"
|
|
51
65
|
];
|
|
66
|
+
const COMPOSE_VOLUMES_LINES = ["volumes:", " storyshelf-data:"];
|
|
67
|
+
const COMPOSE_POSTGRES_VOLUMES_LINES = [" postgres-data:"];
|
|
52
68
|
function generateDockerfile() {
|
|
53
69
|
return DOCKERFILE_LINES.join("\n");
|
|
54
70
|
}
|
|
55
71
|
function generateDockerignore() {
|
|
56
72
|
return DOCKERIGNORE_LINES.join("\n");
|
|
57
73
|
}
|
|
58
|
-
function generateComposeYaml() {
|
|
59
|
-
return
|
|
74
|
+
function generateComposeYaml(database = "sqlite") {
|
|
75
|
+
return [
|
|
76
|
+
...COMPOSE_BASE_LINES,
|
|
77
|
+
" # Add your env vars here:",
|
|
78
|
+
" # - AUTH_PASSWORD=your-password",
|
|
79
|
+
...database === "turso" ? COMPOSE_TURSO_LINES : [],
|
|
80
|
+
...database === "postgres" ? COMPOSE_POSTGRES_LINES : [],
|
|
81
|
+
...COMPOSE_VOLUMES_LINES,
|
|
82
|
+
...database === "postgres" ? COMPOSE_POSTGRES_VOLUMES_LINES : []
|
|
83
|
+
].join("\n");
|
|
84
|
+
}
|
|
85
|
+
const WORKER_DOCKERFILE_LINES = [
|
|
86
|
+
"FROM node:lts-alpine AS builder",
|
|
87
|
+
"WORKDIR /app",
|
|
88
|
+
"COPY package.json ./",
|
|
89
|
+
"RUN npm install",
|
|
90
|
+
"COPY worker.ts ./",
|
|
91
|
+
"RUN npx esbuild worker.ts --bundle --platform=node --format=esm \\",
|
|
92
|
+
" --external:playwright-core \\",
|
|
93
|
+
" --outfile=dist/worker.mjs",
|
|
94
|
+
"",
|
|
95
|
+
"FROM mcr.microsoft.com/playwright:v1.63.0-noble",
|
|
96
|
+
"WORKDIR /app",
|
|
97
|
+
"COPY --from=builder /app/dist/worker.mjs ./",
|
|
98
|
+
"COPY --from=builder /app/node_modules/playwright-core ./node_modules/playwright-core",
|
|
99
|
+
"ENV DATA_DIR=/data",
|
|
100
|
+
"CMD [\"node\", \"dist/worker.mjs\"]"
|
|
101
|
+
];
|
|
102
|
+
const WORKER_COMPOSE_LINES = [
|
|
103
|
+
" worker:",
|
|
104
|
+
" build:",
|
|
105
|
+
" dockerfile: Dockerfile.worker",
|
|
106
|
+
" volumes:",
|
|
107
|
+
" - storyshelf-data:/data",
|
|
108
|
+
" environment:",
|
|
109
|
+
" - SECRET=change-me",
|
|
110
|
+
" - DATA_DIR=/data",
|
|
111
|
+
" - QUEUE_URL=${QUEUE_URL}",
|
|
112
|
+
" - WORKER_CONCURRENCY=2",
|
|
113
|
+
" depends_on:",
|
|
114
|
+
" storyshelf:",
|
|
115
|
+
" condition: service_started"
|
|
116
|
+
];
|
|
117
|
+
function generateWorkerDockerfile() {
|
|
118
|
+
return WORKER_DOCKERFILE_LINES.join("\n");
|
|
119
|
+
}
|
|
120
|
+
function generateWorkerComposeSnippet() {
|
|
121
|
+
return WORKER_COMPOSE_LINES.join("\n");
|
|
122
|
+
}
|
|
123
|
+
function generateComposeYamlWithWorker(database = "sqlite") {
|
|
124
|
+
return `${generateComposeYaml(database)}\n${generateWorkerComposeSnippet()}`;
|
|
60
125
|
}
|
|
61
126
|
//#endregion
|
|
62
|
-
export { generateComposeYaml, generateDockerfile, generateDockerignore };
|
|
127
|
+
export { generateComposeYaml, generateComposeYamlWithWorker, generateDockerfile, generateDockerignore, generateWorkerComposeSnippet, generateWorkerDockerfile };
|