robodev 0.38.0 → 0.39.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.
- package/package.json +3 -3
- package/src/ensure-deploy-link.test.ts +228 -0
- package/src/ensure-deploy-link.ts +67 -0
- package/src/index.ts +30 -7
- package/src/parse-create-args.test.ts +7 -0
- package/src/parse-deploy-args.test.ts +37 -0
- package/src/parse-deploy-args.ts +53 -0
- package/templates/auth-chat/package.json +2 -2
- package/templates/backend/package.json +2 -2
- package/templates/empty/package.json +2 -2
- package/templates/marketplace/package.json +2 -2
- package/templates/space/package.json +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "robodev",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"description": "CLI for Robodev Starbase — create, auth, link, deploy, and run a project locally with robodev dev",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"embedded-postgres": "17.10.0-beta.17",
|
|
27
27
|
"fastify": "^5.12.1",
|
|
28
28
|
"tsx": "^4.20.5",
|
|
29
|
-
"@robodev-ai/
|
|
30
|
-
"@robodev-ai/
|
|
29
|
+
"@robodev-ai/sdk": "0.13.0",
|
|
30
|
+
"@robodev-ai/runtime": "0.5.1"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^24.3.0",
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
ensureDeployLink,
|
|
5
|
+
UNLINKED_NON_TTY_MESSAGE,
|
|
6
|
+
type EnsureDeployLinkDeps,
|
|
7
|
+
type EnsureDeployLinkInput,
|
|
8
|
+
} from "./ensure-deploy-link.js";
|
|
9
|
+
import { defaultFrontendConfig, type ProjectLink } from "./config.js";
|
|
10
|
+
|
|
11
|
+
const linked: ProjectLink = {
|
|
12
|
+
projectId: "proj_existing",
|
|
13
|
+
apiUrl: "https://robodev.povio.dev",
|
|
14
|
+
projectApiUrl: "https://proj_existing.robodev.povio.dev",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function stubDeps(overrides: Partial<EnsureDeployLinkDeps> = {}): EnsureDeployLinkDeps & {
|
|
18
|
+
calls: {
|
|
19
|
+
ensureAuth: number;
|
|
20
|
+
collectFiles: number;
|
|
21
|
+
resolveOrganization: number;
|
|
22
|
+
createProject: number;
|
|
23
|
+
writeLink: number;
|
|
24
|
+
writeViteApiUrl: number;
|
|
25
|
+
promptName: number;
|
|
26
|
+
};
|
|
27
|
+
createdBodies: { name: string; organizationId?: string }[];
|
|
28
|
+
writtenLinks: ProjectLink[];
|
|
29
|
+
} {
|
|
30
|
+
const calls = {
|
|
31
|
+
ensureAuth: 0,
|
|
32
|
+
collectFiles: 0,
|
|
33
|
+
resolveOrganization: 0,
|
|
34
|
+
createProject: 0,
|
|
35
|
+
writeLink: 0,
|
|
36
|
+
writeViteApiUrl: 0,
|
|
37
|
+
promptName: 0,
|
|
38
|
+
};
|
|
39
|
+
const createdBodies: { name: string; organizationId?: string }[] = [];
|
|
40
|
+
const writtenLinks: ProjectLink[] = [];
|
|
41
|
+
return {
|
|
42
|
+
calls,
|
|
43
|
+
createdBodies,
|
|
44
|
+
writtenLinks,
|
|
45
|
+
readLink: async () => null,
|
|
46
|
+
collectFiles: async () => {
|
|
47
|
+
calls.collectFiles += 1;
|
|
48
|
+
return [];
|
|
49
|
+
},
|
|
50
|
+
ensureAuth: async () => {
|
|
51
|
+
calls.ensureAuth += 1;
|
|
52
|
+
},
|
|
53
|
+
resolveOrganization: async (flagId) => {
|
|
54
|
+
calls.resolveOrganization += 1;
|
|
55
|
+
return flagId;
|
|
56
|
+
},
|
|
57
|
+
createProject: async (body) => {
|
|
58
|
+
calls.createProject += 1;
|
|
59
|
+
createdBodies.push(body);
|
|
60
|
+
return { id: "proj_new", name: body.name, apiUrl: "https://proj_new.robodev.povio.dev" };
|
|
61
|
+
},
|
|
62
|
+
writeLink: async (link) => {
|
|
63
|
+
calls.writeLink += 1;
|
|
64
|
+
writtenLinks.push(link);
|
|
65
|
+
},
|
|
66
|
+
writeViteApiUrl: async () => {
|
|
67
|
+
calls.writeViteApiUrl += 1;
|
|
68
|
+
},
|
|
69
|
+
hasAppsFePackageJson: async () => false,
|
|
70
|
+
starbaseUrl: () => "https://robodev.povio.dev",
|
|
71
|
+
projectNameFromPath: () => "my-folder",
|
|
72
|
+
promptName: async () => {
|
|
73
|
+
calls.promptName += 1;
|
|
74
|
+
return "";
|
|
75
|
+
},
|
|
76
|
+
...overrides,
|
|
77
|
+
...(overrides.collectFiles
|
|
78
|
+
? {
|
|
79
|
+
collectFiles: async (root: string) => {
|
|
80
|
+
calls.collectFiles += 1;
|
|
81
|
+
return overrides.collectFiles!(root);
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
: {}),
|
|
85
|
+
...(overrides.ensureAuth
|
|
86
|
+
? {
|
|
87
|
+
ensureAuth: async () => {
|
|
88
|
+
calls.ensureAuth += 1;
|
|
89
|
+
return overrides.ensureAuth!();
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
: {}),
|
|
93
|
+
...(overrides.resolveOrganization
|
|
94
|
+
? {
|
|
95
|
+
resolveOrganization: async (flagId: string | undefined) => {
|
|
96
|
+
calls.resolveOrganization += 1;
|
|
97
|
+
return overrides.resolveOrganization!(flagId);
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
: {}),
|
|
101
|
+
...(overrides.createProject
|
|
102
|
+
? {
|
|
103
|
+
createProject: async (body: { name: string; organizationId?: string }) => {
|
|
104
|
+
calls.createProject += 1;
|
|
105
|
+
createdBodies.push(body);
|
|
106
|
+
return overrides.createProject!(body);
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
: {}),
|
|
110
|
+
...(overrides.writeLink
|
|
111
|
+
? {
|
|
112
|
+
writeLink: async (link: ProjectLink, root: string) => {
|
|
113
|
+
calls.writeLink += 1;
|
|
114
|
+
writtenLinks.push(link);
|
|
115
|
+
return overrides.writeLink!(link, root);
|
|
116
|
+
},
|
|
117
|
+
}
|
|
118
|
+
: {}),
|
|
119
|
+
...(overrides.writeViteApiUrl
|
|
120
|
+
? {
|
|
121
|
+
writeViteApiUrl: async (url: string, root: string) => {
|
|
122
|
+
calls.writeViteApiUrl += 1;
|
|
123
|
+
return overrides.writeViteApiUrl!(url, root);
|
|
124
|
+
},
|
|
125
|
+
}
|
|
126
|
+
: {}),
|
|
127
|
+
...(overrides.promptName
|
|
128
|
+
? {
|
|
129
|
+
promptName: async (defaultName: string) => {
|
|
130
|
+
calls.promptName += 1;
|
|
131
|
+
return overrides.promptName!(defaultName);
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
: {}),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function run(input: Partial<EnsureDeployLinkInput>, deps: ReturnType<typeof stubDeps>) {
|
|
139
|
+
return ensureDeployLink({ root: "/tmp/app", isTty: false, ...input }, deps);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
test("ensureDeployLink returns an existing link and does not create again", async () => {
|
|
143
|
+
const deps = stubDeps({ readLink: async () => linked });
|
|
144
|
+
const result = await run({ name: "Ignored", orgId: "org_x", isTty: true }, deps);
|
|
145
|
+
assert.deepEqual(result, linked);
|
|
146
|
+
assert.equal(deps.calls.ensureAuth, 0);
|
|
147
|
+
assert.equal(deps.calls.collectFiles, 0);
|
|
148
|
+
assert.equal(deps.calls.createProject, 0);
|
|
149
|
+
assert.equal(deps.calls.writeLink, 0);
|
|
150
|
+
assert.equal(deps.calls.resolveOrganization, 0);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("ensureDeployLink fails collect before creating a project", async () => {
|
|
154
|
+
const deps = stubDeps({
|
|
155
|
+
collectFiles: async () => {
|
|
156
|
+
throw new Error("database.ts not found (looked in robodev/database.ts and ./database.ts)");
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
await assert.rejects(() => run({ name: "Orbit", isTty: false }, deps), /database\.ts not found/);
|
|
160
|
+
assert.equal(deps.calls.ensureAuth, 1);
|
|
161
|
+
assert.equal(deps.calls.collectFiles, 1);
|
|
162
|
+
assert.equal(deps.calls.createProject, 0);
|
|
163
|
+
assert.equal(deps.calls.writeLink, 0);
|
|
164
|
+
assert.equal(deps.calls.resolveOrganization, 0);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("ensureDeployLink non-TTY without --name exits without creating", async () => {
|
|
168
|
+
const deps = stubDeps();
|
|
169
|
+
await assert.rejects(() => run({ isTty: false }, deps), new Error(UNLINKED_NON_TTY_MESSAGE));
|
|
170
|
+
assert.equal(deps.calls.ensureAuth, 1);
|
|
171
|
+
assert.equal(deps.calls.collectFiles, 1);
|
|
172
|
+
assert.equal(deps.calls.createProject, 0);
|
|
173
|
+
assert.equal(deps.calls.writeLink, 0);
|
|
174
|
+
assert.equal(deps.calls.promptName, 0);
|
|
175
|
+
assert.equal(deps.calls.resolveOrganization, 0);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("ensureDeployLink TTY prompts for name and writes the link before returning", async () => {
|
|
179
|
+
const deps = stubDeps({
|
|
180
|
+
promptName: async (defaultName) => {
|
|
181
|
+
assert.equal(defaultName, "my-folder");
|
|
182
|
+
return "";
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
const result = await run({ isTty: true }, deps);
|
|
186
|
+
assert.equal(deps.calls.promptName, 1);
|
|
187
|
+
assert.deepEqual(deps.createdBodies, [{ name: "my-folder" }]);
|
|
188
|
+
assert.equal(deps.calls.writeLink, 1);
|
|
189
|
+
assert.equal(deps.calls.writeViteApiUrl, 1);
|
|
190
|
+
assert.equal(deps.calls.createProject, 1);
|
|
191
|
+
assert.ok(deps.calls.writeLink >= 1);
|
|
192
|
+
assert.deepEqual(result, deps.writtenLinks[0]);
|
|
193
|
+
assert.equal(result.projectId, "proj_new");
|
|
194
|
+
assert.equal(result.frontend, undefined);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("ensureDeployLink TTY uses a typed name and optional org", async () => {
|
|
198
|
+
const deps = stubDeps({
|
|
199
|
+
resolveOrganization: async (flagId) => flagId,
|
|
200
|
+
promptName: async () => "From prompt",
|
|
201
|
+
});
|
|
202
|
+
await run({ isTty: true, orgId: "org_2" }, deps);
|
|
203
|
+
assert.deepEqual(deps.createdBodies, [{ name: "From prompt", organizationId: "org_2" }]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("ensureDeployLink non-TTY with --name creates a name-only project", async () => {
|
|
207
|
+
const deps = stubDeps({
|
|
208
|
+
hasAppsFePackageJson: async () => true,
|
|
209
|
+
});
|
|
210
|
+
const result = await run({ isTty: false, name: " Orbit ", orgId: "org_1" }, deps);
|
|
211
|
+
assert.equal(deps.calls.promptName, 0);
|
|
212
|
+
assert.deepEqual(deps.createdBodies, [{ name: "Orbit", organizationId: "org_1" }]);
|
|
213
|
+
assert.equal("starterId" in deps.createdBodies[0], false);
|
|
214
|
+
assert.deepEqual(result.frontend, defaultFrontendConfig());
|
|
215
|
+
assert.equal(deps.calls.writeLink, 1);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("ensureDeployLink auth failure happens before collect and create", async () => {
|
|
219
|
+
const deps = stubDeps({
|
|
220
|
+
ensureAuth: async () => {
|
|
221
|
+
throw new Error("Not authenticated. Run `robodev auth` first.");
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
await assert.rejects(() => run({ name: "Orbit", isTty: false }, deps), /Not authenticated/);
|
|
225
|
+
assert.equal(deps.calls.ensureAuth, 1);
|
|
226
|
+
assert.equal(deps.calls.collectFiles, 0);
|
|
227
|
+
assert.equal(deps.calls.createProject, 0);
|
|
228
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { defaultFrontendConfig, type ProjectLink } from "./config.js";
|
|
2
|
+
|
|
3
|
+
export type DeployProject = { id: string; name: string; apiUrl: string };
|
|
4
|
+
|
|
5
|
+
export const UNLINKED_NON_TTY_MESSAGE =
|
|
6
|
+
"No robodev/.robodev file. Pass --name / --org or run `robodev link` or `robodev create`.";
|
|
7
|
+
|
|
8
|
+
export type EnsureDeployLinkInput = {
|
|
9
|
+
root: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
orgId?: string;
|
|
12
|
+
isTty: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type EnsureDeployLinkDeps = {
|
|
16
|
+
readLink: (root: string) => Promise<ProjectLink | null>;
|
|
17
|
+
collectFiles: (root: string) => Promise<unknown>;
|
|
18
|
+
ensureAuth: () => Promise<void>;
|
|
19
|
+
resolveOrganization: (flagId: string | undefined) => Promise<string | undefined>;
|
|
20
|
+
createProject: (body: { name: string; organizationId?: string }) => Promise<DeployProject>;
|
|
21
|
+
writeLink: (link: ProjectLink, root: string) => Promise<void>;
|
|
22
|
+
writeViteApiUrl: (projectApiUrl: string, root: string) => Promise<void>;
|
|
23
|
+
hasAppsFePackageJson: (root: string) => Promise<boolean>;
|
|
24
|
+
starbaseUrl: () => string;
|
|
25
|
+
projectNameFromPath: (root: string) => string;
|
|
26
|
+
promptName: (defaultName: string) => Promise<string>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function resolveProjectName(raw: string | undefined, fallback: string): string {
|
|
30
|
+
const trimmed = raw?.trim() ?? "";
|
|
31
|
+
return (trimmed || fallback).slice(0, 80);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Creates and writes a project link when the folder is not linked yet. */
|
|
35
|
+
export async function ensureDeployLink(
|
|
36
|
+
input: EnsureDeployLinkInput,
|
|
37
|
+
deps: EnsureDeployLinkDeps,
|
|
38
|
+
): Promise<ProjectLink> {
|
|
39
|
+
const existing = await deps.readLink(input.root);
|
|
40
|
+
if (existing) return existing;
|
|
41
|
+
|
|
42
|
+
await deps.ensureAuth();
|
|
43
|
+
await deps.collectFiles(input.root);
|
|
44
|
+
|
|
45
|
+
const flaggedName = input.name?.trim() ?? "";
|
|
46
|
+
if (!input.isTty && !flaggedName) {
|
|
47
|
+
throw new Error(UNLINKED_NON_TTY_MESSAGE);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const organizationId = await deps.resolveOrganization(input.orgId);
|
|
51
|
+
const defaultName = deps.projectNameFromPath(input.root);
|
|
52
|
+
const name = flaggedName
|
|
53
|
+
? resolveProjectName(flaggedName, defaultName)
|
|
54
|
+
: resolveProjectName(await deps.promptName(defaultName), defaultName);
|
|
55
|
+
|
|
56
|
+
const project = await deps.createProject(organizationId ? { name, organizationId } : { name });
|
|
57
|
+
const wantFrontend = await deps.hasAppsFePackageJson(input.root);
|
|
58
|
+
const link: ProjectLink = {
|
|
59
|
+
projectId: project.id,
|
|
60
|
+
apiUrl: deps.starbaseUrl(),
|
|
61
|
+
projectApiUrl: project.apiUrl,
|
|
62
|
+
...(wantFrontend ? { frontend: defaultFrontendConfig() } : {}),
|
|
63
|
+
};
|
|
64
|
+
await deps.writeLink(link, input.root);
|
|
65
|
+
await deps.writeViteApiUrl(project.apiUrl, input.root);
|
|
66
|
+
return link;
|
|
67
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -36,6 +36,8 @@ import {
|
|
|
36
36
|
type Starter,
|
|
37
37
|
} from "./emit-starter.js";
|
|
38
38
|
import { parseCreateArgs } from "./parse-create-args.js";
|
|
39
|
+
import { parseDeployArgs } from "./parse-deploy-args.js";
|
|
40
|
+
import { ensureDeployLink } from "./ensure-deploy-link.js";
|
|
39
41
|
import { printLiveUrl } from "./print-live-url.js";
|
|
40
42
|
|
|
41
43
|
const execFileAsync = promisify(execFile);
|
|
@@ -52,7 +54,7 @@ Commands:
|
|
|
52
54
|
projects List your projects
|
|
53
55
|
create [path] [--starter <id>] [--empty] [--org <id>] Create a project, scaffold a starter, and deploy
|
|
54
56
|
link [projectId] Write robodev/.robodev in the current folder
|
|
55
|
-
deploy [--force]
|
|
57
|
+
deploy [--force] [--name <name>] [--org <id>] Deploy schema, APIs, jobs, and frontend (creates and links if unlinked)
|
|
56
58
|
dev [options] Run this project's APIs locally on an embedded Postgres, no Starbase needed
|
|
57
59
|
|
|
58
60
|
dev options:
|
|
@@ -247,12 +249,31 @@ async function collectDeployFiles(
|
|
|
247
249
|
async function runDeploy(
|
|
248
250
|
forceFlag: boolean,
|
|
249
251
|
options: DeployOptions = {},
|
|
252
|
+
flags: { name?: string; orgId?: string } = {},
|
|
250
253
|
): Promise<string | undefined> {
|
|
251
254
|
const root = options.root ?? process.cwd();
|
|
252
|
-
const link = await
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
255
|
+
const link = await ensureDeployLink(
|
|
256
|
+
{ root, name: flags.name, orgId: flags.orgId, isTty: Boolean(stdin.isTTY) },
|
|
257
|
+
{
|
|
258
|
+
readLink,
|
|
259
|
+
collectFiles,
|
|
260
|
+
ensureAuth: async () => {
|
|
261
|
+
await authed("/v1/auth/me");
|
|
262
|
+
},
|
|
263
|
+
resolveOrganization,
|
|
264
|
+
createProject: (body) =>
|
|
265
|
+
authed<Project>("/v1/projects", {
|
|
266
|
+
method: "POST",
|
|
267
|
+
body: JSON.stringify(body),
|
|
268
|
+
}),
|
|
269
|
+
writeLink,
|
|
270
|
+
writeViteApiUrl,
|
|
271
|
+
hasAppsFePackageJson,
|
|
272
|
+
starbaseUrl,
|
|
273
|
+
projectNameFromPath,
|
|
274
|
+
promptName: (defaultName) => prompt(`Project name [${defaultName}]: `),
|
|
275
|
+
},
|
|
276
|
+
);
|
|
256
277
|
const files = await collectDeployFiles(root, link, options);
|
|
257
278
|
let force = forceFlag;
|
|
258
279
|
|
|
@@ -631,9 +652,11 @@ async function main(): Promise<void> {
|
|
|
631
652
|
case "link":
|
|
632
653
|
await runLink(args[0]);
|
|
633
654
|
break;
|
|
634
|
-
case "deploy":
|
|
635
|
-
|
|
655
|
+
case "deploy": {
|
|
656
|
+
const parsed = parseDeployArgs(args);
|
|
657
|
+
await runDeploy(parsed.force, {}, { name: parsed.name, orgId: parsed.orgId });
|
|
636
658
|
break;
|
|
659
|
+
}
|
|
637
660
|
case "dev": {
|
|
638
661
|
const { main: runDev } = await import("./dev.js");
|
|
639
662
|
await runDev(args);
|
|
@@ -24,3 +24,10 @@ test("parseCreateArgs rejects a missing --org value", () => {
|
|
|
24
24
|
test("parseCreateArgs rejects unknown flags including --organization", () => {
|
|
25
25
|
assert.throws(() => parseCreateArgs(["--organization", "org_1"]), /Unknown flag --organization/);
|
|
26
26
|
});
|
|
27
|
+
|
|
28
|
+
test("parseCreateArgs rejects --empty together with --starter", () => {
|
|
29
|
+
assert.throws(
|
|
30
|
+
() => parseCreateArgs(["--empty", "--starter", "empty"]),
|
|
31
|
+
/Use either --empty or --starter, not both/,
|
|
32
|
+
);
|
|
33
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { test } from "node:test";
|
|
3
|
+
import { parseDeployArgs } from "./parse-deploy-args.js";
|
|
4
|
+
|
|
5
|
+
test("parseDeployArgs accepts --force --name and --org", () => {
|
|
6
|
+
assert.deepEqual(parseDeployArgs([]), { force: false, name: undefined, orgId: undefined });
|
|
7
|
+
assert.deepEqual(parseDeployArgs(["--force"]), {
|
|
8
|
+
force: true,
|
|
9
|
+
name: undefined,
|
|
10
|
+
orgId: undefined,
|
|
11
|
+
});
|
|
12
|
+
assert.deepEqual(parseDeployArgs(["--name", "Orbit", "--org", "org_1"]), {
|
|
13
|
+
force: false,
|
|
14
|
+
name: "Orbit",
|
|
15
|
+
orgId: "org_1",
|
|
16
|
+
});
|
|
17
|
+
assert.deepEqual(parseDeployArgs(["--name=Orbit", "--org=org_2", "--force"]), {
|
|
18
|
+
force: true,
|
|
19
|
+
name: "Orbit",
|
|
20
|
+
orgId: "org_2",
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("parseDeployArgs rejects a missing --name or --org value", () => {
|
|
25
|
+
assert.throws(() => parseDeployArgs(["--name"]), /Missing value for --name/);
|
|
26
|
+
assert.throws(() => parseDeployArgs(["--name="]), /Missing value for --name/);
|
|
27
|
+
assert.throws(() => parseDeployArgs(["--name", "--force"]), /Missing value for --name/);
|
|
28
|
+
assert.throws(() => parseDeployArgs(["--org"]), /Missing value for --org/);
|
|
29
|
+
assert.throws(() => parseDeployArgs(["--org="]), /Missing value for --org/);
|
|
30
|
+
assert.throws(() => parseDeployArgs(["--org", "--name", "x"]), /Missing value for --org/);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("parseDeployArgs rejects unknown flags the same way as create", () => {
|
|
34
|
+
assert.throws(() => parseDeployArgs(["--organization", "org_1"]), /Unknown flag --organization/);
|
|
35
|
+
assert.throws(() => parseDeployArgs(["--empty"]), /Unknown flag --empty/);
|
|
36
|
+
assert.throws(() => parseDeployArgs(["my-app"]), /Unexpected argument my-app/);
|
|
37
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export type DeployArgs = { force: boolean; name?: string; orgId?: string };
|
|
2
|
+
|
|
3
|
+
export function parseDeployArgs(args: string[]): DeployArgs {
|
|
4
|
+
let force = false;
|
|
5
|
+
let name: string | undefined;
|
|
6
|
+
let orgId: string | undefined;
|
|
7
|
+
for (let i = 0; i < args.length; i++) {
|
|
8
|
+
const arg = args[i];
|
|
9
|
+
if (arg === "--force") {
|
|
10
|
+
force = true;
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
if (arg === "--name") {
|
|
14
|
+
const value = args[i + 1];
|
|
15
|
+
if (!value || value.startsWith("-")) {
|
|
16
|
+
throw new Error("Missing value for --name");
|
|
17
|
+
}
|
|
18
|
+
name = value;
|
|
19
|
+
i += 1;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (arg.startsWith("--name=")) {
|
|
23
|
+
const value = arg.slice("--name=".length);
|
|
24
|
+
if (!value) {
|
|
25
|
+
throw new Error("Missing value for --name");
|
|
26
|
+
}
|
|
27
|
+
name = value;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (arg === "--org") {
|
|
31
|
+
const value = args[i + 1];
|
|
32
|
+
if (!value || value.startsWith("-")) {
|
|
33
|
+
throw new Error("Missing value for --org");
|
|
34
|
+
}
|
|
35
|
+
orgId = value;
|
|
36
|
+
i += 1;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (arg.startsWith("--org=")) {
|
|
40
|
+
const value = arg.slice("--org=".length);
|
|
41
|
+
if (!value) {
|
|
42
|
+
throw new Error("Missing value for --org");
|
|
43
|
+
}
|
|
44
|
+
orgId = value;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (arg.startsWith("-")) {
|
|
48
|
+
throw new Error(`Unknown flag ${arg}`);
|
|
49
|
+
}
|
|
50
|
+
throw new Error(`Unexpected argument ${arg}`);
|
|
51
|
+
}
|
|
52
|
+
return { force, name, orgId };
|
|
53
|
+
}
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
"rules:gen": "rulesync generate"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@robodev-ai/sdk": "^0.
|
|
12
|
+
"@robodev-ai/sdk": "^0.13.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"robodev": "^0.
|
|
15
|
+
"robodev": "^0.39.0",
|
|
16
16
|
"rulesync": "^8.18.0",
|
|
17
17
|
"typescript": "^5.9.2"
|
|
18
18
|
}
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
"rules:gen": "rulesync generate"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@robodev-ai/sdk": "^0.
|
|
12
|
+
"@robodev-ai/sdk": "^0.13.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"robodev": "^0.
|
|
15
|
+
"robodev": "^0.39.0",
|
|
16
16
|
"rulesync": "^8.18.0",
|
|
17
17
|
"typescript": "^5.9.2"
|
|
18
18
|
}
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
"rules:gen": "rulesync generate"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@robodev-ai/sdk": "^0.
|
|
12
|
+
"@robodev-ai/sdk": "^0.13.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"robodev": "^0.
|
|
15
|
+
"robodev": "^0.39.0",
|
|
16
16
|
"rulesync": "^8.18.0",
|
|
17
17
|
"typescript": "^5.9.2"
|
|
18
18
|
}
|