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.
@@ -0,0 +1,30 @@
1
+ import type { Middleware } from "openapi-fetch";
2
+ import type z from "zod";
3
+ import type { sharedSchemas } from "./schema-utils";
4
+
5
+ export function createAuthMiddleware(options: {
6
+ authType: z.infer<typeof sharedSchemas.authType>;
7
+ authValue: z.infer<typeof sharedSchemas.authValue>;
8
+ }): Middleware {
9
+ const { authType, authValue } = options;
10
+ return {
11
+ onRequest: ({ request }) => {
12
+ if (!authValue) {
13
+ return request;
14
+ }
15
+ if (!authType) {
16
+ return request;
17
+ }
18
+
19
+ switch (authType) {
20
+ case "auth-header": {
21
+ request.headers.set("Authorization", authValue);
22
+ return request;
23
+ }
24
+ default: {
25
+ return request;
26
+ }
27
+ }
28
+ },
29
+ };
30
+ }
@@ -0,0 +1,34 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+
4
+ export type PkgManager = "npm" | "yarn" | "pnpm" | "bun";
5
+ export function detectPackageManager(
6
+ startDir: string = process.cwd(),
7
+ maxDepth = 5,
8
+ ): PkgManager {
9
+ let currentDir = startDir;
10
+ let depth = 0;
11
+
12
+ while (depth < maxDepth) {
13
+ if (fs.existsSync(path.join(currentDir, `yarn.lock`))) {
14
+ return "yarn";
15
+ }
16
+ if (fs.existsSync(path.join(currentDir, `pnpm-lock.yaml`))) {
17
+ return "pnpm";
18
+ }
19
+ if (fs.existsSync(path.join(currentDir, `package-lock.json`))) {
20
+ return "npm";
21
+ }
22
+ if (fs.existsSync(path.join(currentDir, `bun.lock`))) {
23
+ return "bun";
24
+ }
25
+
26
+ const parentDir = path.dirname(currentDir);
27
+ if (parentDir === currentDir) {
28
+ break;
29
+ }
30
+ currentDir = parentDir;
31
+ }
32
+
33
+ throw new Error("Package manager could not be determined.");
34
+ }
@@ -0,0 +1,51 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { detectPackageManager } from "./pkg-utils";
5
+
6
+ export function buildStoryBook({
7
+ build,
8
+ cwd,
9
+ silent,
10
+ }: {
11
+ build?: string | boolean;
12
+ cwd: string;
13
+ silent?: boolean;
14
+ }): string | undefined {
15
+ if (build === false) {
16
+ console.log("> Skipping StoryBook Build.");
17
+ return;
18
+ }
19
+
20
+ let output = "";
21
+ if (typeof build === "string" && build.trim() !== "") {
22
+ console.log("> Building StoryBook with script: %s", build);
23
+ const pkgManager = detectPackageManager(cwd);
24
+ output = spawnSync(pkgManager, ["run", build], {
25
+ cwd,
26
+ shell: true,
27
+ stdio: silent ? undefined : "inherit",
28
+ encoding: "utf8",
29
+ }).stdout;
30
+ } else {
31
+ console.log("> Building StoryBook with storybook CLI");
32
+ output = spawnSync("npx", ["-y", "storybook", "build"], {
33
+ cwd,
34
+ stdio: silent ? undefined : "inherit",
35
+ encoding: "utf8",
36
+ }).stdout;
37
+ }
38
+
39
+ const outputDirpath = output?.split("Output directory: ").at(1)?.trim();
40
+
41
+ if (!outputDirpath || !fs.existsSync(outputDirpath)) {
42
+ console.error(`Could not find build output at '${outputDirpath}'.`);
43
+ return undefined;
44
+ }
45
+
46
+ console.log(
47
+ "> Built StoryBook to dir: '%s'.",
48
+ path.relative(cwd, outputDirpath),
49
+ );
50
+ return outputDirpath;
51
+ }
@@ -0,0 +1,115 @@
1
+ // oxlint-disable require-await
2
+ // oxlint-disable max-lines-per-function
3
+
4
+ import { spawnSync } from "node:child_process";
5
+ import * as fs from "node:fs";
6
+ import * as path from "node:path";
7
+ import { detectPackageManager } from "./pkg-utils";
8
+
9
+ export function testStoryBook({
10
+ test,
11
+ cwd,
12
+ silent,
13
+ testReportDir = ".test/report",
14
+ testCoverageDir = ".test/coverage",
15
+ }: {
16
+ test: string | boolean | undefined;
17
+ cwd: string;
18
+ silent?: boolean;
19
+ testReportDir?: string;
20
+ testCoverageDir?: string;
21
+ }): {
22
+ testCoverageDirpath: string | undefined;
23
+ testReportDirpath: string | undefined;
24
+ } {
25
+ if (test) {
26
+ try {
27
+ runTest(test, { cwd, silent, testCoverageDir, testReportDir });
28
+ } catch (error) {
29
+ console.error(error);
30
+ }
31
+ } else {
32
+ console.log("> Skipping tests");
33
+ }
34
+
35
+ const testReportDirpath = path.join(cwd, testReportDir);
36
+ const existsTestReportDirpath = fs.existsSync(testReportDirpath);
37
+
38
+ if (existsTestReportDirpath) {
39
+ console.log(
40
+ "> Test report saved at '%s'.",
41
+ path.relative(cwd, testReportDirpath),
42
+ );
43
+ } else {
44
+ console.warn("> Test report was not created'.");
45
+ }
46
+
47
+ const testCoverageDirpath = path.join(cwd, testCoverageDir);
48
+ const existsTestCoverageDirpath = fs.existsSync(testCoverageDirpath);
49
+ if (existsTestCoverageDirpath) {
50
+ console.log(
51
+ "> Test coverage saved at '%s'.",
52
+ path.relative(cwd, testCoverageDirpath),
53
+ );
54
+ } else {
55
+ console.warn("> Test coverage was not created'.");
56
+ }
57
+
58
+ return {
59
+ testCoverageDirpath: existsTestCoverageDirpath
60
+ ? testCoverageDirpath
61
+ : undefined,
62
+ testReportDirpath: existsTestReportDirpath ? testReportDirpath : undefined,
63
+ };
64
+ }
65
+
66
+ function runTest(
67
+ test: string | true,
68
+ options: {
69
+ cwd: string;
70
+ silent?: boolean;
71
+ testReportDir: string;
72
+ testCoverageDir: string;
73
+ },
74
+ ): void {
75
+ const { cwd, silent, testCoverageDir, testReportDir } = options;
76
+ if (typeof test === "string" && test.trim() !== "") {
77
+ console.log("> Testing StoryBook with script: %s", test);
78
+ const pkgManager = detectPackageManager(cwd);
79
+ spawnSync(pkgManager, ["run", test], {
80
+ cwd,
81
+ shell: true,
82
+ stdio: silent ? undefined : "inherit",
83
+ });
84
+ return;
85
+ }
86
+
87
+ console.log("> Testing StoryBook with Vitest");
88
+ spawnSync(
89
+ "npx",
90
+ [
91
+ "-y",
92
+ "vitest",
93
+ "run",
94
+ // "--silent=passed-only",
95
+ "--reporter=default",
96
+ "--reporter=html",
97
+ `--outputFile.html=${path.join(testReportDir, "index.html")}`,
98
+ "--reporter=json",
99
+ `--outputFile.json=${path.join(testReportDir, "report.json")}`,
100
+ "--coverage",
101
+ "--coverage.provider=v8",
102
+ "--coverage.reportOnFailure",
103
+ `--coverage.reportsDirectory=${testCoverageDir}`,
104
+ "--coverage.reporter=text",
105
+ "--coverage.reporter=html",
106
+ "--coverage.reporter=text-summary",
107
+ "--coverage.reporter=json-summary",
108
+ ],
109
+ {
110
+ cwd,
111
+ stdio: silent ? undefined : "inherit",
112
+ },
113
+ );
114
+ return;
115
+ }
@@ -0,0 +1,123 @@
1
+ // oxlint-disable no-useless-undefined
2
+
3
+ import type { CommandBuilder, Options } from "yargs";
4
+ import z from "zod";
5
+
6
+ declare module "zod" {
7
+ interface GlobalMeta {
8
+ alias?: string[];
9
+ hidden?: boolean;
10
+ implies?: string;
11
+ }
12
+ }
13
+
14
+ export const sharedSchemas = {
15
+ project: z
16
+ .string({ error: "ProjectID is required to match with project." })
17
+ .meta({
18
+ alias: ["p"],
19
+ description: "Project ID associated with the StoryBook.",
20
+ }),
21
+ url: z.url({ error: "URL is required to connect to the service." }).meta({
22
+ alias: ["u"],
23
+ description: "URL of the StoryBooker service.",
24
+ }),
25
+ cwd: z
26
+ .string()
27
+ .optional()
28
+ .meta({ description: "Change the working directory for the command." }),
29
+
30
+ testReportDir: z.string().optional().meta({
31
+ description: "Relative path of the test report directory to upload.",
32
+ }),
33
+ testCoverageDir: z.string().optional().meta({
34
+ description: "Relative path of the test coverage directory to upload.",
35
+ }),
36
+
37
+ authType: z
38
+ .enum(["auth-header"])
39
+ .optional()
40
+ .meta({ description: "Enable auth for outgoing requests." }),
41
+ authValue: z.string().optional().meta({
42
+ description: "Auth value set for outgoing requests.",
43
+ implies: "authType",
44
+ }),
45
+ };
46
+
47
+ export function zodSchemaToCommandBuilder(
48
+ objectSchema: z.core.$ZodObject,
49
+ ): CommandBuilder {
50
+ const builder: CommandBuilder = {};
51
+ for (const [key, schema] of Object.entries(objectSchema._zod.def.shape)) {
52
+ const meta = schema instanceof z.ZodType ? schema.meta() : undefined;
53
+
54
+ let optional = false;
55
+ try {
56
+ // @ts-expect-error undefined
57
+ const valueOrPromise = schema["~standard"].validate(undefined, {});
58
+ if (valueOrPromise instanceof Promise) {
59
+ throw new TypeError("Cannot handle async schema");
60
+ }
61
+ optional = true;
62
+ } catch {
63
+ optional = false;
64
+ }
65
+
66
+ // @ts-expect-error undefined -> never
67
+ let errorMessage = schema._zod.def.error?.(undefined);
68
+ if (typeof errorMessage === "object") {
69
+ errorMessage = errorMessage?.message;
70
+ }
71
+
72
+ let defaultValue =
73
+ schema instanceof z.core.$ZodDefault
74
+ ? schema._zod.def.defaultValue
75
+ : undefined;
76
+ if (typeof defaultValue === "function") {
77
+ defaultValue = String(defaultValue());
78
+ }
79
+
80
+ const description =
81
+ // @ts-expect-error unknown schema
82
+ // oxlint-disable-next-line no-unsafe-assignment
83
+ String(schema._zod.def.description ?? schema.description);
84
+
85
+ builder[key] = {
86
+ alias: meta?.alias,
87
+ description,
88
+ demandOption: optional ? false : (errorMessage ?? true),
89
+ type: zodSchemaTypeToYargsBuilderType(schema),
90
+ default: defaultValue,
91
+ deprecated: meta?.deprecated,
92
+ hidden: meta?.hidden,
93
+ implies: meta?.implies,
94
+ };
95
+ }
96
+
97
+ return builder;
98
+ }
99
+
100
+ function zodSchemaTypeToYargsBuilderType(
101
+ schema: z.core.$ZodType,
102
+ ): Options["type"] {
103
+ if (schema instanceof z.core.$ZodArray) {
104
+ return "array";
105
+ }
106
+ if (schema instanceof z.core.$ZodBoolean) {
107
+ return "boolean";
108
+ }
109
+ if (schema instanceof z.core.$ZodNumber) {
110
+ return "number";
111
+ }
112
+ if (schema instanceof z.core.$ZodOptional) {
113
+ return zodSchemaTypeToYargsBuilderType(schema._zod.def.innerType);
114
+ }
115
+ if (schema instanceof z.core.$ZodDefault) {
116
+ return zodSchemaTypeToYargsBuilderType(schema._zod.def.innerType);
117
+ }
118
+ if (schema instanceof z.core.$ZodUnion) {
119
+ return undefined;
120
+ }
121
+
122
+ return "string";
123
+ }
@@ -0,0 +1,72 @@
1
+ // oxlint-disable no-useless-undefined
2
+ // oxlint-disable no-nested-ternary
3
+
4
+ import * as process from "node:process";
5
+ import type { Readable } from "node:stream";
6
+ import { styleText } from "node:util";
7
+
8
+ export function toReadableStream(
9
+ readable: Readable,
10
+ filesize: number,
11
+ onProgress: (uploaded: number, total: number) => void = onUploadProgress,
12
+ ): ReadableStream {
13
+ let uploaded = 0;
14
+
15
+ return new ReadableStream({
16
+ start: (controller) => {
17
+ function onData(chunk: Buffer): void {
18
+ try {
19
+ uploaded += chunk.length;
20
+ controller.enqueue(chunk);
21
+ onProgress?.(uploaded, filesize);
22
+ } catch {
23
+ // If controller is closed, remove listeners and destroy stream
24
+ cleanup();
25
+ readable.destroy();
26
+ }
27
+ }
28
+
29
+ function onEnd(): void {
30
+ cleanup();
31
+ controller.close();
32
+ }
33
+
34
+ function onError(err: Error): void {
35
+ cleanup();
36
+ controller.error(err);
37
+ }
38
+
39
+ function cleanup(): void {
40
+ readable.off("data", onData);
41
+ readable.off("end", onEnd);
42
+ readable.off("error", onError);
43
+
44
+ console.log("");
45
+ }
46
+
47
+ readable.on("data", onData);
48
+ readable.on("end", onEnd);
49
+ readable.on("error", onError);
50
+ },
51
+
52
+ cancel: () => {
53
+ readable.destroy();
54
+ },
55
+ });
56
+ }
57
+
58
+ /** Function to handle upload progress */
59
+ export function onUploadProgress(uploaded: number, total: number): void {
60
+ const percent = ((uploaded / total) * 100).toFixed(2);
61
+ const percentStr = percent.padStart(6, " ");
62
+ const totalStr = (total / 1000).toFixed(2);
63
+ const uploadedStr = (uploaded / 1000)
64
+ .toFixed(2)
65
+ .padStart(totalStr.length, " ");
66
+ process.stdout.write(
67
+ styleText(
68
+ "dim",
69
+ `\r - Uploaded: ${uploadedStr} / ${totalStr} KB (${percentStr}%)`,
70
+ ),
71
+ );
72
+ }
@@ -0,0 +1,4 @@
1
+ import type { Client } from "openapi-fetch";
2
+ import type { paths } from "../service-schema";
3
+
4
+ export type ServiceClient = Client<paths>;
@@ -0,0 +1,75 @@
1
+ // @ts-check
2
+ /*! cross-zip. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
3
+
4
+ import { ChildProcess, execSync } from "node:child_process";
5
+ import fs from "node:fs";
6
+ import { tmpdir } from "node:os";
7
+ import path from "node:path";
8
+
9
+ const isWindows = process.platform === "win32";
10
+
11
+ /**
12
+ * Cross-platform sync zip utility that works on Windows and Unix-like systems.
13
+ * It uses the `zip` command on Unix-like systems and PowerShell on Windows.
14
+ * It throws an error if the `zip` command is not available on Unix-like systems.
15
+ */
16
+ export function zip(inPath: string, outPath: fs.PathLike): void {
17
+ if (isWindows) {
18
+ if (fs.statSync(inPath).isFile()) {
19
+ const inFile = fs.readFileSync(inPath);
20
+ const tmpPath = path.join(tmpdir(), `cross-zip-${Date.now()}`);
21
+ fs.mkdirSync(tmpPath);
22
+ fs.writeFileSync(path.join(tmpPath, path.basename(inPath)), inFile);
23
+ inPath = tmpPath;
24
+ }
25
+ fs.rmdirSync(outPath, { recursive: true, maxRetries: 3 });
26
+ }
27
+
28
+ const cwd = path.dirname(inPath);
29
+ const zipCmd = getZipCommand();
30
+ const zipCmdArgs = getZipArgs(inPath, outPath);
31
+ try {
32
+ execSync([zipCmd, ...zipCmdArgs].join(" "), {
33
+ cwd,
34
+ maxBuffer: Infinity,
35
+ encoding: "utf8",
36
+ });
37
+ } catch (error) {
38
+ if (error instanceof ChildProcess) {
39
+ console.error("STDOUT:", error.stdout);
40
+ console.error("STDERR:", error.stderr);
41
+ }
42
+ throw error;
43
+ }
44
+ }
45
+
46
+ // Unzip function to extract files from a zip archive
47
+
48
+ function getZipCommand(): string {
49
+ if (isWindows) {
50
+ return "powershell.exe";
51
+ }
52
+ return "zip";
53
+ }
54
+
55
+ function getZipArgs(inPath: fs.PathLike, outPath: fs.PathLike): string[] {
56
+ if (isWindows) {
57
+ return [
58
+ "-nologo",
59
+ "-noprofile",
60
+ "-command",
61
+ '& { param([String]$myInPath, [String]$myOutPath); Add-Type -A "System.IO.Compression.FileSystem"; [IO.Compression.ZipFile]::CreateFromDirectory($myInPath, $myOutPath); exit !$? }',
62
+ "-myInPath",
63
+ quotePath(inPath),
64
+ "-myOutPath",
65
+ quotePath(outPath),
66
+ ];
67
+ }
68
+
69
+ const dirname = path.basename(inPath.toString());
70
+ return ["-r", "-y", outPath.toString(), dirname];
71
+ }
72
+
73
+ function quotePath(pathToTransform: fs.PathLike): string {
74
+ return `"${pathToTransform.toString()}"`;
75
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "include": ["src"],
4
+ "exclude": ["node_modules", "dist"],
5
+ "compilerOptions": {
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "skipLibCheck": true
9
+ }
10
+ }
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "tsdown";
2
+
3
+ export default defineConfig({
4
+ banner: { js: "#!/usr/bin/env node" },
5
+ dts: false,
6
+ entry: ["./src/index.ts"],
7
+ platform: "node",
8
+ treeshake: true,
9
+ });