@project-ajax/sdk 0.0.28

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 (145) hide show
  1. package/README.md +22 -0
  2. package/dist/block.d.ts +321 -0
  3. package/dist/block.d.ts.map +1 -0
  4. package/dist/block.js +0 -0
  5. package/dist/builder.d.ts +12 -0
  6. package/dist/builder.d.ts.map +1 -0
  7. package/dist/builder.js +22 -0
  8. package/dist/capabilities/slashCommand.d.ts +56 -0
  9. package/dist/capabilities/slashCommand.d.ts.map +1 -0
  10. package/dist/capabilities/slashCommand.js +32 -0
  11. package/dist/capabilities/sync.d.ts +58 -0
  12. package/dist/capabilities/sync.d.ts.map +1 -0
  13. package/dist/capabilities/sync.js +26 -0
  14. package/dist/capabilities/tool.d.ts +69 -0
  15. package/dist/capabilities/tool.d.ts.map +1 -0
  16. package/dist/capabilities/tool.js +75 -0
  17. package/dist/capabilities/tool.test.d.ts +2 -0
  18. package/dist/capabilities/tool.test.d.ts.map +1 -0
  19. package/dist/cli/api/client.d.ts +185 -0
  20. package/dist/cli/api/client.d.ts.map +1 -0
  21. package/dist/cli/api/client.js +315 -0
  22. package/dist/cli/api/result.d.ts +43 -0
  23. package/dist/cli/api/result.d.ts.map +1 -0
  24. package/dist/cli/api/result.js +43 -0
  25. package/dist/cli/bin/cli.d.ts +3 -0
  26. package/dist/cli/bin/cli.d.ts.map +1 -0
  27. package/dist/cli/bin/cli.js +5 -0
  28. package/dist/cli/commands/auth.d.ts +2 -0
  29. package/dist/cli/commands/auth.d.ts.map +1 -0
  30. package/dist/cli/commands/auth.impl.d.ts +10 -0
  31. package/dist/cli/commands/auth.impl.d.ts.map +1 -0
  32. package/dist/cli/commands/auth.impl.js +68 -0
  33. package/dist/cli/commands/auth.js +71 -0
  34. package/dist/cli/commands/bundle.d.ts +2 -0
  35. package/dist/cli/commands/bundle.d.ts.map +1 -0
  36. package/dist/cli/commands/bundle.impl.d.ts +2 -0
  37. package/dist/cli/commands/bundle.impl.d.ts.map +1 -0
  38. package/dist/cli/commands/bundle.impl.js +21 -0
  39. package/dist/cli/commands/bundle.js +23 -0
  40. package/dist/cli/commands/capabilities.d.ts +2 -0
  41. package/dist/cli/commands/capabilities.d.ts.map +1 -0
  42. package/dist/cli/commands/capabilities.impl.d.ts +3 -0
  43. package/dist/cli/commands/capabilities.impl.d.ts.map +1 -0
  44. package/dist/cli/commands/capabilities.impl.js +40 -0
  45. package/dist/cli/commands/capabilities.js +24 -0
  46. package/dist/cli/commands/deploy.d.ts +3 -0
  47. package/dist/cli/commands/deploy.d.ts.map +1 -0
  48. package/dist/cli/commands/deploy.impl.d.ts +2 -0
  49. package/dist/cli/commands/deploy.impl.d.ts.map +1 -0
  50. package/dist/cli/commands/deploy.impl.js +31 -0
  51. package/dist/cli/commands/deploy.js +16 -0
  52. package/dist/cli/commands/exec.d.ts +3 -0
  53. package/dist/cli/commands/exec.d.ts.map +1 -0
  54. package/dist/cli/commands/exec.impl.d.ts +7 -0
  55. package/dist/cli/commands/exec.impl.d.ts.map +1 -0
  56. package/dist/cli/commands/exec.impl.js +122 -0
  57. package/dist/cli/commands/exec.js +30 -0
  58. package/dist/cli/commands/runs.d.ts +2 -0
  59. package/dist/cli/commands/runs.d.ts.map +1 -0
  60. package/dist/cli/commands/runs.impl.d.ts +4 -0
  61. package/dist/cli/commands/runs.impl.d.ts.map +1 -0
  62. package/dist/cli/commands/runs.impl.js +71 -0
  63. package/dist/cli/commands/runs.js +45 -0
  64. package/dist/cli/commands/secrets.d.ts +2 -0
  65. package/dist/cli/commands/secrets.d.ts.map +1 -0
  66. package/dist/cli/commands/secrets.impl.d.ts +5 -0
  67. package/dist/cli/commands/secrets.impl.d.ts.map +1 -0
  68. package/dist/cli/commands/secrets.impl.js +93 -0
  69. package/dist/cli/commands/secrets.js +64 -0
  70. package/dist/cli/config.d.ts +38 -0
  71. package/dist/cli/config.d.ts.map +1 -0
  72. package/dist/cli/config.js +133 -0
  73. package/dist/cli/context.d.ts +15 -0
  74. package/dist/cli/context.d.ts.map +1 -0
  75. package/dist/cli/context.js +16 -0
  76. package/dist/cli/deploy.d.ts +25 -0
  77. package/dist/cli/deploy.d.ts.map +1 -0
  78. package/dist/cli/deploy.js +101 -0
  79. package/dist/cli/flags.d.ts +16 -0
  80. package/dist/cli/flags.d.ts.map +1 -0
  81. package/dist/cli/flags.js +24 -0
  82. package/dist/cli/handler.d.ts +14 -0
  83. package/dist/cli/handler.d.ts.map +1 -0
  84. package/dist/cli/handler.js +30 -0
  85. package/dist/cli/routes.d.ts +2 -0
  86. package/dist/cli/routes.d.ts.map +1 -0
  87. package/dist/cli/routes.js +44 -0
  88. package/dist/cli/utils/array.d.ts +2 -0
  89. package/dist/cli/utils/array.d.ts.map +1 -0
  90. package/dist/cli/utils/array.js +10 -0
  91. package/dist/cli/utils/string.d.ts +2 -0
  92. package/dist/cli/utils/string.d.ts.map +1 -0
  93. package/dist/cli/utils/string.js +12 -0
  94. package/dist/cli/writer.d.ts +48 -0
  95. package/dist/cli/writer.d.ts.map +1 -0
  96. package/dist/cli/writer.js +73 -0
  97. package/dist/error.d.ts +8 -0
  98. package/dist/error.d.ts.map +1 -0
  99. package/dist/error.js +11 -0
  100. package/dist/index.d.ts +4 -0
  101. package/dist/index.d.ts.map +1 -0
  102. package/dist/index.js +8 -0
  103. package/dist/schema.d.ts +45 -0
  104. package/dist/schema.d.ts.map +1 -0
  105. package/dist/schema.js +14 -0
  106. package/dist/types.d.ts +11 -0
  107. package/dist/types.d.ts.map +1 -0
  108. package/dist/types.js +0 -0
  109. package/package.json +79 -0
  110. package/src/block.ts +529 -0
  111. package/src/builder.ts +26 -0
  112. package/src/capabilities/slashCommand.ts +71 -0
  113. package/src/capabilities/sync.ts +76 -0
  114. package/src/capabilities/tool.test.ts +181 -0
  115. package/src/capabilities/tool.ts +145 -0
  116. package/src/cli/api/client.ts +588 -0
  117. package/src/cli/api/result.ts +71 -0
  118. package/src/cli/bin/cli.ts +7 -0
  119. package/src/cli/commands/auth.impl.ts +92 -0
  120. package/src/cli/commands/auth.ts +77 -0
  121. package/src/cli/commands/bundle.impl.ts +21 -0
  122. package/src/cli/commands/bundle.ts +23 -0
  123. package/src/cli/commands/capabilities.impl.ts +47 -0
  124. package/src/cli/commands/capabilities.ts +25 -0
  125. package/src/cli/commands/deploy.impl.ts +34 -0
  126. package/src/cli/commands/deploy.ts +16 -0
  127. package/src/cli/commands/exec.impl.ts +169 -0
  128. package/src/cli/commands/exec.ts +32 -0
  129. package/src/cli/commands/runs.impl.ts +87 -0
  130. package/src/cli/commands/runs.ts +49 -0
  131. package/src/cli/commands/secrets.impl.ts +124 -0
  132. package/src/cli/commands/secrets.ts +73 -0
  133. package/src/cli/config.ts +175 -0
  134. package/src/cli/context.ts +26 -0
  135. package/src/cli/deploy.ts +175 -0
  136. package/src/cli/flags.ts +43 -0
  137. package/src/cli/handler.ts +62 -0
  138. package/src/cli/routes.ts +43 -0
  139. package/src/cli/utils/array.ts +7 -0
  140. package/src/cli/utils/string.ts +9 -0
  141. package/src/cli/writer.ts +97 -0
  142. package/src/error.ts +12 -0
  143. package/src/index.ts +3 -0
  144. package/src/schema.ts +54 -0
  145. package/src/types.ts +10 -0
@@ -0,0 +1,124 @@
1
+ import { Result } from "../api/result.js";
2
+ import type { FormatFlags, GlobalFlags } from "../flags.js";
3
+ import { buildAuthedHandler } from "../handler.js";
4
+ import { chunkEvery } from "../utils/array.js";
5
+ import { pluralize } from "../utils/string.js";
6
+
7
+ export const setSecrets = buildAuthedHandler(async function (
8
+ _flags: GlobalFlags,
9
+ ...args: string[]
10
+ ) {
11
+ const secrets = parseSecretArgs(args);
12
+ const { workerId } = this.config;
13
+
14
+ if (!workerId) {
15
+ throw new Error(
16
+ "No worker configured. Run 'workers deploy' first to create a worker.",
17
+ );
18
+ }
19
+
20
+ this.writer.writeErr(`Setting ${pluralize(secrets.length, "secret")}...`);
21
+
22
+ const result = await this.apiClient.upsertSecrets(workerId, secrets);
23
+
24
+ if (Result.isSuccess(result)) {
25
+ for (const secret of Result.unwrap(result).secrets) {
26
+ this.writer.writeErr(`Set secret "${secret.key}"`);
27
+ }
28
+ } else {
29
+ this.writer.writeErr(`✗ Failed to set secrets`);
30
+ this.writer.writeErr(`✗ ${result.error.message}`);
31
+ throw new Error(result.error.message);
32
+ }
33
+ });
34
+
35
+ export const listSecrets = buildAuthedHandler(async function (
36
+ flags: FormatFlags,
37
+ ) {
38
+ const { workerId } = this.config;
39
+ if (!workerId) {
40
+ throw new Error(
41
+ "No worker configured. Run 'workers deploy' first to create a worker.",
42
+ );
43
+ }
44
+
45
+ this.process.stderr.write(`Fetching secrets...`);
46
+
47
+ const result = await this.apiClient.listSecrets(workerId);
48
+
49
+ if (Result.isSuccess(result)) {
50
+ this.process.stderr.write("OK\n\n");
51
+
52
+ const data = Result.unwrap(result);
53
+ if (data.secrets.length === 0) {
54
+ this.writer.writeErr("No secrets for this worker.");
55
+ } else {
56
+ this.writer.writeTableOut({
57
+ headers: ["Key", "Created At"],
58
+ rows: data.secrets.map((secret) => [secret.key, secret.createdAt]),
59
+ plain: flags.plain,
60
+ });
61
+ }
62
+ } else {
63
+ this.process.stderr.write("ERROR\n\n");
64
+
65
+ this.writer.writeErr(`✗ Failed to list secrets`);
66
+ this.writer.writeErr(`✗ ${result.error.message}`);
67
+ throw new Error(result.error.message);
68
+ }
69
+ });
70
+
71
+ export const removeSecret = buildAuthedHandler(async function (
72
+ _flags: GlobalFlags,
73
+ key: string,
74
+ ) {
75
+ const { workerId } = this.config;
76
+ if (!workerId) {
77
+ throw new Error(
78
+ "No worker configured. Run 'workers deploy' first to create a worker.",
79
+ );
80
+ }
81
+
82
+ this.process.stderr.write(`Removing secret "${key}"...`);
83
+
84
+ const result = await this.apiClient.deleteSecret(workerId, key);
85
+
86
+ if (Result.isSuccess(result)) {
87
+ this.process.stderr.write("OK\n\n");
88
+ } else {
89
+ this.process.stderr.write("ERROR\n\n");
90
+
91
+ this.writer.writeErr(`✗ Failed to remove secret "${key}"`);
92
+ this.writer.writeErr(`✗ ${result.error.message}`);
93
+ throw new Error(result.error.message);
94
+ }
95
+ });
96
+
97
+ function usageError() {
98
+ return new Error(
99
+ "Invalid secrets provided. Usage: workers secrets set <key> <value> [<key2> <value2>...] or <key>=<value> [<key2>=<value2>...]",
100
+ );
101
+ }
102
+
103
+ /**
104
+ * Parse secret arguments from CLI, supporting "key value", "key=value", and "key:value" formats.
105
+ */
106
+ function parseSecretArgs(
107
+ args: readonly string[],
108
+ ): Array<{ key: string; value: string }> {
109
+ if (args.length === 0) {
110
+ throw usageError();
111
+ }
112
+
113
+ // Split each arg by = or : to flatten everything into tokens
114
+ const tokens = args.flatMap((arg) => arg.split(/[=:]/));
115
+
116
+ const secrets = chunkEvery(tokens, 2).map(([key, value]) => {
117
+ if (!key || !value) {
118
+ throw usageError();
119
+ }
120
+
121
+ return { key, value };
122
+ });
123
+ return secrets;
124
+ }
@@ -0,0 +1,73 @@
1
+ import { buildCommand, buildRouteMap } from "@stricli/core";
2
+ import { formatFlags, globalFlags } from "../flags.js";
3
+
4
+ export const secretsCommands = buildRouteMap({
5
+ docs: {
6
+ brief: "Commands for managing worker secrets",
7
+ },
8
+ routes: {
9
+ set: buildCommand({
10
+ docs: {
11
+ brief:
12
+ "Set one or more secrets for a worker. Supports 'key value' or 'key=value' format.",
13
+ },
14
+
15
+ parameters: {
16
+ positional: {
17
+ kind: "array",
18
+ parameter: {
19
+ brief: "Secret key-value pairs (key value or key=value)",
20
+ parse: String,
21
+ placeholder: "secrets...",
22
+ },
23
+ },
24
+
25
+ flags: {
26
+ ...globalFlags,
27
+ },
28
+ },
29
+
30
+ loader: () => import("./secrets.impl.js").then((m) => m.setSecrets),
31
+ }),
32
+
33
+ list: buildCommand({
34
+ docs: {
35
+ brief: "List all secrets for a worker (keys only)",
36
+ },
37
+
38
+ parameters: {
39
+ flags: {
40
+ ...globalFlags,
41
+ ...formatFlags,
42
+ },
43
+ },
44
+
45
+ loader: () => import("./secrets.impl.js").then((m) => m.listSecrets),
46
+ }),
47
+
48
+ rm: buildCommand({
49
+ docs: {
50
+ brief: "Remove a secret from a worker",
51
+ },
52
+
53
+ parameters: {
54
+ positional: {
55
+ kind: "tuple",
56
+ parameters: [
57
+ {
58
+ brief: "The secret key name to remove",
59
+ parse: String,
60
+ placeholder: "key",
61
+ },
62
+ ],
63
+ },
64
+
65
+ flags: {
66
+ ...globalFlags,
67
+ },
68
+ },
69
+
70
+ loader: () => import("./secrets.impl.js").then((m) => m.removeSecret),
71
+ }),
72
+ },
73
+ });
@@ -0,0 +1,175 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+
4
+ export const Environments = ["local", "staging", "dev", "prod"] as const;
5
+ export type Environment = (typeof Environments)[number];
6
+
7
+ interface ConfigFile {
8
+ environment?: Environment;
9
+ baseUrl?: string;
10
+ token: string | null;
11
+ workerId: string | null;
12
+ }
13
+
14
+ export function parseEnvironment(name: string): Environment {
15
+ if (["local", "staging", "dev", "prod"].includes(name)) {
16
+ return name as Environment;
17
+ }
18
+
19
+ throw new Error(`Invalid environment name: ${name}`);
20
+ }
21
+
22
+ export class TokenNotSetError extends Error {
23
+ constructor(
24
+ message: string = "Not authenticated. Run 'workers auth login' first.",
25
+ ) {
26
+ super(message);
27
+ this.name = "TokenNotSetError";
28
+ }
29
+ }
30
+
31
+ export function parseEnvironmentName(name: string): Environment {
32
+ if (["local", "staging", "dev", "prod"].includes(name)) {
33
+ return name as Environment;
34
+ }
35
+
36
+ throw new Error(`Invalid environment name: ${name}`);
37
+ }
38
+
39
+ export class Config {
40
+ readonly #configFile: ConfigFile;
41
+ readonly #configFilePath: string;
42
+
43
+ constructor(configFilePath: string, configFile: ConfigFile) {
44
+ this.#configFilePath = configFilePath;
45
+ this.#configFile = configFile;
46
+ }
47
+
48
+ async setEnvironment(environment: Environment) {
49
+ this.#configFile.environment = environment;
50
+ await this.#writeConfigFile();
51
+ }
52
+
53
+ async setBaseUrl(baseUrl: string) {
54
+ this.#configFile.baseUrl = baseUrl;
55
+ await this.#writeConfigFile();
56
+ }
57
+
58
+ get baseUrl() {
59
+ return this.#configFile.baseUrl;
60
+ }
61
+
62
+ get token() {
63
+ return this.#configFile.token;
64
+ }
65
+
66
+ get tokenInfo(): { token: string; spaceId: string; cellId: string } {
67
+ const token = this.#configFile.token;
68
+ if (!token) {
69
+ throw new TokenNotSetError();
70
+ }
71
+
72
+ const { spaceId, cellId } = extractPayloadFromToken(token);
73
+ return { token, spaceId, cellId };
74
+ }
75
+
76
+ get environment() {
77
+ return this.#configFile.environment;
78
+ }
79
+
80
+ get workerId() {
81
+ return this.#configFile.workerId;
82
+ }
83
+
84
+ async setToken(token: string | null) {
85
+ this.#configFile.token = token;
86
+ await this.#writeConfigFile();
87
+ }
88
+
89
+ async setWorkerId(workerId: string | null) {
90
+ this.#configFile.workerId = workerId;
91
+ await this.#writeConfigFile();
92
+ }
93
+
94
+ async #writeConfigFile() {
95
+ await fs.promises.writeFile(
96
+ this.#configFilePath,
97
+ JSON.stringify(this.#configFile, null, 2),
98
+ "utf-8",
99
+ );
100
+ }
101
+
102
+ static async load(configFilePath: string) {
103
+ const absConfigFilePath = path.resolve(process.cwd(), configFilePath);
104
+ const configFile = await Config.#readConfigFile(absConfigFilePath);
105
+ return new Config(absConfigFilePath, configFile);
106
+ }
107
+
108
+ static async #readConfigFile(configFilePath: string) {
109
+ let configFile: ConfigFile;
110
+ try {
111
+ const configContents = await fs.promises.readFile(
112
+ configFilePath,
113
+ "utf-8",
114
+ );
115
+
116
+ configFile = JSON.parse(configContents) as ConfigFile;
117
+ } catch (error) {
118
+ if (isENOENT(error)) {
119
+ configFile = { token: null, workerId: null };
120
+ } else {
121
+ throw error;
122
+ }
123
+ }
124
+
125
+ return configFile;
126
+ }
127
+ }
128
+
129
+ function isENOENT(
130
+ error: unknown,
131
+ ): error is NodeJS.ErrnoException & { code: "ENOENT" } {
132
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
133
+ }
134
+
135
+ export function extractPayloadFromToken(token: string): {
136
+ spaceId: string;
137
+ userId: string;
138
+ cellId: string;
139
+ } {
140
+ try {
141
+ // Token format: <version>.<type>.<encoded-payload-json>.<encoded-signature>
142
+ const parts = token.split(".");
143
+ if (parts.length !== 4 || !parts[2]) {
144
+ throw new Error("Invalid token format.");
145
+ }
146
+
147
+ // Decode the payload (third part)
148
+ const payloadBase64 = parts[2];
149
+ const payloadJson = Buffer.from(payloadBase64, "base64url").toString(
150
+ "utf-8",
151
+ );
152
+ const payload = JSON.parse(payloadJson) as {
153
+ spaceId?: string;
154
+ userId?: string;
155
+ cellId?: string;
156
+ };
157
+
158
+ if (!payload.spaceId || !payload.userId || !payload.cellId) {
159
+ throw new Error(
160
+ "Token payload missing required fields (spaceId, userId, cellId).",
161
+ );
162
+ }
163
+
164
+ return {
165
+ spaceId: payload.spaceId,
166
+ userId: payload.userId,
167
+ cellId: payload.cellId,
168
+ };
169
+ } catch (error) {
170
+ if (error instanceof Error && error.message.startsWith("Token")) {
171
+ throw error;
172
+ }
173
+ throw new Error("Failed to parse token payload.");
174
+ }
175
+ }
@@ -0,0 +1,26 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import type { StricliAutoCompleteContext } from "@stricli/auto-complete";
5
+ import type { CommandContext } from "@stricli/core";
6
+ import { Writer } from "./writer.js";
7
+
8
+ export interface LocalContext
9
+ extends CommandContext,
10
+ StricliAutoCompleteContext {
11
+ readonly fs: typeof fs;
12
+ readonly os: typeof os;
13
+ readonly path: typeof path;
14
+ readonly process: NodeJS.Process;
15
+ readonly writer: Writer;
16
+ }
17
+
18
+ export function buildContext(process: NodeJS.Process): LocalContext {
19
+ return {
20
+ fs,
21
+ os,
22
+ path,
23
+ process,
24
+ writer: new Writer({ debugEnabled: false }),
25
+ };
26
+ }
@@ -0,0 +1,175 @@
1
+ // AI-Generated: Significantly generated by model: Claude Sonnet 4.5; agent: AI Agents.
2
+
3
+ import * as childProcess from "node:child_process";
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import * as esbuild from "esbuild";
7
+ import type { ApiError, Environment } from "./api/client.js";
8
+ import { Result } from "./api/result.js";
9
+ import type { LocalContext } from "./context.js";
10
+ import type { AuthedContext } from "./handler.js";
11
+
12
+ export interface DeployOptions {
13
+ /**
14
+ * Path to the worker directory to deploy
15
+ */
16
+ workerPath: string;
17
+
18
+ /**
19
+ * API token for authentication
20
+ */
21
+ token: string;
22
+
23
+ /**
24
+ * Environment to deploy to
25
+ */
26
+ environment: Environment;
27
+
28
+ workerId?: string | null;
29
+ }
30
+
31
+ /**
32
+ * Deploy a worker to the specified environment
33
+ */
34
+ export async function deployWorker(
35
+ context: AuthedContext,
36
+ options: DeployOptions,
37
+ ): Promise<Result<{ workerId: string }, ApiError>> {
38
+ const { workerPath, workerId: workerIdForUpdate } = options;
39
+
40
+ // Resolve absolute path
41
+ const absPath = path.resolve(process.cwd(), workerPath);
42
+ context.writer.debug(`Deploying worker from: ${absPath}`);
43
+
44
+ // Create API client
45
+ const client = context.apiClient;
46
+
47
+ let uploadUrl: string;
48
+ let uploadFields: Record<string, string>;
49
+ let workerId: string;
50
+
51
+ context.writer.writeErr(
52
+ workerIdForUpdate ? `Updating worker...` : `Creating worker...`,
53
+ );
54
+
55
+ if (workerIdForUpdate) {
56
+ workerId = workerIdForUpdate;
57
+ const updateResult = await client.updateWorkerBundle(workerIdForUpdate);
58
+ if (Result.isSuccess(updateResult)) {
59
+ const res = Result.unwrap(updateResult);
60
+ uploadUrl = res.url;
61
+ uploadFields = res.fields;
62
+ } else {
63
+ context.writer.writeErr(
64
+ "Failed to generate pre-signed upload URL for worker bundle:",
65
+ );
66
+ throw new Error(updateResult.error.message);
67
+ }
68
+ } else {
69
+ const createResult = await client.createWorker("tmp-give-me-a-good-name");
70
+ if (Result.isSuccess(createResult)) {
71
+ const res = Result.unwrap(createResult);
72
+ uploadUrl = res.url;
73
+ uploadFields = res.fields;
74
+ workerId = res.worker.workerId;
75
+ } else {
76
+ console.error("Failed to create worker:");
77
+ throw new Error(createResult.error.message);
78
+ }
79
+ }
80
+
81
+ context.writer.debug(`Generated upload URL: ${uploadUrl}`);
82
+
83
+ // Build the worker bundle
84
+ context.writer.writeErr("Building worker bundle...");
85
+ const archivePath = await buildWorkerBundle(context, absPath);
86
+ context.writer.debug(`Created bundle at: ${archivePath}`);
87
+
88
+ // Upload the bundle
89
+ context.writer.writeErr("Uploading bundle...");
90
+ await uploadBundle(uploadUrl, uploadFields, archivePath);
91
+ context.writer.debug("Upload complete");
92
+
93
+ // Fetch and save capabilities
94
+ context.writer.writeErr("Fetching and saving worker capabilities...");
95
+ const capabilitiesResult = await client.fetchAndSaveCapabilities(workerId);
96
+
97
+ if (Result.isSuccess(capabilitiesResult)) {
98
+ return Result.success({ workerId });
99
+ } else {
100
+ return capabilitiesResult;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Build the worker bundle using esbuild and create a tar.gz archive
106
+ */
107
+ async function buildWorkerBundle(
108
+ context: LocalContext,
109
+ workerPath: string,
110
+ ): Promise<string> {
111
+ const entrypoint = "src/index.ts";
112
+
113
+ if (!fs.existsSync(path.join(workerPath, entrypoint))) {
114
+ throw new Error(`Entrypoint not found: ${entrypoint}`);
115
+ }
116
+
117
+ // Create temporary output directory
118
+ const outdir = fs.mkdtempSync("/tmp/workers-build-");
119
+
120
+ // Build with esbuild
121
+ esbuild.buildSync({
122
+ entryPoints: [entrypoint],
123
+ absWorkingDir: workerPath,
124
+ bundle: true,
125
+ outdir,
126
+ platform: "node",
127
+ });
128
+
129
+ context.writer.debug(`Built bundle to: ${outdir}`);
130
+
131
+ // Create tar.gz archive
132
+ const archiveDir = fs.mkdtempSync("/tmp/workers-archive-");
133
+ const archivePath = path.join(archiveDir, "archive.tar.gz");
134
+
135
+ childProcess.execSync(`tar -czf ${archivePath} -C ${outdir} .`);
136
+
137
+ return archivePath;
138
+ }
139
+
140
+ /**
141
+ * Upload the worker bundle to the pre-signed URL
142
+ */
143
+ async function uploadBundle(
144
+ url: string,
145
+ fields: Record<string, string>,
146
+ archivePath: string,
147
+ ): Promise<void> {
148
+ const formData = new FormData();
149
+
150
+ // Add all the pre-signed fields
151
+ for (const [key, value] of Object.entries(fields)) {
152
+ formData.append(key, value);
153
+ }
154
+
155
+ // Add the file
156
+ const fileBuffer = fs.readFileSync(archivePath);
157
+ formData.append(
158
+ "file",
159
+ new Blob([new Uint8Array(fileBuffer)]),
160
+ "archive.tar.gz",
161
+ );
162
+
163
+ // Upload
164
+ const response = await fetch(url, {
165
+ method: "POST",
166
+ body: formData,
167
+ });
168
+
169
+ if (!response.ok) {
170
+ const errorText = await response.text();
171
+ throw new Error(
172
+ `Failed to upload bundle: ${response.status} ${response.statusText}\n${errorText}`,
173
+ );
174
+ }
175
+ }
@@ -0,0 +1,43 @@
1
+ import type { TypedFlagParameter } from "@stricli/core";
2
+ import type { LocalContext } from "./context.js";
3
+
4
+ export interface GlobalFlags {
5
+ config?: string;
6
+ debug: boolean;
7
+ }
8
+
9
+ export const globalFlags: {
10
+ [K in keyof Required<GlobalFlags>]: TypedFlagParameter<
11
+ GlobalFlags[K],
12
+ LocalContext
13
+ >;
14
+ } = {
15
+ config: {
16
+ kind: "parsed",
17
+ parse: String,
18
+ brief: "The path to the config file to use",
19
+ optional: true,
20
+ },
21
+ debug: {
22
+ kind: "boolean",
23
+ brief: "Enable debug logging",
24
+ default: false,
25
+ },
26
+ };
27
+
28
+ export interface FormatFlags {
29
+ plain: boolean;
30
+ }
31
+
32
+ export const formatFlags: {
33
+ [K in keyof Required<FormatFlags>]: TypedFlagParameter<
34
+ FormatFlags[K],
35
+ LocalContext
36
+ >;
37
+ } = {
38
+ plain: {
39
+ kind: "boolean",
40
+ brief: "Output the results in plain text format",
41
+ default: false,
42
+ },
43
+ };
@@ -0,0 +1,62 @@
1
+ import type { BaseArgs } from "@stricli/core";
2
+ import { ApiClient } from "./api/client.js";
3
+ import { Config } from "./config.js";
4
+ import type { LocalContext } from "./context.js";
5
+ import type { GlobalFlags } from "./flags.js";
6
+
7
+ export interface HandlerContext extends LocalContext {
8
+ config: Config;
9
+ }
10
+
11
+ export interface AuthedContext extends HandlerContext {
12
+ apiClient: ApiClient;
13
+ }
14
+
15
+ export function buildHandler<const FLAGS, const ARGS extends BaseArgs = []>(
16
+ handler: (
17
+ this: HandlerContext,
18
+ flags: FLAGS,
19
+ ...args: ARGS
20
+ ) => Promise<void> | void,
21
+ ) {
22
+ return async function (
23
+ this: LocalContext,
24
+ flags: GlobalFlags & FLAGS,
25
+ ...args: ARGS
26
+ ) {
27
+ const config = await Config.load(flags.config ?? "./workers.json");
28
+
29
+ this.writer.debugEnabled = flags.debug;
30
+
31
+ await handler.call({ ...this, config }, flags, ...args);
32
+ };
33
+ }
34
+
35
+ export function buildAuthedHandler<
36
+ const FLAGS,
37
+ const ARGS extends BaseArgs = [],
38
+ >(
39
+ handler: (
40
+ this: AuthedContext,
41
+ flags: FLAGS,
42
+ ...args: ARGS
43
+ ) => Promise<void> | void,
44
+ ) {
45
+ return buildHandler(function (flags: FLAGS, ...args: ARGS) {
46
+ const environment = this.config.environment;
47
+ if (!environment) {
48
+ // Unexpected to get here when token is set
49
+ throw new Error("Unexpected error: Environment not set");
50
+ }
51
+
52
+ const { token, cellId } = this.config.tokenInfo;
53
+ const client = new ApiClient({
54
+ token,
55
+ environment,
56
+ baseUrl: this.config.baseUrl,
57
+ cellId,
58
+ writer: this.writer,
59
+ });
60
+ return handler.call({ ...this, apiClient: client }, flags, ...args);
61
+ });
62
+ }