@irtio/cli 0.5.1 → 0.6.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/dist/api.d.ts +52 -0
- package/dist/api.js +15 -0
- package/dist/bundle.js +1 -1
- package/dist/{static-deploy-BP3MDXCP.js → chunk-3HQMVCYA.js} +89 -48
- package/dist/chunk-DKWG7MGO.js +93 -0
- package/dist/{chunk-KRQUAEN2.js → chunk-OTSFRVJN.js} +12 -6
- package/dist/{chunk-I37DLT7K.js → chunk-RNAH5T4W.js} +14 -3
- package/dist/chunk-RQSJZWQC.js +452 -0
- package/dist/{chunk-NVUKSP5U.js → chunk-UPHQM6NZ.js} +12 -1
- package/dist/chunk-ZD4ND6X6.js +31 -0
- package/dist/chunk-ZK5JLUD4.js +94 -0
- package/dist/credentials.d.ts +61 -0
- package/dist/credentials.js +20 -0
- package/dist/delete-project-VENS2B44.js +118 -0
- package/dist/deploy.d.ts +149 -0
- package/dist/deploy.js +567 -0
- package/dist/{dev-7UZZGE4U.js → dev-QM26ONKS.js} +3095 -294
- package/dist/index.js +113 -28
- package/dist/init.d.ts +2 -1
- package/dist/init.js +42 -1
- package/dist/{keys-KEKO3EJ6.js → keys-JHLMEGRA.js} +49 -18
- package/dist/leaderboard-SYPSBPS3.js +352 -0
- package/dist/{login-OV2EFNTJ.js → login-2M73HBZT.js} +25 -1
- package/dist/{logs-5OUDPAIX.js → logs-2W7CPZO5.js} +42 -17
- package/dist/{migrate-UD245ULI.js → migrate-T3DZJREY.js} +44 -19
- package/dist/ratings-VG32WFDG.js +297 -0
- package/dist/{rollback-CLQVYFHW.js → rollback-SO74MVZV.js} +41 -17
- package/dist/{rooms-OJ3JLYHD.js → rooms-VI33P4RA.js} +73 -20
- package/dist/simulate.d.ts +215 -3
- package/dist/simulate.js +865 -64
- package/dist/static-deploy-KOWFKWZA.js +19 -0
- package/dist/status-HF3ZEKB7.js +219 -0
- package/dist/usage-4G23QXCH.js +213 -0
- package/dist/{whoami-S73O6KJF.js → whoami-KTMTQNHM.js} +21 -2
- package/package.json +24 -7
- package/dist/chunk-BPE452KF.js +0 -180
- package/dist/chunk-D7CDJRFF.js +0 -24
- package/dist/deploy-YVCVDVMS.js +0 -396
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A thin typed `fetch` wrapper around `@irtio/control`'s `/v1` API (contract.ts). Two things
|
|
3
|
+
* earn this its own module rather than inlined `fetch` calls in every command:
|
|
4
|
+
*
|
|
5
|
+
* 1. **`ApiError` becomes a real thrown error.** The control plane's error bodies (`code`,
|
|
6
|
+
* `message`, and — on `E_BREAKING_SCHEMA` — `changes`/`hint`) are exactly what `irtio deploy`
|
|
7
|
+
* needs to print verbatim; `ApiClientError` carries them through instead of a generic
|
|
8
|
+
* "request failed" the command layer would have to re-parse.
|
|
9
|
+
* 2. **401 is a command, not an error.** Every command that hits the control plane wants the same
|
|
10
|
+
* "run: irtio login" line and exit code 1, so it lives here once.
|
|
11
|
+
*/
|
|
12
|
+
declare class ApiClientError extends Error {
|
|
13
|
+
readonly name = "ApiClientError";
|
|
14
|
+
readonly status: number;
|
|
15
|
+
readonly code: string;
|
|
16
|
+
readonly changes: readonly unknown[] | undefined;
|
|
17
|
+
readonly hint: string | undefined;
|
|
18
|
+
constructor(status: number, body: {
|
|
19
|
+
code: string;
|
|
20
|
+
message: string;
|
|
21
|
+
changes?: readonly unknown[];
|
|
22
|
+
hint?: string;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/** Thrown by `requireApiClient`/callers when there is no stored token: the login nudge. */
|
|
26
|
+
declare class NotLoggedInError extends Error {
|
|
27
|
+
readonly controlUrl: string;
|
|
28
|
+
readonly name = "NotLoggedInError";
|
|
29
|
+
constructor(controlUrl: string);
|
|
30
|
+
}
|
|
31
|
+
interface ApiClient {
|
|
32
|
+
readonly controlUrl: string;
|
|
33
|
+
get<T>(path: string, query?: Record<string, string | undefined>): Promise<T>;
|
|
34
|
+
post<T>(path: string, body?: unknown): Promise<T>;
|
|
35
|
+
patch<T>(path: string, body?: unknown): Promise<T>;
|
|
36
|
+
/**
|
|
37
|
+
* `DELETE`, with the same query-string handling as `get`. The one route that needs it today is
|
|
38
|
+
* `DELETE /v1/projects/:id/origins`, which names the origin to remove in the query rather than
|
|
39
|
+
* the body, so a client without this method cannot remove an origin at all.
|
|
40
|
+
*/
|
|
41
|
+
del<T>(path: string, query?: Record<string, string | undefined>): Promise<T>;
|
|
42
|
+
/** Uploads raw bytes (a bundle) rather than JSON. */
|
|
43
|
+
postBytes<T>(path: string, bytes: Uint8Array): Promise<T>;
|
|
44
|
+
}
|
|
45
|
+
/** Builds a client bound to one control URL, using whatever token `irtio login` last stored. */
|
|
46
|
+
declare function createApiClient(controlUrl: string): Promise<ApiClient>;
|
|
47
|
+
/** Same as `createApiClient`, but with an explicit token — what tests use. */
|
|
48
|
+
declare function createApiClientWithToken(controlUrl: string, token: string): ApiClient;
|
|
49
|
+
/** True when `err` is the "please log in" case — the shape every command checks for. */
|
|
50
|
+
declare function isLoginRequired(err: unknown): boolean;
|
|
51
|
+
|
|
52
|
+
export { type ApiClient, ApiClientError, NotLoggedInError, createApiClient, createApiClientWithToken, isLoginRequired };
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ApiClientError,
|
|
3
|
+
NotLoggedInError,
|
|
4
|
+
createApiClient,
|
|
5
|
+
createApiClientWithToken,
|
|
6
|
+
isLoginRequired
|
|
7
|
+
} from "./chunk-RNAH5T4W.js";
|
|
8
|
+
import "./chunk-UPHQM6NZ.js";
|
|
9
|
+
export {
|
|
10
|
+
ApiClientError,
|
|
11
|
+
NotLoggedInError,
|
|
12
|
+
createApiClient,
|
|
13
|
+
createApiClientWithToken,
|
|
14
|
+
isLoginRequired
|
|
15
|
+
};
|
package/dist/bundle.js
CHANGED
|
@@ -1,33 +1,66 @@
|
|
|
1
|
-
import {
|
|
2
|
-
readIrtioJsonName
|
|
3
|
-
} from "./chunk-D7CDJRFF.js";
|
|
4
1
|
import {
|
|
5
2
|
STATIC_LIMITS,
|
|
6
3
|
staticPathProblem
|
|
7
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-RQSJZWQC.js";
|
|
5
|
+
import {
|
|
6
|
+
ensureProject,
|
|
7
|
+
readProjectConfig
|
|
8
|
+
} from "./chunk-DKWG7MGO.js";
|
|
9
|
+
import {
|
|
10
|
+
HelpRequested,
|
|
11
|
+
helpFor,
|
|
12
|
+
helpRequested
|
|
13
|
+
} from "./chunk-ZD4ND6X6.js";
|
|
8
14
|
import {
|
|
9
|
-
ApiClientError,
|
|
10
15
|
createApiClient,
|
|
11
16
|
isLoginRequired
|
|
12
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-RNAH5T4W.js";
|
|
13
18
|
import {
|
|
14
19
|
resolveControlUrlForUser
|
|
15
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-UPHQM6NZ.js";
|
|
16
21
|
|
|
17
22
|
// src/static-deploy.ts
|
|
18
23
|
import { existsSync } from "fs";
|
|
19
24
|
import { lstat, readFile, readdir } from "fs/promises";
|
|
20
25
|
import * as path from "path";
|
|
26
|
+
import pc2 from "picocolors";
|
|
27
|
+
|
|
28
|
+
// src/deploy-log.ts
|
|
21
29
|
import pc from "picocolors";
|
|
30
|
+
function logEnsured(log, projectId, ensured) {
|
|
31
|
+
if (ensured.action === "created") log(pc.dim(`created project ${projectId} (${ensured.name})`));
|
|
32
|
+
if (ensured.action === "renamed") {
|
|
33
|
+
log(pc.dim(`renamed project ${projectId}: ${ensured.from} -> ${ensured.name}`));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/static-deploy.ts
|
|
38
|
+
var USAGE = `usage: irtio deploy --static [<dir>] [options]
|
|
39
|
+
|
|
40
|
+
Uploads a BUILT client directory to the project's static site and prints the playable link. There
|
|
41
|
+
is no build step: point it at the output your bundler already produced.
|
|
42
|
+
|
|
43
|
+
options:
|
|
44
|
+
[<dir>] the directory to upload (default: the project file's "static")
|
|
45
|
+
--label <label> the site's hostname label (<label>.irt-serve.com)
|
|
46
|
+
--project <id> project id (default: the project file)
|
|
47
|
+
--name <name> the name a first deploy registers the project under
|
|
48
|
+
-c, --config <f> the project file to read (default irtio.json)
|
|
49
|
+
--url <control> control plane (default: your stored login)
|
|
50
|
+
-h, --help print this
|
|
51
|
+
`;
|
|
22
52
|
function parseStaticDeployArgs(args) {
|
|
53
|
+
if (helpRequested(args)) throw helpFor(USAGE);
|
|
23
54
|
const parsed = {};
|
|
24
55
|
for (let i = 0; i < args.length; i++) {
|
|
25
56
|
const arg = args[i];
|
|
26
57
|
switch (arg) {
|
|
27
58
|
case "--static": {
|
|
28
|
-
const value = args[
|
|
29
|
-
if (value
|
|
30
|
-
|
|
59
|
+
const value = args[i + 1];
|
|
60
|
+
if (value !== void 0 && !value.startsWith("-")) {
|
|
61
|
+
parsed.dir = value;
|
|
62
|
+
i++;
|
|
63
|
+
}
|
|
31
64
|
break;
|
|
32
65
|
}
|
|
33
66
|
case "--project": {
|
|
@@ -56,11 +89,19 @@ function parseStaticDeployArgs(args) {
|
|
|
56
89
|
parsed.name = value;
|
|
57
90
|
break;
|
|
58
91
|
}
|
|
92
|
+
case "-c":
|
|
93
|
+
case "--config": {
|
|
94
|
+
const value = args[++i];
|
|
95
|
+
if (value === void 0 || value === "") {
|
|
96
|
+
throw new Error("irtio deploy: --config needs a value");
|
|
97
|
+
}
|
|
98
|
+
parsed.config = value;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
59
101
|
default:
|
|
60
102
|
throw new Error(`irtio deploy --static: unknown option ${JSON.stringify(arg)}`);
|
|
61
103
|
}
|
|
62
104
|
}
|
|
63
|
-
if (parsed.dir === void 0) throw new Error("irtio deploy: --static needs a directory");
|
|
64
105
|
return parsed;
|
|
65
106
|
}
|
|
66
107
|
async function walkStaticDir(dir) {
|
|
@@ -95,41 +136,27 @@ async function walkStaticDir(dir) {
|
|
|
95
136
|
await walk(dir, "");
|
|
96
137
|
return { files, skipped };
|
|
97
138
|
}
|
|
98
|
-
async function readIrtioJsonProject(cwd) {
|
|
99
|
-
const file = path.join(cwd, "irtio.json");
|
|
100
|
-
if (!existsSync(file)) return void 0;
|
|
101
|
-
let parsed;
|
|
102
|
-
try {
|
|
103
|
-
parsed = JSON.parse(await readFile(file, "utf8"));
|
|
104
|
-
} catch {
|
|
105
|
-
throw new Error(`irtio deploy: ${file} is not valid JSON`);
|
|
106
|
-
}
|
|
107
|
-
return typeof parsed.project === "string" && parsed.project.length > 0 ? parsed.project : void 0;
|
|
108
|
-
}
|
|
109
|
-
async function ensureProject(client, projectId, cwd, explicitName, log) {
|
|
110
|
-
try {
|
|
111
|
-
await client.get(`/v1/projects/${projectId}`);
|
|
112
|
-
return;
|
|
113
|
-
} catch (err) {
|
|
114
|
-
if (!(err instanceof ApiClientError && err.status === 404)) throw err;
|
|
115
|
-
}
|
|
116
|
-
const name = explicitName ?? await readIrtioJsonName(cwd, "irtio deploy") ?? path.basename(cwd);
|
|
117
|
-
await client.post("/v1/projects", { name, id: projectId });
|
|
118
|
-
log(pc.dim(`created project ${projectId} (${name})`));
|
|
119
|
-
}
|
|
120
139
|
async function runStaticDeploy(options) {
|
|
121
140
|
const log = options.log ?? ((line) => console.log(line));
|
|
122
141
|
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
123
|
-
const
|
|
142
|
+
const config = await readProjectConfig(cwd, "irtio deploy", options.config);
|
|
143
|
+
const relativeDir = options.dir ?? config.static?.dir;
|
|
144
|
+
if (relativeDir === void 0) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`irtio deploy: no directory to upload \u2014 pass --static <dir> or add "static" to ${path.basename(config.file)}`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
const dir = path.resolve(cwd, relativeDir);
|
|
124
150
|
if (!existsSync(dir)) throw new Error(`irtio deploy: ${dir} does not exist`);
|
|
125
|
-
const project = options.project ??
|
|
151
|
+
const project = options.project ?? config.project;
|
|
126
152
|
if (project === void 0) {
|
|
127
153
|
throw new Error(
|
|
128
|
-
|
|
154
|
+
`irtio deploy: no project id \u2014 pass --project or run from a project with ${path.basename(config.file)}`
|
|
129
155
|
);
|
|
130
156
|
}
|
|
157
|
+
const label = options.label ?? config.static?.label;
|
|
131
158
|
const { files, skipped } = await walkStaticDir(dir);
|
|
132
|
-
for (const line of skipped) log(
|
|
159
|
+
for (const line of skipped) log(pc2.yellow(`skipped ${line}`));
|
|
133
160
|
if (files.length === 0) throw new Error(`irtio deploy: ${dir} has no files to upload`);
|
|
134
161
|
if (files.length > STATIC_LIMITS.maxFiles) {
|
|
135
162
|
throw new Error(
|
|
@@ -150,15 +177,21 @@ async function runStaticDeploy(options) {
|
|
|
150
177
|
}
|
|
151
178
|
if (!files.some((f) => f.rel === "index.html")) {
|
|
152
179
|
log(
|
|
153
|
-
|
|
180
|
+
pc2.yellow("note: no index.html at the top level \u2014 the site link will 404 until one exists")
|
|
154
181
|
);
|
|
155
182
|
}
|
|
156
183
|
const controlUrl = await resolveControlUrlForUser(options.controlUrl);
|
|
157
184
|
const client = options.client ?? await createApiClient(controlUrl);
|
|
158
|
-
await ensureProject(client, project,
|
|
185
|
+
const ensured = await ensureProject(client, project, {
|
|
186
|
+
cwd,
|
|
187
|
+
config,
|
|
188
|
+
command: "irtio deploy",
|
|
189
|
+
...options.name !== void 0 ? { name: options.name } : {}
|
|
190
|
+
});
|
|
191
|
+
logEnsured(log, project, ensured);
|
|
159
192
|
const begin = await client.post(
|
|
160
193
|
`/v1/projects/${project}/static/begin`,
|
|
161
|
-
|
|
194
|
+
label !== void 0 ? { label } : {}
|
|
162
195
|
);
|
|
163
196
|
for (const file of files) {
|
|
164
197
|
const bytes = await readFile(file.abs);
|
|
@@ -173,13 +206,13 @@ async function runStaticDeploy(options) {
|
|
|
173
206
|
label: begin.label
|
|
174
207
|
});
|
|
175
208
|
if (activated.url !== void 0) {
|
|
176
|
-
log(
|
|
209
|
+
log(pc2.green(`live: ${activated.url}`));
|
|
177
210
|
if (activated.origin !== void 0) {
|
|
178
|
-
log(
|
|
211
|
+
log(pc2.dim(`allowed origin: ${activated.origin} (localhost is always allowed)`));
|
|
179
212
|
}
|
|
180
213
|
} else {
|
|
181
214
|
log(
|
|
182
|
-
|
|
215
|
+
pc2.green(
|
|
183
216
|
`activated ${activated.label} v${activated.version} (this control plane has no static domain configured; nothing serves it yet)`
|
|
184
217
|
)
|
|
185
218
|
);
|
|
@@ -200,29 +233,37 @@ async function staticDeploy(args, deps = {}) {
|
|
|
200
233
|
try {
|
|
201
234
|
const parsed = parseStaticDeployArgs(args);
|
|
202
235
|
await runStaticDeploy({
|
|
203
|
-
dir: parsed.dir,
|
|
204
236
|
log,
|
|
237
|
+
...parsed.dir !== void 0 ? { dir: parsed.dir } : {},
|
|
205
238
|
...parsed.project !== void 0 ? { project: parsed.project } : {},
|
|
206
239
|
...parsed.url !== void 0 ? { controlUrl: parsed.url } : {},
|
|
207
240
|
...parsed.label !== void 0 ? { label: parsed.label } : {},
|
|
208
241
|
...parsed.name !== void 0 ? { name: parsed.name } : {},
|
|
242
|
+
...parsed.config !== void 0 ? { config: parsed.config } : {},
|
|
209
243
|
...deps.client !== void 0 ? { client: deps.client } : {},
|
|
210
244
|
...deps.cwd !== void 0 ? { cwd: deps.cwd } : {}
|
|
211
245
|
});
|
|
212
246
|
} catch (err) {
|
|
247
|
+
if (err instanceof HelpRequested) {
|
|
248
|
+
log(err.usage);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
213
251
|
if (isLoginRequired(err)) {
|
|
214
|
-
errorLog(
|
|
252
|
+
errorLog(pc2.red("not logged in"));
|
|
215
253
|
errorLog("run: irtio login");
|
|
216
254
|
process.exitCode = 1;
|
|
217
255
|
return;
|
|
218
256
|
}
|
|
219
|
-
errorLog(
|
|
257
|
+
errorLog(pc2.red(err instanceof Error ? err.message : String(err)));
|
|
220
258
|
process.exitCode = 1;
|
|
221
259
|
}
|
|
222
260
|
}
|
|
261
|
+
|
|
223
262
|
export {
|
|
263
|
+
logEnsured,
|
|
264
|
+
USAGE,
|
|
224
265
|
parseStaticDeployArgs,
|
|
266
|
+
walkStaticDir,
|
|
225
267
|
runStaticDeploy,
|
|
226
|
-
staticDeploy
|
|
227
|
-
walkStaticDir
|
|
268
|
+
staticDeploy
|
|
228
269
|
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ApiClientError
|
|
3
|
+
} from "./chunk-RNAH5T4W.js";
|
|
4
|
+
|
|
5
|
+
// src/project-file.ts
|
|
6
|
+
import { existsSync } from "fs";
|
|
7
|
+
import { readFile } from "fs/promises";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
var DEFAULT_CONFIG_FILE = "irtio.json";
|
|
10
|
+
function configPath(cwd, configFile) {
|
|
11
|
+
return path.resolve(cwd, configFile ?? DEFAULT_CONFIG_FILE);
|
|
12
|
+
}
|
|
13
|
+
async function readProjectConfig(cwd, command, configFile) {
|
|
14
|
+
const file = configPath(cwd, configFile);
|
|
15
|
+
if (!existsSync(file)) {
|
|
16
|
+
if (configFile !== void 0) throw new Error(`${command}: no config file at ${file}`);
|
|
17
|
+
return { file, exists: false };
|
|
18
|
+
}
|
|
19
|
+
let parsed;
|
|
20
|
+
try {
|
|
21
|
+
parsed = JSON.parse(await readFile(file, "utf8"));
|
|
22
|
+
} catch (err) {
|
|
23
|
+
throw new Error(`${command}: ${file} is not valid JSON: ${String(err)}`);
|
|
24
|
+
}
|
|
25
|
+
const raw = typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
26
|
+
const str = (key) => {
|
|
27
|
+
const value = raw[key];
|
|
28
|
+
if (value === void 0) return void 0;
|
|
29
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
30
|
+
throw new Error(`${command}: ${file} has a "${key}" that is not a non-empty string`);
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
};
|
|
34
|
+
const project = str("project");
|
|
35
|
+
const name = str("name");
|
|
36
|
+
const client = str("client");
|
|
37
|
+
const staticSite = readStatic(raw.static, file, command);
|
|
38
|
+
return {
|
|
39
|
+
file,
|
|
40
|
+
exists: true,
|
|
41
|
+
...project !== void 0 ? { project } : {},
|
|
42
|
+
...name !== void 0 ? { name } : {},
|
|
43
|
+
...client !== void 0 ? { client } : {},
|
|
44
|
+
...staticSite !== void 0 ? { static: staticSite } : {}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function readStatic(value, file, command) {
|
|
48
|
+
if (value === void 0) return void 0;
|
|
49
|
+
if (typeof value === "string") {
|
|
50
|
+
if (value.length === 0) {
|
|
51
|
+
throw new Error(`${command}: ${file} has a "static" that is an empty string`);
|
|
52
|
+
}
|
|
53
|
+
return { dir: value };
|
|
54
|
+
}
|
|
55
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`${command}: ${file} has a "static" that is neither a directory string nor an object`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
const { dir, label } = value;
|
|
61
|
+
if (typeof dir !== "string" || dir.length === 0) {
|
|
62
|
+
throw new Error(`${command}: ${file} has a "static" with no "dir"`);
|
|
63
|
+
}
|
|
64
|
+
if (label !== void 0 && (typeof label !== "string" || label.length === 0)) {
|
|
65
|
+
throw new Error(`${command}: ${file} has a "static.label" that is not a non-empty string`);
|
|
66
|
+
}
|
|
67
|
+
return label === void 0 ? { dir } : { dir, label };
|
|
68
|
+
}
|
|
69
|
+
async function ensureProject(client, projectId, options) {
|
|
70
|
+
const config = options.config ?? await readProjectConfig(options.cwd, options.command);
|
|
71
|
+
const declared = options.name ?? config.name;
|
|
72
|
+
let existing;
|
|
73
|
+
try {
|
|
74
|
+
existing = await client.get(`/v1/projects/${projectId}`);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
if (!(err instanceof ApiClientError && err.status === 404)) throw err;
|
|
77
|
+
}
|
|
78
|
+
if (existing === void 0) {
|
|
79
|
+
const name = declared ?? path.basename(options.cwd);
|
|
80
|
+
await client.post("/v1/projects", { name, id: projectId });
|
|
81
|
+
return { action: "created", name };
|
|
82
|
+
}
|
|
83
|
+
if (declared !== void 0 && declared !== existing.name) {
|
|
84
|
+
await client.patch(`/v1/projects/${projectId}`, { name: declared });
|
|
85
|
+
return { action: "renamed", name: declared, from: existing.name };
|
|
86
|
+
}
|
|
87
|
+
return { action: "unchanged", name: existing.name };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export {
|
|
91
|
+
readProjectConfig,
|
|
92
|
+
ensureProject
|
|
93
|
+
};
|
|
@@ -5,8 +5,14 @@ import * as path from "path";
|
|
|
5
5
|
import { pathToFileURL } from "url";
|
|
6
6
|
import { Worker } from "worker_threads";
|
|
7
7
|
import * as esbuild from "esbuild";
|
|
8
|
-
var ALLOWED_IMPORTS = [
|
|
9
|
-
|
|
8
|
+
var ALLOWED_IMPORTS = [
|
|
9
|
+
"@irtio/server",
|
|
10
|
+
"@irtio/schema",
|
|
11
|
+
"@dimforge/rapier3d-compat",
|
|
12
|
+
// D45: the second blessed engine. Exactly one import added, as the decision says.
|
|
13
|
+
"matter-js"
|
|
14
|
+
];
|
|
15
|
+
var EXTERNAL_IMPORTS = ["@dimforge/rapier3d-compat", "matter-js"];
|
|
10
16
|
var BundleError = class extends Error {
|
|
11
17
|
name = "BundleError";
|
|
12
18
|
};
|
|
@@ -85,8 +91,8 @@ async function bundleRoom(options) {
|
|
|
85
91
|
const out = result.outputFiles[0];
|
|
86
92
|
if (!out) throw new BundleError("esbuild produced no output");
|
|
87
93
|
const warnings = result.warnings.map((w) => w.text);
|
|
88
|
-
const
|
|
89
|
-
(o) => o.imports.some((i) => i.path === "@dimforge/rapier3d-compat")
|
|
94
|
+
const importsEngine = Object.values(result.metafile?.outputs ?? {}).some(
|
|
95
|
+
(o) => o.imports.some((i) => i.path === "@dimforge/rapier3d-compat" || i.path === "matter-js")
|
|
90
96
|
);
|
|
91
97
|
const hash = createHash("sha256").update(out.contents).digest("hex");
|
|
92
98
|
const file = path.join(options.outDir, `room.${hash.slice(0, 16)}.mjs`);
|
|
@@ -108,9 +114,9 @@ async function bundleRoom(options) {
|
|
|
108
114
|
}
|
|
109
115
|
if (options.verify === false) return { file, hash, warnings };
|
|
110
116
|
const verified = await verifyBundle(file);
|
|
111
|
-
if (
|
|
117
|
+
if (importsEngine && !verified.physics) {
|
|
112
118
|
throw new BundleError(
|
|
113
|
-
"irtio: this room imports
|
|
119
|
+
"irtio: this room imports a physics engine but declares no physics. Add\n physics: { engine: 'rapier3d', gravity: { x: 0, y: -9.81, z: 0 }, bodies: { ... } }\nor, for the 2D engine,\n physics: { engine: 'matter2d', gravity: { x: 0, y: 1 }, bodies: { ... } }\nto defineRoom(...), and mark the body-backed fields with the schema's physics option. Without it the runtime never creates a world, and nothing you build with the engine steps."
|
|
114
120
|
);
|
|
115
121
|
}
|
|
116
122
|
const fullHash = createHash("sha256").update(out.contents).update(verified.canonical).digest("hex");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readCredential
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-UPHQM6NZ.js";
|
|
4
4
|
|
|
5
5
|
// src/api-client.ts
|
|
6
6
|
var ApiClientError = class extends Error {
|
|
@@ -60,15 +60,24 @@ function createApiClientWithToken(controlUrl, token) {
|
|
|
60
60
|
}
|
|
61
61
|
return parsed;
|
|
62
62
|
}
|
|
63
|
+
function withQuery(path, query) {
|
|
64
|
+
const qs = query ? Object.entries(query).filter((e) => e[1] !== void 0).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&") : "";
|
|
65
|
+
return qs ? `${path}?${qs}` : path;
|
|
66
|
+
}
|
|
63
67
|
return {
|
|
64
68
|
controlUrl,
|
|
65
69
|
get(path, query) {
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
return request("GET", withQuery(path, query));
|
|
71
|
+
},
|
|
72
|
+
del(path, query) {
|
|
73
|
+
return request("DELETE", withQuery(path, query));
|
|
68
74
|
},
|
|
69
75
|
post(path, body) {
|
|
70
76
|
return request("POST", path, { body });
|
|
71
77
|
},
|
|
78
|
+
patch(path, body) {
|
|
79
|
+
return request("PATCH", path, { body });
|
|
80
|
+
},
|
|
72
81
|
postBytes(path, bytes) {
|
|
73
82
|
return request("POST", path, { bytes });
|
|
74
83
|
}
|
|
@@ -80,6 +89,8 @@ function isLoginRequired(err) {
|
|
|
80
89
|
|
|
81
90
|
export {
|
|
82
91
|
ApiClientError,
|
|
92
|
+
NotLoggedInError,
|
|
83
93
|
createApiClient,
|
|
94
|
+
createApiClientWithToken,
|
|
84
95
|
isLoginRequired
|
|
85
96
|
};
|