storybooker 0.0.1
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/.oxlintrc.json +24 -0
- package/.turbo/turbo-build.log +11 -0
- package/.turbo/turbo-check.log +2 -0
- package/.turbo/turbo-lint.log +3 -0
- package/CHANGELOG.md +7 -0
- package/README.md +25 -0
- package/dist/index.js +554 -0
- package/package.json +45 -0
- package/src/commands/create.ts +260 -0
- package/src/commands/purge.ts +70 -0
- package/src/commands/test.ts +42 -0
- package/src/index.ts +17 -0
- package/src/service-schema.d.ts +2023 -0
- package/src/utils/auth-utils.ts +30 -0
- package/src/utils/pkg-utils.ts +34 -0
- package/src/utils/sb-build.ts +51 -0
- package/src/utils/sb-test.ts +115 -0
- package/src/utils/schema-utils.ts +123 -0
- package/src/utils/stream-utils.ts +72 -0
- package/src/utils/types.ts +4 -0
- package/src/utils/zip.ts +75 -0
- package/tsconfig.json +10 -0
- package/tsdown.config.ts +9 -0
package/.oxlintrc.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": ["../../.oxlintrc.json"],
|
|
3
|
+
"env": {
|
|
4
|
+
"builtin": true,
|
|
5
|
+
"node": true
|
|
6
|
+
},
|
|
7
|
+
"rules": {
|
|
8
|
+
"no-console": "allow",
|
|
9
|
+
"no-process-exit": "allow",
|
|
10
|
+
"no-unsafe-call": "allow",
|
|
11
|
+
"sort-keys": "allow"
|
|
12
|
+
},
|
|
13
|
+
"ignorePatterns": [],
|
|
14
|
+
"overrides": [
|
|
15
|
+
{
|
|
16
|
+
"files": ["*.d.ts"],
|
|
17
|
+
"rules": {
|
|
18
|
+
"max-lines": "allow",
|
|
19
|
+
"consistent-indexed-object-style": "allow",
|
|
20
|
+
"no-redundant-type-constituents": "allow"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
[34mℹ[39m tsdown [2mv0.14.2[22m powered by rolldown [2mv1.0.0-beta.36[22m
|
|
3
|
+
[34mℹ[39m Using tsdown config: [4m/Users/siddhant.c.gupta/Projects/GS/storybooker/packages/cli/tsdown.config.ts[24m
|
|
4
|
+
[34mℹ[39m entry: [34msrc/index.ts[39m
|
|
5
|
+
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
6
|
+
[34mℹ[39m Build start
|
|
7
|
+
[34mℹ[39m Cleaning 1 files
|
|
8
|
+
[34mℹ[39m Granting execute permission to [4mdist/index.js[24m
|
|
9
|
+
[34mℹ[39m [2mdist/[22m[1mindex.js[22m [2m18.25 kB[22m [2m│ gzip: 5.14 kB[22m
|
|
10
|
+
[34mℹ[39m 1 files, total: 18.25 kB
|
|
11
|
+
[32m✔[39m Build complete in [32m53ms[39m
|
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# StoryBooker CLI
|
|
2
|
+
|
|
3
|
+
A NodeJS CLI to sync with StoryBooker service. The CLI can be used to build, test, upload StoryBook to the service.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
See commands + help
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npx -y storybooker -h
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Build+Upload assets
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npx -y storybooker create \
|
|
17
|
+
-u https://<storybooker-service> \
|
|
18
|
+
-p <project-id> \
|
|
19
|
+
--id <build-id> \
|
|
20
|
+
--test \
|
|
21
|
+
-l <branch-name> -l <another-label> \
|
|
22
|
+
--authorName <your-name> \
|
|
23
|
+
--authorEmail <your-email> \
|
|
24
|
+
--message "<readable message>"
|
|
25
|
+
```
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import yargs from "yargs";
|
|
3
|
+
import { hideBin } from "yargs/helpers";
|
|
4
|
+
import * as fs$1 from "node:fs";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import * as path$1 from "node:path";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { styleText } from "node:util";
|
|
9
|
+
import createClient from "openapi-fetch";
|
|
10
|
+
import z from "zod";
|
|
11
|
+
import { ChildProcess, execSync, spawnSync } from "node:child_process";
|
|
12
|
+
import * as process$1 from "node:process";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
|
|
15
|
+
//#region package.json
|
|
16
|
+
var name = "storybooker";
|
|
17
|
+
var version = "0.0.0";
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/utils/auth-utils.ts
|
|
21
|
+
function createAuthMiddleware(options) {
|
|
22
|
+
const { authType, authValue } = options;
|
|
23
|
+
return { onRequest: ({ request }) => {
|
|
24
|
+
if (!authValue) return request;
|
|
25
|
+
if (!authType) return request;
|
|
26
|
+
switch (authType) {
|
|
27
|
+
case "auth-header":
|
|
28
|
+
request.headers.set("Authorization", authValue);
|
|
29
|
+
return request;
|
|
30
|
+
default: return request;
|
|
31
|
+
}
|
|
32
|
+
} };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/utils/pkg-utils.ts
|
|
37
|
+
function detectPackageManager(startDir = process.cwd(), maxDepth = 5) {
|
|
38
|
+
let currentDir = startDir;
|
|
39
|
+
let depth = 0;
|
|
40
|
+
while (depth < maxDepth) {
|
|
41
|
+
if (fs$1.existsSync(path$1.join(currentDir, `yarn.lock`))) return "yarn";
|
|
42
|
+
if (fs$1.existsSync(path$1.join(currentDir, `pnpm-lock.yaml`))) return "pnpm";
|
|
43
|
+
if (fs$1.existsSync(path$1.join(currentDir, `package-lock.json`))) return "npm";
|
|
44
|
+
if (fs$1.existsSync(path$1.join(currentDir, `bun.lock`))) return "bun";
|
|
45
|
+
const parentDir = path$1.dirname(currentDir);
|
|
46
|
+
if (parentDir === currentDir) break;
|
|
47
|
+
currentDir = parentDir;
|
|
48
|
+
}
|
|
49
|
+
throw new Error("Package manager could not be determined.");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/utils/sb-build.ts
|
|
54
|
+
function buildStoryBook({ build, cwd, silent }) {
|
|
55
|
+
if (build === false) {
|
|
56
|
+
console.log("> Skipping StoryBook Build.");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
let output = "";
|
|
60
|
+
if (typeof build === "string" && build.trim() !== "") {
|
|
61
|
+
console.log("> Building StoryBook with script: %s", build);
|
|
62
|
+
const pkgManager = detectPackageManager(cwd);
|
|
63
|
+
output = spawnSync(pkgManager, ["run", build], {
|
|
64
|
+
cwd,
|
|
65
|
+
shell: true,
|
|
66
|
+
stdio: silent ? void 0 : "inherit",
|
|
67
|
+
encoding: "utf8"
|
|
68
|
+
}).stdout;
|
|
69
|
+
} else {
|
|
70
|
+
console.log("> Building StoryBook with storybook CLI");
|
|
71
|
+
output = spawnSync("npx", [
|
|
72
|
+
"-y",
|
|
73
|
+
"storybook",
|
|
74
|
+
"build"
|
|
75
|
+
], {
|
|
76
|
+
cwd,
|
|
77
|
+
stdio: silent ? void 0 : "inherit",
|
|
78
|
+
encoding: "utf8"
|
|
79
|
+
}).stdout;
|
|
80
|
+
}
|
|
81
|
+
const outputDirpath = output?.split("Output directory: ").at(1)?.trim();
|
|
82
|
+
if (!outputDirpath || !fs$1.existsSync(outputDirpath)) {
|
|
83
|
+
console.error(`Could not find build output at '${outputDirpath}'.`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
console.log("> Built StoryBook to dir: '%s'.", path$1.relative(cwd, outputDirpath));
|
|
87
|
+
return outputDirpath;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/utils/sb-test.ts
|
|
92
|
+
function testStoryBook({ test, cwd, silent, testReportDir = ".test/report", testCoverageDir = ".test/coverage" }) {
|
|
93
|
+
if (test) try {
|
|
94
|
+
runTest(test, {
|
|
95
|
+
cwd,
|
|
96
|
+
silent,
|
|
97
|
+
testCoverageDir,
|
|
98
|
+
testReportDir
|
|
99
|
+
});
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error(error);
|
|
102
|
+
}
|
|
103
|
+
else console.log("> Skipping tests");
|
|
104
|
+
const testReportDirpath = path$1.join(cwd, testReportDir);
|
|
105
|
+
const existsTestReportDirpath = fs$1.existsSync(testReportDirpath);
|
|
106
|
+
if (existsTestReportDirpath) console.log("> Test report saved at '%s'.", path$1.relative(cwd, testReportDirpath));
|
|
107
|
+
else console.warn("> Test report was not created'.");
|
|
108
|
+
const testCoverageDirpath = path$1.join(cwd, testCoverageDir);
|
|
109
|
+
const existsTestCoverageDirpath = fs$1.existsSync(testCoverageDirpath);
|
|
110
|
+
if (existsTestCoverageDirpath) console.log("> Test coverage saved at '%s'.", path$1.relative(cwd, testCoverageDirpath));
|
|
111
|
+
else console.warn("> Test coverage was not created'.");
|
|
112
|
+
return {
|
|
113
|
+
testCoverageDirpath: existsTestCoverageDirpath ? testCoverageDirpath : void 0,
|
|
114
|
+
testReportDirpath: existsTestReportDirpath ? testReportDirpath : void 0
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function runTest(test, options) {
|
|
118
|
+
const { cwd, silent, testCoverageDir, testReportDir } = options;
|
|
119
|
+
if (typeof test === "string" && test.trim() !== "") {
|
|
120
|
+
console.log("> Testing StoryBook with script: %s", test);
|
|
121
|
+
const pkgManager = detectPackageManager(cwd);
|
|
122
|
+
spawnSync(pkgManager, ["run", test], {
|
|
123
|
+
cwd,
|
|
124
|
+
shell: true,
|
|
125
|
+
stdio: silent ? void 0 : "inherit"
|
|
126
|
+
});
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
console.log("> Testing StoryBook with Vitest");
|
|
130
|
+
spawnSync("npx", [
|
|
131
|
+
"-y",
|
|
132
|
+
"vitest",
|
|
133
|
+
"run",
|
|
134
|
+
"--reporter=default",
|
|
135
|
+
"--reporter=html",
|
|
136
|
+
`--outputFile.html=${path$1.join(testReportDir, "index.html")}`,
|
|
137
|
+
"--reporter=json",
|
|
138
|
+
`--outputFile.json=${path$1.join(testReportDir, "report.json")}`,
|
|
139
|
+
"--coverage",
|
|
140
|
+
"--coverage.provider=v8",
|
|
141
|
+
"--coverage.reportOnFailure",
|
|
142
|
+
`--coverage.reportsDirectory=${testCoverageDir}`,
|
|
143
|
+
"--coverage.reporter=text",
|
|
144
|
+
"--coverage.reporter=html",
|
|
145
|
+
"--coverage.reporter=text-summary",
|
|
146
|
+
"--coverage.reporter=json-summary"
|
|
147
|
+
], {
|
|
148
|
+
cwd,
|
|
149
|
+
stdio: silent ? void 0 : "inherit"
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/utils/schema-utils.ts
|
|
155
|
+
const sharedSchemas = {
|
|
156
|
+
project: z.string({ error: "ProjectID is required to match with project." }).meta({
|
|
157
|
+
alias: ["p"],
|
|
158
|
+
description: "Project ID associated with the StoryBook."
|
|
159
|
+
}),
|
|
160
|
+
url: z.url({ error: "URL is required to connect to the service." }).meta({
|
|
161
|
+
alias: ["u"],
|
|
162
|
+
description: "URL of the StoryBooker service."
|
|
163
|
+
}),
|
|
164
|
+
cwd: z.string().optional().meta({ description: "Change the working directory for the command." }),
|
|
165
|
+
testReportDir: z.string().optional().meta({ description: "Relative path of the test report directory to upload." }),
|
|
166
|
+
testCoverageDir: z.string().optional().meta({ description: "Relative path of the test coverage directory to upload." }),
|
|
167
|
+
authType: z.enum(["auth-header"]).optional().meta({ description: "Enable auth for outgoing requests." }),
|
|
168
|
+
authValue: z.string().optional().meta({
|
|
169
|
+
description: "Auth value set for outgoing requests.",
|
|
170
|
+
implies: "authType"
|
|
171
|
+
})
|
|
172
|
+
};
|
|
173
|
+
function zodSchemaToCommandBuilder(objectSchema) {
|
|
174
|
+
const builder = {};
|
|
175
|
+
for (const [key, schema$1] of Object.entries(objectSchema._zod.def.shape)) {
|
|
176
|
+
const meta = schema$1 instanceof z.ZodType ? schema$1.meta() : void 0;
|
|
177
|
+
let optional = false;
|
|
178
|
+
try {
|
|
179
|
+
if (schema$1["~standard"].validate(void 0, {}) instanceof Promise) throw new TypeError("Cannot handle async schema");
|
|
180
|
+
optional = true;
|
|
181
|
+
} catch {
|
|
182
|
+
optional = false;
|
|
183
|
+
}
|
|
184
|
+
let errorMessage = schema$1._zod.def.error?.(void 0);
|
|
185
|
+
if (typeof errorMessage === "object") errorMessage = errorMessage?.message;
|
|
186
|
+
let defaultValue = schema$1 instanceof z.core.$ZodDefault ? schema$1._zod.def.defaultValue : void 0;
|
|
187
|
+
if (typeof defaultValue === "function") defaultValue = String(defaultValue());
|
|
188
|
+
const description = String(schema$1._zod.def.description ?? schema$1.description);
|
|
189
|
+
builder[key] = {
|
|
190
|
+
alias: meta?.alias,
|
|
191
|
+
description,
|
|
192
|
+
demandOption: optional ? false : errorMessage ?? true,
|
|
193
|
+
type: zodSchemaTypeToYargsBuilderType(schema$1),
|
|
194
|
+
default: defaultValue,
|
|
195
|
+
deprecated: meta?.deprecated,
|
|
196
|
+
hidden: meta?.hidden,
|
|
197
|
+
implies: meta?.implies
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return builder;
|
|
201
|
+
}
|
|
202
|
+
function zodSchemaTypeToYargsBuilderType(schema$1) {
|
|
203
|
+
if (schema$1 instanceof z.core.$ZodArray) return "array";
|
|
204
|
+
if (schema$1 instanceof z.core.$ZodBoolean) return "boolean";
|
|
205
|
+
if (schema$1 instanceof z.core.$ZodNumber) return "number";
|
|
206
|
+
if (schema$1 instanceof z.core.$ZodOptional) return zodSchemaTypeToYargsBuilderType(schema$1._zod.def.innerType);
|
|
207
|
+
if (schema$1 instanceof z.core.$ZodDefault) return zodSchemaTypeToYargsBuilderType(schema$1._zod.def.innerType);
|
|
208
|
+
if (schema$1 instanceof z.core.$ZodUnion) return;
|
|
209
|
+
return "string";
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/utils/stream-utils.ts
|
|
214
|
+
function toReadableStream(readable, filesize, onProgress = onUploadProgress) {
|
|
215
|
+
let uploaded = 0;
|
|
216
|
+
return new ReadableStream({
|
|
217
|
+
start: (controller) => {
|
|
218
|
+
function onData(chunk) {
|
|
219
|
+
try {
|
|
220
|
+
uploaded += chunk.length;
|
|
221
|
+
controller.enqueue(chunk);
|
|
222
|
+
onProgress?.(uploaded, filesize);
|
|
223
|
+
} catch {
|
|
224
|
+
cleanup();
|
|
225
|
+
readable.destroy();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function onEnd() {
|
|
229
|
+
cleanup();
|
|
230
|
+
controller.close();
|
|
231
|
+
}
|
|
232
|
+
function onError(err) {
|
|
233
|
+
cleanup();
|
|
234
|
+
controller.error(err);
|
|
235
|
+
}
|
|
236
|
+
function cleanup() {
|
|
237
|
+
readable.off("data", onData);
|
|
238
|
+
readable.off("end", onEnd);
|
|
239
|
+
readable.off("error", onError);
|
|
240
|
+
console.log("");
|
|
241
|
+
}
|
|
242
|
+
readable.on("data", onData);
|
|
243
|
+
readable.on("end", onEnd);
|
|
244
|
+
readable.on("error", onError);
|
|
245
|
+
},
|
|
246
|
+
cancel: () => {
|
|
247
|
+
readable.destroy();
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
/** Function to handle upload progress */
|
|
252
|
+
function onUploadProgress(uploaded, total) {
|
|
253
|
+
const percentStr = (uploaded / total * 100).toFixed(2).padStart(6, " ");
|
|
254
|
+
const totalStr = (total / 1e3).toFixed(2);
|
|
255
|
+
const uploadedStr = (uploaded / 1e3).toFixed(2).padStart(totalStr.length, " ");
|
|
256
|
+
process$1.stdout.write(styleText("dim", `\r - Uploaded: ${uploadedStr} / ${totalStr} KB (${percentStr}%)`));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
//#endregion
|
|
260
|
+
//#region src/utils/zip.ts
|
|
261
|
+
const isWindows = process.platform === "win32";
|
|
262
|
+
/**
|
|
263
|
+
* Cross-platform sync zip utility that works on Windows and Unix-like systems.
|
|
264
|
+
* It uses the `zip` command on Unix-like systems and PowerShell on Windows.
|
|
265
|
+
* It throws an error if the `zip` command is not available on Unix-like systems.
|
|
266
|
+
*/
|
|
267
|
+
function zip(inPath, outPath) {
|
|
268
|
+
if (isWindows) {
|
|
269
|
+
if (fs.statSync(inPath).isFile()) {
|
|
270
|
+
const inFile = fs.readFileSync(inPath);
|
|
271
|
+
const tmpPath = path.join(tmpdir(), `cross-zip-${Date.now()}`);
|
|
272
|
+
fs.mkdirSync(tmpPath);
|
|
273
|
+
fs.writeFileSync(path.join(tmpPath, path.basename(inPath)), inFile);
|
|
274
|
+
inPath = tmpPath;
|
|
275
|
+
}
|
|
276
|
+
fs.rmdirSync(outPath, {
|
|
277
|
+
recursive: true,
|
|
278
|
+
maxRetries: 3
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
const cwd = path.dirname(inPath);
|
|
282
|
+
const zipCmd = getZipCommand();
|
|
283
|
+
const zipCmdArgs = getZipArgs(inPath, outPath);
|
|
284
|
+
try {
|
|
285
|
+
execSync([zipCmd, ...zipCmdArgs].join(" "), {
|
|
286
|
+
cwd,
|
|
287
|
+
maxBuffer: Infinity,
|
|
288
|
+
encoding: "utf8"
|
|
289
|
+
});
|
|
290
|
+
} catch (error) {
|
|
291
|
+
if (error instanceof ChildProcess) {
|
|
292
|
+
console.error("STDOUT:", error.stdout);
|
|
293
|
+
console.error("STDERR:", error.stderr);
|
|
294
|
+
}
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function getZipCommand() {
|
|
299
|
+
if (isWindows) return "powershell.exe";
|
|
300
|
+
return "zip";
|
|
301
|
+
}
|
|
302
|
+
function getZipArgs(inPath, outPath) {
|
|
303
|
+
if (isWindows) return [
|
|
304
|
+
"-nologo",
|
|
305
|
+
"-noprofile",
|
|
306
|
+
"-command",
|
|
307
|
+
"& { param([String]$myInPath, [String]$myOutPath); Add-Type -A \"System.IO.Compression.FileSystem\"; [IO.Compression.ZipFile]::CreateFromDirectory($myInPath, $myOutPath); exit !$? }",
|
|
308
|
+
"-myInPath",
|
|
309
|
+
quotePath(inPath),
|
|
310
|
+
"-myOutPath",
|
|
311
|
+
quotePath(outPath)
|
|
312
|
+
];
|
|
313
|
+
const dirname = path.basename(inPath.toString());
|
|
314
|
+
return [
|
|
315
|
+
"-r",
|
|
316
|
+
"-y",
|
|
317
|
+
outPath.toString(),
|
|
318
|
+
dirname
|
|
319
|
+
];
|
|
320
|
+
}
|
|
321
|
+
function quotePath(pathToTransform) {
|
|
322
|
+
return `"${pathToTransform.toString()}"`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
//#endregion
|
|
326
|
+
//#region src/commands/create.ts
|
|
327
|
+
const CreateSchema = z.object({
|
|
328
|
+
project: sharedSchemas.project,
|
|
329
|
+
url: sharedSchemas.url,
|
|
330
|
+
cwd: sharedSchemas.cwd,
|
|
331
|
+
sha: z.string({ error: "URL is required to connect to the service." }).meta({
|
|
332
|
+
alias: ["id"],
|
|
333
|
+
description: "Unique ID of the build."
|
|
334
|
+
}),
|
|
335
|
+
message: z.string().optional().meta({
|
|
336
|
+
alias: ["m"],
|
|
337
|
+
description: "Readable message for the build."
|
|
338
|
+
}),
|
|
339
|
+
labels: z.string().array().meta({
|
|
340
|
+
alias: ["l"],
|
|
341
|
+
description: "Labels associated with the build."
|
|
342
|
+
}),
|
|
343
|
+
build: z.union([z.string(), z.boolean()]).optional().meta({
|
|
344
|
+
alias: ["b"],
|
|
345
|
+
description: "Name of the script in package.json to build the StoryBook."
|
|
346
|
+
}),
|
|
347
|
+
test: z.union([z.string(), z.boolean()]).optional().meta({
|
|
348
|
+
alias: ["t"],
|
|
349
|
+
description: "Name of the script in package.json to test the StoryBook."
|
|
350
|
+
}),
|
|
351
|
+
testCoverageDir: sharedSchemas.testCoverageDir,
|
|
352
|
+
testReportDir: sharedSchemas.testReportDir,
|
|
353
|
+
silent: z.boolean().default(false).meta({
|
|
354
|
+
alias: ["s"],
|
|
355
|
+
description: "Silent the logs and only show final error/status."
|
|
356
|
+
}),
|
|
357
|
+
authorName: z.string().optional().meta({ description: "Name of the author of the build." }),
|
|
358
|
+
authorEmail: z.email().optional().meta({ description: "Email of the author of the build." }),
|
|
359
|
+
ignoreError: z.boolean().default(false).meta({ hidden: true }),
|
|
360
|
+
authType: sharedSchemas.authType,
|
|
361
|
+
authValue: sharedSchemas.authValue
|
|
362
|
+
});
|
|
363
|
+
const createCommandModule = {
|
|
364
|
+
command: "create",
|
|
365
|
+
describe: "Create and upload StoryBook assets to the service.",
|
|
366
|
+
builder: zodSchemaToCommandBuilder(CreateSchema),
|
|
367
|
+
handler: async (args) => {
|
|
368
|
+
const result = CreateSchema.safeParse(args);
|
|
369
|
+
if (!result.success) throw new Error(z.prettifyError(result.error));
|
|
370
|
+
const cwd = result.data.cwd ? path$1.resolve(result.data.cwd) : process.cwd();
|
|
371
|
+
if (cwd && !fs$1.existsSync(cwd)) throw new Error(`Path provided to CWD does not exists: '${cwd}'`);
|
|
372
|
+
const { build, silent, url, project, sha, ignoreError, test, testCoverageDir, testReportDir, authType, authValue } = result.data;
|
|
373
|
+
const client = createClient({ baseUrl: url });
|
|
374
|
+
client.use(createAuthMiddleware({
|
|
375
|
+
authType,
|
|
376
|
+
authValue
|
|
377
|
+
}));
|
|
378
|
+
try {
|
|
379
|
+
console.group(styleText("bold", "\nCreate Build Entry"));
|
|
380
|
+
await createSBRBuild(client, result.data, ignoreError);
|
|
381
|
+
console.groupEnd();
|
|
382
|
+
console.group(styleText("bold", "\nBuild StoryBook"));
|
|
383
|
+
const buildDirpath = buildStoryBook({
|
|
384
|
+
build,
|
|
385
|
+
cwd,
|
|
386
|
+
silent
|
|
387
|
+
});
|
|
388
|
+
if (buildDirpath) {
|
|
389
|
+
await uploadSBRBuild(client, {
|
|
390
|
+
project,
|
|
391
|
+
sha,
|
|
392
|
+
dirpath: buildDirpath,
|
|
393
|
+
variant: "storybook",
|
|
394
|
+
cwd
|
|
395
|
+
});
|
|
396
|
+
console.groupEnd();
|
|
397
|
+
}
|
|
398
|
+
console.group(styleText("bold", "\nTest StoryBook"));
|
|
399
|
+
const { testCoverageDirpath, testReportDirpath } = testStoryBook({
|
|
400
|
+
cwd,
|
|
401
|
+
test,
|
|
402
|
+
silent,
|
|
403
|
+
testCoverageDir,
|
|
404
|
+
testReportDir
|
|
405
|
+
});
|
|
406
|
+
if (testReportDirpath) await uploadSBRBuild(client, {
|
|
407
|
+
project,
|
|
408
|
+
sha,
|
|
409
|
+
dirpath: testReportDirpath,
|
|
410
|
+
cwd,
|
|
411
|
+
variant: "testReport"
|
|
412
|
+
});
|
|
413
|
+
if (testCoverageDirpath) await uploadSBRBuild(client, {
|
|
414
|
+
project,
|
|
415
|
+
sha,
|
|
416
|
+
dirpath: testCoverageDirpath,
|
|
417
|
+
cwd,
|
|
418
|
+
variant: "coverage"
|
|
419
|
+
});
|
|
420
|
+
console.groupEnd();
|
|
421
|
+
console.log(styleText("green", "Created build successfully."));
|
|
422
|
+
} catch (error) {
|
|
423
|
+
console.error(error);
|
|
424
|
+
process.exit(1);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
async function createSBRBuild(client, { project, sha, message, labels }, ignorePrevious) {
|
|
429
|
+
const { error, response } = await client.POST("/projects/{projectId}/builds/create", {
|
|
430
|
+
params: { path: { projectId: project } },
|
|
431
|
+
body: {
|
|
432
|
+
authorEmail: "Siddhant@asd.com",
|
|
433
|
+
authorName: "Siddhant",
|
|
434
|
+
labels,
|
|
435
|
+
sha,
|
|
436
|
+
message
|
|
437
|
+
},
|
|
438
|
+
headers: {
|
|
439
|
+
Accept: "application/json",
|
|
440
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
if (error) if (ignorePrevious) console.warn(styleText("yellow", "> StoryBooker Build entry already exits."));
|
|
444
|
+
else throw new Error(error.errorMessage || `Request to service failed with status: ${response.status}.`);
|
|
445
|
+
else console.log("New Build entry created '%s / %s'", project, sha);
|
|
446
|
+
}
|
|
447
|
+
async function uploadSBRBuild(client, { project, sha, dirpath, variant, cwd }) {
|
|
448
|
+
const zipFilepath = path$1.join(cwd, `${variant}.zip`);
|
|
449
|
+
console.log(`> Compressing directory '%s' to file '%s'...`, path$1.relative(cwd, dirpath), path$1.relative(cwd, zipFilepath));
|
|
450
|
+
zip(path$1.join(dirpath, "*"), zipFilepath);
|
|
451
|
+
const fileSize = fs$1.statSync(zipFilepath).size;
|
|
452
|
+
console.log(`> Uploading file '%s'...`, path$1.relative(cwd, zipFilepath));
|
|
453
|
+
const { error, response } = await client.POST("/projects/{projectId}/builds/{buildSHA}/upload", {
|
|
454
|
+
params: {
|
|
455
|
+
path: {
|
|
456
|
+
projectId: project,
|
|
457
|
+
buildSHA: sha
|
|
458
|
+
},
|
|
459
|
+
query: { variant }
|
|
460
|
+
},
|
|
461
|
+
body: toReadableStream(fs$1.createReadStream(zipFilepath), fileSize),
|
|
462
|
+
bodySerializer: (body) => body,
|
|
463
|
+
headers: {
|
|
464
|
+
Accept: "application/json",
|
|
465
|
+
"Content-Type": "application/zip",
|
|
466
|
+
"Content-Length": fileSize.toString()
|
|
467
|
+
},
|
|
468
|
+
duplex: "half"
|
|
469
|
+
});
|
|
470
|
+
if (error) throw new Error(error.errorMessage || `Request to service failed with status: ${response.status}.`);
|
|
471
|
+
else {
|
|
472
|
+
console.log("> Uploaded '%s / %s / %s'.", project, sha, variant);
|
|
473
|
+
fs$1.rmSync(zipFilepath);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
//#endregion
|
|
478
|
+
//#region src/commands/purge.ts
|
|
479
|
+
const PurgeSchema = z.object({
|
|
480
|
+
project: sharedSchemas.project,
|
|
481
|
+
url: sharedSchemas.url,
|
|
482
|
+
label: z.string().meta({
|
|
483
|
+
alias: ["l"],
|
|
484
|
+
description: "The label slug to purge associated builds."
|
|
485
|
+
}),
|
|
486
|
+
authType: sharedSchemas.authType,
|
|
487
|
+
authValue: sharedSchemas.authValue
|
|
488
|
+
});
|
|
489
|
+
const purgeCommandModule = {
|
|
490
|
+
command: "purge",
|
|
491
|
+
describe: "Purge StoryBook assets from the service.",
|
|
492
|
+
builder: zodSchemaToCommandBuilder(PurgeSchema),
|
|
493
|
+
handler: async (args) => {
|
|
494
|
+
const result = PurgeSchema.safeParse(args);
|
|
495
|
+
if (!result.success) throw new Error(z.prettifyError(result.error));
|
|
496
|
+
const { label, project, url, authType, authValue } = result.data;
|
|
497
|
+
const client = createClient({ baseUrl: url });
|
|
498
|
+
client.use(createAuthMiddleware({
|
|
499
|
+
authType,
|
|
500
|
+
authValue
|
|
501
|
+
}));
|
|
502
|
+
try {
|
|
503
|
+
console.group(styleText("bold", "\nPurge Label '%s' (project: %s)"), label, project);
|
|
504
|
+
console.log(`> Deleting label '%s' and associated builds...`, label);
|
|
505
|
+
const { error, response } = await client.DELETE("/projects/{projectId}/labels/{labelSlug}", {
|
|
506
|
+
params: { path: {
|
|
507
|
+
labelSlug: label,
|
|
508
|
+
projectId: project
|
|
509
|
+
} },
|
|
510
|
+
headers: { Accept: "application/json" }
|
|
511
|
+
});
|
|
512
|
+
if (error) throw new Error(error.errorMessage || `Request to service failed with status: ${response.status}.`);
|
|
513
|
+
else console.log("> Purged '%s / %s'.", project, label);
|
|
514
|
+
console.groupEnd();
|
|
515
|
+
} catch (error) {
|
|
516
|
+
console.error(error);
|
|
517
|
+
process.exit(1);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
//#endregion
|
|
523
|
+
//#region src/commands/test.ts
|
|
524
|
+
const schema = z.object({
|
|
525
|
+
cwd: sharedSchemas.cwd,
|
|
526
|
+
testCoverageDir: sharedSchemas.testCoverageDir,
|
|
527
|
+
testReportDir: sharedSchemas.testReportDir
|
|
528
|
+
});
|
|
529
|
+
const testCommandModule = {
|
|
530
|
+
command: "test",
|
|
531
|
+
describe: "Run test on StoryBook with Vitest",
|
|
532
|
+
builder: zodSchemaToCommandBuilder(schema),
|
|
533
|
+
handler(args) {
|
|
534
|
+
const result = schema.safeParse(args);
|
|
535
|
+
if (!result.success) throw new Error(z.prettifyError(result.error));
|
|
536
|
+
const cwd = result.data.cwd ? path$1.resolve(result.data.cwd) : process.cwd();
|
|
537
|
+
if (cwd && !fs$1.existsSync(cwd)) throw new Error(`Path provided to CWD does not exists: '${cwd}'`);
|
|
538
|
+
console.group(styleText("bold", "\nTest StoryBook"));
|
|
539
|
+
testStoryBook({
|
|
540
|
+
cwd,
|
|
541
|
+
test: true,
|
|
542
|
+
testCoverageDir: result.data.testCoverageDir,
|
|
543
|
+
testReportDir: result.data.testReportDir
|
|
544
|
+
});
|
|
545
|
+
console.groupEnd();
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
//#endregion
|
|
550
|
+
//#region src/index.ts
|
|
551
|
+
await yargs(hideBin(process.argv)).scriptName(name).usage(`npx -y $0 [command] (options)`).version(version).command(createCommandModule).command(purgeCommandModule).command(testCommandModule).alias("h", "help").alias("v", "version").parse();
|
|
552
|
+
|
|
553
|
+
//#endregion
|
|
554
|
+
export { };
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "storybooker",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": "./dist/index.js",
|
|
6
|
+
"description": "Storybooker CLI for uploading builds and files.",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "Siddhant Gupta",
|
|
9
|
+
"url": "https://guptasiddhant.com"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/guptasiddhant/storybooker",
|
|
14
|
+
"directory": "packages/cli"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsdown",
|
|
20
|
+
"check": "tsgo --noEmit",
|
|
21
|
+
"cli": "node ./dist/index.js --cwd ../../apps/test-ui",
|
|
22
|
+
"dev": "tsdown -w ./src",
|
|
23
|
+
"fmt": "prettier src --write --config ../../.prettierrc",
|
|
24
|
+
"lint": "oxlint --type-aware ./src",
|
|
25
|
+
"lint:fix": "yarn lint --fix",
|
|
26
|
+
"openapi": "openapi-typescript ../core/dist/openapi.json -o ./src/service-schema.d.ts",
|
|
27
|
+
"start": "node --watch ./dist/index.js --cwd ../../apps/test-ui"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"openapi-fetch": "^0.14.0",
|
|
31
|
+
"yargs": "^18.0.0",
|
|
32
|
+
"zod": "4.1.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^22.0.0",
|
|
36
|
+
"@types/yargs": "^17.0.33",
|
|
37
|
+
"@typescript/native-preview": "^7.0.0-dev.20250823.8",
|
|
38
|
+
"openapi-typescript": "^7.9.1",
|
|
39
|
+
"oxlint": "^1.12.0",
|
|
40
|
+
"oxlint-tsgolint": "^0.0.4",
|
|
41
|
+
"prettier": "^3.6.2",
|
|
42
|
+
"tsdown": "^0.14.1",
|
|
43
|
+
"typescript": "^5.9.2"
|
|
44
|
+
}
|
|
45
|
+
}
|