@superblocksteam/util 2.0.162-next.2 → 2.0.163-next.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.
Files changed (109) hide show
  1. package/dist/api-files/api.d.mts +8 -0
  2. package/dist/api-files/api.d.mts.map +1 -0
  3. package/dist/api-files/api.mjs +262 -0
  4. package/dist/api-files/api.mjs.map +1 -0
  5. package/dist/api-files/constants.d.ts +6 -0
  6. package/dist/api-files/constants.d.ts.map +1 -0
  7. package/dist/api-files/constants.js +9 -0
  8. package/dist/api-files/constants.js.map +1 -0
  9. package/dist/api-files/index.d.ts +8 -0
  10. package/dist/api-files/index.d.ts.map +1 -0
  11. package/dist/api-files/index.js +18 -0
  12. package/dist/api-files/index.js.map +1 -0
  13. package/dist/api-files/types.d.ts +115 -0
  14. package/dist/api-files/types.d.ts.map +1 -0
  15. package/dist/api-files/types.js +14 -0
  16. package/dist/api-files/types.js.map +1 -0
  17. package/dist/component-configs.d.ts +6 -0
  18. package/dist/component-configs.d.ts.map +1 -0
  19. package/dist/component-configs.js +178 -0
  20. package/dist/component-configs.js.map +1 -0
  21. package/dist/constants.d.ts +18 -0
  22. package/dist/constants.d.ts.map +1 -0
  23. package/dist/constants.js +37 -0
  24. package/dist/constants.js.map +1 -0
  25. package/dist/data-types.d.ts +8 -0
  26. package/dist/data-types.d.ts.map +1 -0
  27. package/dist/data-types.js +22 -0
  28. package/dist/data-types.js.map +1 -0
  29. package/dist/events.d.ts +13 -0
  30. package/dist/events.d.ts.map +1 -0
  31. package/dist/events.js +17 -0
  32. package/dist/events.js.map +1 -0
  33. package/dist/file-uploader.d.ts +3 -0
  34. package/dist/file-uploader.d.ts.map +1 -0
  35. package/dist/file-uploader.js +41 -0
  36. package/dist/file-uploader.js.map +1 -0
  37. package/dist/file-uploader.test.d.ts +2 -0
  38. package/dist/file-uploader.test.d.ts.map +1 -0
  39. package/dist/file-uploader.test.js +43 -0
  40. package/dist/file-uploader.test.js.map +1 -0
  41. package/dist/generate-component-types.d.ts +3 -0
  42. package/dist/generate-component-types.d.ts.map +1 -0
  43. package/dist/generate-component-types.js +45 -0
  44. package/dist/generate-component-types.js.map +1 -0
  45. package/dist/identifiers.d.ts +9 -0
  46. package/dist/identifiers.d.ts.map +1 -0
  47. package/dist/identifiers.js +68 -0
  48. package/dist/identifiers.js.map +1 -0
  49. package/{src/index.ts → dist/index.d.ts} +1 -0
  50. package/dist/index.d.ts.map +1 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/localGitRepoState.d.ts +20 -0
  53. package/dist/localGitRepoState.d.ts.map +1 -0
  54. package/dist/localGitRepoState.js +3 -0
  55. package/dist/localGitRepoState.js.map +1 -0
  56. package/dist/login.d.ts +17 -0
  57. package/dist/login.d.ts.map +1 -0
  58. package/dist/login.js +74 -0
  59. package/dist/login.js.map +1 -0
  60. package/dist/mask-unix-signals.d.ts +28 -0
  61. package/dist/mask-unix-signals.d.ts.map +1 -0
  62. package/dist/mask-unix-signals.js +81 -0
  63. package/dist/mask-unix-signals.js.map +1 -0
  64. package/dist/resource-configs.d.ts +73 -0
  65. package/dist/resource-configs.d.ts.map +1 -0
  66. package/dist/resource-configs.js +224 -0
  67. package/dist/resource-configs.js.map +1 -0
  68. package/dist/token-manager.d.ts +22 -0
  69. package/dist/token-manager.d.ts.map +1 -0
  70. package/dist/token-manager.js +3 -0
  71. package/dist/token-manager.js.map +1 -0
  72. package/dist/types.d.ts +105 -0
  73. package/dist/types.d.ts.map +1 -0
  74. package/dist/types.js +3 -0
  75. package/dist/types.js.map +1 -0
  76. package/dist/unreachable.d.ts +2 -0
  77. package/dist/unreachable.d.ts.map +1 -0
  78. package/dist/unreachable.js +7 -0
  79. package/dist/unreachable.js.map +1 -0
  80. package/dist/validation.d.ts +5 -0
  81. package/dist/validation.d.ts.map +1 -0
  82. package/dist/validation.js +152 -0
  83. package/dist/validation.js.map +1 -0
  84. package/package.json +5 -2
  85. package/eslint.config.mjs +0 -67
  86. package/lint-staged.config.mjs +0 -6
  87. package/src/api-files/api.mts +0 -506
  88. package/src/api-files/constants.ts +0 -6
  89. package/src/api-files/index.ts +0 -22
  90. package/src/api-files/types.ts +0 -131
  91. package/src/component-configs.ts +0 -163
  92. package/src/constants.ts +0 -34
  93. package/src/data-types.ts +0 -25
  94. package/src/events.ts +0 -12
  95. package/src/file-uploader.test.ts +0 -69
  96. package/src/file-uploader.ts +0 -42
  97. package/src/generate-component-types.ts +0 -53
  98. package/src/identifiers.ts +0 -70
  99. package/src/localGitRepoState.ts +0 -20
  100. package/src/login.ts +0 -82
  101. package/src/mask-unix-signals.ts +0 -86
  102. package/src/resource-configs.ts +0 -356
  103. package/src/token-manager.ts +0 -26
  104. package/src/types.ts +0 -138
  105. package/src/unreachable.ts +0 -5
  106. package/src/validation.ts +0 -155
  107. package/tsconfig.json +0 -21
  108. package/turbo.json +0 -9
  109. package/vitest.config.ts +0 -7
@@ -1,163 +0,0 @@
1
- import { execFile } from "node:child_process";
2
- import os from "node:os";
3
- import path from "node:path";
4
- import * as util from "node:util";
5
-
6
- import fs from "fs-extra";
7
- import mimeTypes from "mime-types";
8
-
9
- import { CUSTOM_COMPONENTS_PATH } from "./constants.js";
10
- import { generateComponentTypesFile } from "./generate-component-types.js";
11
- import { validateCustomComponents } from "./validation.js";
12
-
13
- const asyncExecFile = util.promisify(execFile);
14
-
15
- async function compileTypeScript(
16
- inputFilePath: string,
17
- outputDir: string,
18
- ): Promise<{ output?: string }> {
19
- try {
20
- const tscPath = require.resolve("typescript/bin/tsc");
21
- if (tscPath) {
22
- const result = await asyncExecFile("node", [
23
- tscPath,
24
- path.resolve(inputFilePath),
25
- "--outDir",
26
- outputDir,
27
- "--jsx",
28
- "react-jsx",
29
- "--target",
30
- "es2019",
31
- "--esModuleInterop",
32
- "true",
33
- "--moduleResolution",
34
- "node",
35
- "--module",
36
- "es2022",
37
- "--strict",
38
- "true",
39
- "--skipLibCheck",
40
- "true",
41
- ]);
42
- if (result.stdout || result.stderr) {
43
- return {
44
- output: result.stdout || result.stderr,
45
- };
46
- }
47
- }
48
- } catch (error: any) {
49
- return {
50
- output: error.stdout || error.stderr,
51
- };
52
- }
53
- return {};
54
- }
55
-
56
- async function getFolderPaths() {
57
- try {
58
- const folderPaths = await fs.readdir(CUSTOM_COMPONENTS_PATH, {
59
- withFileTypes: true,
60
- });
61
- // filter out any non-directory items
62
- const directories = folderPaths.filter(
63
- (dirent) =>
64
- dirent.isDirectory() ||
65
- (dirent.isSymbolicLink() &&
66
- fs
67
- .statSync(path.join(CUSTOM_COMPONENTS_PATH, dirent.name))
68
- .isDirectory()),
69
- );
70
- // map each directory to its path
71
- const folderPathsArray = directories.map((dirent) =>
72
- path.join(CUSTOM_COMPONENTS_PATH, dirent.name),
73
- );
74
- return folderPathsArray;
75
- } catch {
76
- throw new Error(
77
- "Could not access component directory, check your permissions",
78
- );
79
- }
80
- }
81
-
82
- export async function getComponentConfigs(
83
- generateTypesFiles: boolean,
84
- ): Promise<{
85
- configs: Record<string, any>;
86
- hasError: boolean;
87
- }> {
88
- const folderPaths = await getFolderPaths();
89
- let hasError = false;
90
- const promiseConfigFiles = folderPaths.map(async (ccpath) => {
91
- const absPath = path.resolve(ccpath);
92
- try {
93
- const config = await fs.readJSON(`${absPath}/config.json`);
94
- console.log(`Found component in ${absPath}`);
95
- return { ccpath, config };
96
- } catch {
97
- // Noop because there is a fallback
98
- }
99
- // This is a tad complex to explain this hack but here's the context:
100
- // We want to be able to hot reload the config.ts file, but we can't import it directly
101
- // because it is going to get cached in the module cache and we dont have access to it to clear it
102
- // so instead we copy the file to a new location with a random name and import that
103
- // this is technically a memory leak, but you shouldnt really have a hot module server running forever
104
- const rawPath = `${ccpath}/config.ts`;
105
- const fileName = path.resolve(rawPath);
106
- const outputDirectory = path.resolve(
107
- `${os.tmpdir()}/superblocks/${ccpath}/config.timestamp-${Date.now()}-${Math.random()
108
- .toString(16)
109
- .slice(2)}`,
110
- );
111
- await fs.ensureDir(outputDirectory);
112
- const compilationResult = await compileTypeScript(
113
- fileName,
114
- outputDirectory,
115
- );
116
- if (compilationResult.output) {
117
- hasError = true;
118
- if (compilationResult.output.includes(`Cannot find module`)) {
119
- throw new Error(
120
- "Could not find a module in node_modules. You likely need to run 'npm install'",
121
- );
122
- }
123
- throw new Error(compilationResult.output);
124
- }
125
- console.log(`Typescript compiled to ${ccpath}`);
126
- const config = (await import(outputDirectory + "/config.js")).default;
127
-
128
- const isValid = validateCustomComponents(config);
129
- if (!isValid.valid) {
130
- // Not throwing because we don't need a stack trace here
131
- console.log(`Invalid config.ts file found`);
132
- console.log(isValid.message);
133
- hasError = true;
134
- }
135
- return { ccpath, config };
136
- });
137
- const configFiles = await Promise.all(promiseConfigFiles);
138
- if (generateTypesFiles) {
139
- for (const { config, ccpath } of configFiles) {
140
- const typesFileContents = generateComponentTypesFile(config);
141
- await fs.writeFile(`${ccpath}/types.ts`, typesFileContents);
142
- console.log(`Generated ${ccpath}/types.ts`);
143
- }
144
- }
145
- const configs = configFiles
146
- .filter((component) => component.config !== null)
147
- .reduce((acc: Record<string, any>, { config }) => {
148
- acc[config.id] = config;
149
- return acc;
150
- }, {});
151
- return {
152
- configs,
153
- hasError,
154
- };
155
- }
156
-
157
- export const getContentType = (filename: string): string => {
158
- const mimeType = mimeTypes.lookup(filename);
159
- if (!mimeType) {
160
- return "";
161
- }
162
- return mimeType as string;
163
- };
package/src/constants.ts DELETED
@@ -1,34 +0,0 @@
1
- export const SUPERBLOCKS_HOME_FOLDER_NAME = ".superblocks";
2
- export const TOKEN_CONFIG_PATH = ".superblocks/auth.json";
3
- export const RESOURCE_CONFIG_PATH = ".superblocks/superblocks.json";
4
- export const DEV_ENVIRONMENT_CONFIG_PATH = ".superblocks/dev-environment.json";
5
-
6
- export const CUSTOM_COMPONENTS_PATH = "components";
7
-
8
- export class FileAccessError extends Error {
9
- constructor(message: string) {
10
- super(message);
11
- this.name = "FileAccessError";
12
- }
13
- }
14
-
15
- export class NotFoundError extends Error {
16
- constructor(message: string) {
17
- super(message);
18
- this.name = "FileNotFoundError";
19
- }
20
- }
21
-
22
- export class BadRequestError extends Error {
23
- constructor(message: string) {
24
- super(message);
25
- this.name = "BadRequestError";
26
- }
27
- }
28
-
29
- export class ForbiddenError extends Error {
30
- constructor(message: string) {
31
- super(message);
32
- this.name = "ForbiddenError";
33
- }
34
- }
package/src/data-types.ts DELETED
@@ -1,25 +0,0 @@
1
- export type DataType = "string" | "number" | "boolean" | "any";
2
-
3
- interface TypeInfo {
4
- tsType: string;
5
- prompt: string;
6
- }
7
-
8
- export const dataTypeDefinions: Record<DataType, TypeInfo> = {
9
- string: {
10
- tsType: "string",
11
- prompt: "string",
12
- },
13
- number: {
14
- tsType: "number",
15
- prompt: "number",
16
- },
17
- boolean: {
18
- tsType: "boolean",
19
- prompt: "boolean",
20
- },
21
- any: {
22
- tsType: "any",
23
- prompt: "any (raw js expression)",
24
- },
25
- };
package/src/events.ts DELETED
@@ -1,12 +0,0 @@
1
- export enum ComponentEvent {
2
- CREATE = "create",
3
- INIT = "init",
4
- LOGIN = "login",
5
- MIGRATE = "migrate",
6
- PULL = "pull",
7
- COMMITS = "commits",
8
- PUSH = "push",
9
- REGISTER = "register",
10
- UPLOAD = "upload",
11
- DEPLOY = "deploy",
12
- }
@@ -1,69 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
-
3
- import { getBucketeerUrlFromSuperblocksUrl } from "./file-uploader.js";
4
-
5
- describe("getBucketeerUrlFromSuperblocksUrl", () => {
6
- let originalUrl: string | undefined;
7
-
8
- beforeEach(() => {
9
- originalUrl = process.env.SUPERBLOCKS_BUCKETEER_URL;
10
- delete process.env.SUPERBLOCKS_BUCKETEER_URL;
11
- });
12
-
13
- afterEach(() => {
14
- if (originalUrl === undefined) {
15
- delete process.env.SUPERBLOCKS_BUCKETEER_URL;
16
- } else {
17
- process.env.SUPERBLOCKS_BUCKETEER_URL = originalUrl;
18
- }
19
- });
20
-
21
- it("returns the default localhost bucketeer URL for localhost servers", () => {
22
- expect(getBucketeerUrlFromSuperblocksUrl("http://localhost:8080")).toBe(
23
- "http://localhost:8030",
24
- );
25
- expect(getBucketeerUrlFromSuperblocksUrl("http://127.0.0.1:8080")).toBe(
26
- "http://localhost:8030",
27
- );
28
- });
29
-
30
- it("honors SUPERBLOCKS_BUCKETEER_URL for localhost servers (slot-aware worktrees)", () => {
31
- process.env.SUPERBLOCKS_BUCKETEER_URL = "http://localhost:8330";
32
- expect(getBucketeerUrlFromSuperblocksUrl("http://localhost:8380")).toBe(
33
- "http://localhost:8330",
34
- );
35
- expect(getBucketeerUrlFromSuperblocksUrl("http://127.0.0.1:8380")).toBe(
36
- "http://localhost:8330",
37
- );
38
- });
39
-
40
- it("falls back to localhost default when the input URL is unparseable", () => {
41
- expect(getBucketeerUrlFromSuperblocksUrl("not a url")).toBe(
42
- "http://localhost:8030",
43
- );
44
- });
45
-
46
- it("uses SUPERBLOCKS_BUCKETEER_URL when the input URL is unparseable", () => {
47
- process.env.SUPERBLOCKS_BUCKETEER_URL = "http://localhost:8330";
48
- expect(getBucketeerUrlFromSuperblocksUrl("not a url")).toBe(
49
- "http://localhost:8330",
50
- );
51
- });
52
-
53
- it("ignores SUPERBLOCKS_BUCKETEER_URL for non-localhost hostnames", () => {
54
- process.env.SUPERBLOCKS_BUCKETEER_URL = "http://localhost:8330";
55
-
56
- expect(
57
- getBucketeerUrlFromSuperblocksUrl("https://app.superblocks.com"),
58
- ).toBe("https://bucketeer.superblocks.com");
59
- expect(
60
- getBucketeerUrlFromSuperblocksUrl("https://staging.superblocks.com"),
61
- ).toBe("https://bucketeer.staging.superblocks.com");
62
- expect(
63
- getBucketeerUrlFromSuperblocksUrl("https://eu.superblocks.com"),
64
- ).toBe("https://bucketeer.eu.superblocks.com");
65
- expect(
66
- getBucketeerUrlFromSuperblocksUrl("https://pr-123.superblocks.dev"),
67
- ).toBe("https://bucketeer.dev.superblocks.com");
68
- });
69
- });
@@ -1,42 +0,0 @@
1
- export const COMPONENT_EVENT_HEADER = "x-superblocks-component-event";
2
-
3
- // SUPERBLOCKS_BUCKETEER_URL lets slot-N Tilt worktrees override the default
4
- // localhost bucketeer URL (http://localhost:8030) when the server runs on a
5
- // different slot offset. The local-orch Tilt profile injects this variable
6
- // for the server's serve env.
7
- const localhostBucketeerUrl = (): string =>
8
- process.env.SUPERBLOCKS_BUCKETEER_URL ?? "http://localhost:8030";
9
-
10
- // retrieves bucketeer base URL given the superblocks server base URL
11
- export const getBucketeerUrlFromSuperblocksUrl = (
12
- superblocksBaseUrl: string,
13
- ): string => {
14
- let hostname: string;
15
- try {
16
- hostname = new URL(superblocksBaseUrl).hostname;
17
- } catch {
18
- return localhostBucketeerUrl();
19
- }
20
-
21
- if (/^pr-.*\.superblocks\.dev$/u.test(hostname)) {
22
- return "https://bucketeer.dev.superblocks.com";
23
- }
24
-
25
- switch (hostname) {
26
- case "eu.superblocks.com":
27
- case "prod-eu.superblocks.com":
28
- return "https://bucketeer.eu.superblocks.com";
29
- case "app.staging.superblocks.com":
30
- case "staging.superblocks.com":
31
- return "https://bucketeer.staging.superblocks.com";
32
- case "app.superblocks.com":
33
- case "app.superblockshq.com":
34
- return "https://bucketeer.superblocks.com";
35
- case "dev.superblocks.com":
36
- return "https://bucketeer.dev.superblocks.com";
37
- case "127.0.0.1":
38
- case "localhost":
39
- default:
40
- return localhostBucketeerUrl();
41
- }
42
- };
@@ -1,53 +0,0 @@
1
- import { dataTypeDefinions, type DataType } from "./data-types.js";
2
- import type { ComponentConfig } from "./types.js";
3
-
4
- const indent = (str: string, spaces: number) => {
5
- const spacesString = " ".repeat(spaces);
6
-
7
- return str
8
- .split("\n")
9
- .map((line) => `${spacesString}${line}`)
10
- .join("\n");
11
- };
12
-
13
- function renderInputTypeAsTS(
14
- name: string,
15
- type: DataType,
16
- trailingChars: string,
17
- indentSpaces: number,
18
- ) {
19
- let { tsType } = dataTypeDefinions[type];
20
- if (["number", "string"].includes(type)) {
21
- tsType += " | null";
22
- }
23
- return indent(`${name}: ${tsType}${trailingChars}`, indentSpaces);
24
- }
25
-
26
- export function generateComponentTypesFile(config: ComponentConfig) {
27
- const properties = config.properties ?? [];
28
- const eventHandlers = config.events ?? [];
29
- return `// GENERATED CODE -- DO NOT EDIT!
30
- //
31
- // This file is automatically generated by the Superblocks CLI
32
- // Use the \`superblocks components watch\` command to regenerate this file from its source in config.ts
33
-
34
- // All properties of your component are defined here.
35
- // These are the properties which are surfaced in the Superblocks properties panel
36
- // and can be referenced throughout your Superblocks Application
37
- export type Props = {
38
- ${properties
39
- .map((v) => renderInputTypeAsTS(v.path, v.dataType, ";", 2) + "\n")
40
- .join("")}};
41
-
42
- export type EventTriggers = {
43
- // Call the subsequent function(s) to trigger event(s) in Superblocks from your component.
44
- // These events can be wired up to event handlers in your Superblocks App
45
- ${
46
- eventHandlers.length === 0
47
- ? " // Note: no event handlers defined\n"
48
- : eventHandlers
49
- .map((v: any) => indent(v.path, 2) + ": () => void;" + "\n")
50
- .join("")
51
- }};
52
- `;
53
- }
@@ -1,70 +0,0 @@
1
- // convert a string to upper camel case, e.g. "123 & this is my component, foo-bar" -> "123ThisIsMyComponentFooBar"
2
- function wordsToUpperCamelCase(str: string) {
3
- return str
4
- .split(/[^A-Za-z0-9_]+/)
5
- .filter(Boolean)
6
- .map((word) => word[0].toUpperCase() + word.slice(1))
7
- .join("");
8
- }
9
-
10
- const digits = "Zero One Two Three Four Five Six Seven Eight Nine".split(" ");
11
-
12
- // convert digits to words, e.g. "123" -> "OneTwoThree"
13
- function digitsToWords(str: string) {
14
- return str.replace(/\d/g, (char) => digits[parseInt(char, 10)] ?? char);
15
- }
16
-
17
- /**
18
- * Suggest an identifier based on a label, e.g. `My Component` -> `myComponent`.
19
- * @param label The label to suggest an identifier for.
20
- * @param capitalizeFirst Whether to capitalize the first letter of the identifier.
21
- */
22
- export function suggestIdentifier(label: string, capitalizeFirst = false) {
23
- const upperCamelCase = wordsToUpperCamelCase(label)
24
- // convert any digits at the start of the string to words as these are not valid in identifiers
25
- .replace(/^\d+/, digitsToWords);
26
- if (capitalizeFirst) {
27
- return upperCamelCase;
28
- } else {
29
- return upperCamelCase[0].toLowerCase() + upperCamelCase.slice(1);
30
- }
31
- }
32
-
33
- const IdentifierRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
34
-
35
- export function isValidIdentifier(str: string) {
36
- return IdentifierRegex.test(str);
37
- }
38
-
39
- const reservedPropertyNames = new Set<string>([
40
- "ENTITY_TYPE",
41
- "bindingPaths",
42
- "bottomRow",
43
- "dynamicBindingPathList",
44
- "dynamicTriggerPathList",
45
- "gridColumns",
46
- "gridRows",
47
- "height",
48
- "isLoading",
49
- "isVisible",
50
- "leftColumn",
51
- "parentId",
52
- "rightColumn",
53
- "snapColumns",
54
- "snapRows",
55
- "top",
56
- "topRow",
57
- "type",
58
- "widgetId",
59
- "widgetLastChange",
60
- "widgetName",
61
- "width",
62
- ]);
63
-
64
- export function isReservedPropertyName(str: string) {
65
- return (
66
- reservedPropertyNames.has(str) ||
67
- // also treat any property name starting with "default_" as reserved since we use this prefix for default properties
68
- str.startsWith("default_")
69
- );
70
- }
@@ -1,20 +0,0 @@
1
- import type { ComponentEvent } from "./events.js";
2
-
3
- export type LocalGitRepoState =
4
- | { status: "NO_GIT" }
5
- | { status: "DETACHED" }
6
- | {
7
- status: "IN_A_BRANCH";
8
- localBranchName: string;
9
- upstream?: {
10
- remoteName: string;
11
- url: string;
12
- pushUrl?: string | null;
13
- branchName: string;
14
- };
15
- };
16
-
17
- export interface ValidateGitSetupRequestBody {
18
- gitState: LocalGitRepoState;
19
- event?: ComponentEvent;
20
- }
package/src/login.ts DELETED
@@ -1,82 +0,0 @@
1
- import { homedir } from "node:os";
2
- import { dirname, join } from "node:path";
3
-
4
- import fs from "fs-extra";
5
-
6
- import { FileAccessError, TOKEN_CONFIG_PATH } from "./constants.js";
7
-
8
- type BaseUrl = {
9
- superblocksBaseUrl: string;
10
- };
11
-
12
- type TokenWithBaseUrl = BaseUrl & {
13
- token: string;
14
- };
15
-
16
- /**
17
- * Returns the resolved auth file path, honoring the SUPERBLOCKS_AUTH_FILE
18
- * environment variable. Both reads and writes MUST use this so the CLI
19
- * never writes to one file and reads from another.
20
- */
21
- export function resolveAuthFilePath(): string {
22
- return (
23
- process.env.SUPERBLOCKS_AUTH_FILE || join(homedir(), TOKEN_CONFIG_PATH)
24
- );
25
- }
26
-
27
- export async function saveApiToken(superblocksBaseUrl: string, token?: string) {
28
- try {
29
- const authFile = resolveAuthFilePath();
30
- await fs.ensureDir(dirname(authFile));
31
- if (token) {
32
- const tokenConfig: TokenWithBaseUrl = {
33
- superblocksBaseUrl,
34
- token,
35
- };
36
- await fs.writeJSON(authFile, tokenConfig);
37
- } else {
38
- const tokenConfig: BaseUrl = {
39
- superblocksBaseUrl,
40
- };
41
- await fs.writeJSON(authFile, tokenConfig);
42
- }
43
- } catch {
44
- throw new FileAccessError("Could not save token");
45
- }
46
- }
47
-
48
- export async function getLocalTokenWithUrlIfExists(): Promise<
49
- TokenWithBaseUrl | BaseUrl | undefined
50
- > {
51
- try {
52
- return await getLocalTokenWithUrl();
53
- } catch {
54
- return undefined;
55
- }
56
- }
57
-
58
- export async function getLocalTokenWithUrl(): Promise<
59
- TokenWithBaseUrl | BaseUrl
60
- > {
61
- try {
62
- const tokenConfig: TokenWithBaseUrl = await fs.readJSON(
63
- resolveAuthFilePath(),
64
- );
65
- const superblocksBaseUrl =
66
- process.env.SUPERBLOCKS_BASE_URL || tokenConfig.superblocksBaseUrl;
67
- if (tokenConfig.token) {
68
- // user logged in
69
- return {
70
- token: tokenConfig.token,
71
- superblocksBaseUrl,
72
- };
73
- } else {
74
- // user not logged in, but set domain
75
- return {
76
- superblocksBaseUrl,
77
- };
78
- }
79
- } catch {
80
- throw new Error("No local API key found");
81
- }
82
- }
@@ -1,86 +0,0 @@
1
- const unixSignalsToMask: NodeJS.Signals[] = ["SIGINT", "SIGTERM", "SIGHUP"];
2
-
3
- // The exit code that is used when the process is aborted due to a unix signal
4
- const exitCode = 254;
5
-
6
- type ActionResult<T> =
7
- | { success: true; returnValue: T }
8
- | { success: false; error: unknown };
9
-
10
- /**
11
- * Runs an action with some unix signals being masked. This is useful for actions that should not be interrupted and need
12
- * to be allowed to complete or perform some cleanup. If a signal is received, the action will be notified that it should
13
- * abort via the `AbortSignal` that is passed to it. The action may or may not choose to abort immediately. The cleanup
14
- * action is called after the main action and this happens unconditionally, i.e. regardless of whether a signal was received,
15
- * the main action threw an error, or the main action returned a value. The cleanup action is passed the abort signal and the
16
- * error (if any) that was thrown by the main action. If a signal was received, the process will exit after the cleanup
17
- * action has completed. If the main action threw an error, that error will be thrown again. Otherwise, the main action's
18
- * return value is returned. This function can be thought of as a (unix) signal aware version of `try { … } finally { … }`
19
- * and `try { … } catch { … } catch { … }`.
20
- *
21
- * *Note*: This function registers listeners for some unix signals, so it affects the global behavior of the process. Be
22
- * careful when using this function in a library.
23
- *
24
- * @param action - The main action to execute.
25
- * @param cleanup - An optional cleanup action to execute when the process is aborted.
26
- * @returns The result of the `action`.
27
- */
28
- export async function maskUnixSignals<T>(
29
- action: (aborted: AbortSignal) => Promise<T>,
30
- cleanup?: (
31
- aborted: AbortSignal,
32
- actionResult: ActionResult<T>,
33
- ) => Promise<void>,
34
- ): Promise<T> {
35
- const abortController = new AbortController();
36
- const abortSignal = abortController.signal;
37
-
38
- const onUnixSignal = (unixSignal: NodeJS.Signals) => {
39
- console.log(`${unixSignal} received, will abort after cleanup`);
40
- if (unixSignal === "SIGINT") {
41
- if (!abortSignal.aborted) {
42
- console.log("Press Ctrl+C again to force abort");
43
- } else {
44
- console.log("Second SIGINT received, will abort immediately");
45
- process.exit(exitCode);
46
- }
47
- }
48
- // Pass the signal as the reason for aborting so that `action` and `cleanup` can know which signal was received
49
- abortController.abort(unixSignal);
50
- };
51
-
52
- for (const signal of unixSignalsToMask) {
53
- process.on(signal, onUnixSignal);
54
- }
55
- try {
56
- let result: ActionResult<T>;
57
- try {
58
- const returnValue = await action(abortSignal);
59
- result = { success: true, returnValue };
60
- } catch (err) {
61
- result = { success: false, error: err };
62
- }
63
- try {
64
- await cleanup?.(abortSignal, result);
65
- } finally {
66
- // If the process received a (unix) signal, this takes precedence over any error that was thrown by `action` or
67
- // `cleanup`. So we exit here. Otherwise, we let standard JS error handling take over, e.g. an exception
68
- // thrown by `cleanup` shadows any exception that was thrown by `action`.
69
- if (abortSignal.aborted) {
70
- // The process received a (unix) signal, so we exit here.
71
- process.exit(exitCode);
72
- }
73
- }
74
- if (!result.success) {
75
- // `action` threw an error
76
- throw result.error;
77
- } else {
78
- // `action` threw an error
79
- return result.returnValue;
80
- }
81
- } finally {
82
- for (const signal of unixSignalsToMask) {
83
- process.off(signal, onUnixSignal);
84
- }
85
- }
86
- }