rowork 0.1.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 (187) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +95 -0
  3. package/bin/rowork.js +18 -0
  4. package/dashboard/app.css +234 -0
  5. package/dashboard/app.js +117 -0
  6. package/dashboard/index.html +67 -0
  7. package/dist/cli/errors.d.ts +15 -0
  8. package/dist/cli/errors.js +18 -0
  9. package/dist/cli/program.d.ts +19 -0
  10. package/dist/cli/program.js +59 -0
  11. package/dist/cli/registry.d.ts +37 -0
  12. package/dist/cli/registry.js +61 -0
  13. package/dist/commands/add.d.ts +8 -0
  14. package/dist/commands/add.js +77 -0
  15. package/dist/commands/assets-setup.d.ts +18 -0
  16. package/dist/commands/assets-setup.js +136 -0
  17. package/dist/commands/assets.d.ts +2 -0
  18. package/dist/commands/assets.js +153 -0
  19. package/dist/commands/console.d.ts +4 -0
  20. package/dist/commands/console.js +141 -0
  21. package/dist/commands/dashboard.d.ts +2 -0
  22. package/dist/commands/dashboard.js +50 -0
  23. package/dist/commands/dev-background.d.ts +3 -0
  24. package/dist/commands/dev-background.js +87 -0
  25. package/dist/commands/dev.d.ts +2 -0
  26. package/dist/commands/dev.js +270 -0
  27. package/dist/commands/eject.d.ts +2 -0
  28. package/dist/commands/eject.js +136 -0
  29. package/dist/commands/index.d.ts +8 -0
  30. package/dist/commands/index.js +54 -0
  31. package/dist/commands/info.d.ts +3 -0
  32. package/dist/commands/info.js +50 -0
  33. package/dist/commands/init.d.ts +2 -0
  34. package/dist/commands/init.js +55 -0
  35. package/dist/commands/links.d.ts +32 -0
  36. package/dist/commands/links.js +152 -0
  37. package/dist/commands/make-event.d.ts +2 -0
  38. package/dist/commands/make-event.js +160 -0
  39. package/dist/commands/make-menu.d.ts +3 -0
  40. package/dist/commands/make-menu.js +36 -0
  41. package/dist/commands/make-stat.d.ts +2 -0
  42. package/dist/commands/make-stat.js +199 -0
  43. package/dist/commands/make-tool.d.ts +2 -0
  44. package/dist/commands/make-tool.js +124 -0
  45. package/dist/commands/make-ui.d.ts +3 -0
  46. package/dist/commands/make-ui.js +199 -0
  47. package/dist/commands/make.d.ts +21 -0
  48. package/dist/commands/make.js +181 -0
  49. package/dist/commands/next-steps.d.ts +5 -0
  50. package/dist/commands/next-steps.js +18 -0
  51. package/dist/commands/start.d.ts +2 -0
  52. package/dist/commands/start.js +144 -0
  53. package/dist/commands/studio.d.ts +3 -0
  54. package/dist/commands/studio.js +45 -0
  55. package/dist/commands/update.d.ts +2 -0
  56. package/dist/commands/update.js +173 -0
  57. package/dist/commands/wire.d.ts +2 -0
  58. package/dist/commands/wire.js +34 -0
  59. package/dist/core/agent-docs.d.ts +23 -0
  60. package/dist/core/agent-docs.js +151 -0
  61. package/dist/core/assets.d.ts +96 -0
  62. package/dist/core/assets.js +268 -0
  63. package/dist/core/background.d.ts +47 -0
  64. package/dist/core/background.js +149 -0
  65. package/dist/core/config.d.ts +9 -0
  66. package/dist/core/config.js +62 -0
  67. package/dist/core/exec.d.ts +22 -0
  68. package/dist/core/exec.js +38 -0
  69. package/dist/core/format-generated.d.ts +12 -0
  70. package/dist/core/format-generated.js +28 -0
  71. package/dist/core/generate.d.ts +37 -0
  72. package/dist/core/generate.js +94 -0
  73. package/dist/core/github-release.d.ts +28 -0
  74. package/dist/core/github-release.js +38 -0
  75. package/dist/core/info.d.ts +77 -0
  76. package/dist/core/info.js +65 -0
  77. package/dist/core/modules.d.ts +32 -0
  78. package/dist/core/modules.js +265 -0
  79. package/dist/core/naming.d.ts +6 -0
  80. package/dist/core/naming.js +26 -0
  81. package/dist/core/open-cloud.d.ts +58 -0
  82. package/dist/core/open-cloud.js +170 -0
  83. package/dist/core/project-index.d.ts +26 -0
  84. package/dist/core/project-index.js +59 -0
  85. package/dist/core/rojo-edit.d.ts +18 -0
  86. package/dist/core/rojo-edit.js +53 -0
  87. package/dist/core/rokit-installer.d.ts +15 -0
  88. package/dist/core/rokit-installer.js +77 -0
  89. package/dist/core/scaffold.d.ts +44 -0
  90. package/dist/core/scaffold.js +163 -0
  91. package/dist/core/schema-edit.d.ts +46 -0
  92. package/dist/core/schema-edit.js +125 -0
  93. package/dist/core/source-scan.d.ts +24 -0
  94. package/dist/core/source-scan.js +84 -0
  95. package/dist/core/studio.d.ts +83 -0
  96. package/dist/core/studio.js +225 -0
  97. package/dist/core/toolchain.d.ts +31 -0
  98. package/dist/core/toolchain.js +98 -0
  99. package/dist/core/tsconfig-edit.d.ts +11 -0
  100. package/dist/core/tsconfig-edit.js +47 -0
  101. package/dist/core/ui-edit.d.ts +31 -0
  102. package/dist/core/ui-edit.js +89 -0
  103. package/dist/core/versions.d.ts +24 -0
  104. package/dist/core/versions.js +67 -0
  105. package/dist/dashboard/open.d.ts +3 -0
  106. package/dist/dashboard/open.js +18 -0
  107. package/dist/dashboard/server.d.ts +28 -0
  108. package/dist/dashboard/server.js +191 -0
  109. package/dist/index.d.ts +3 -0
  110. package/dist/index.js +3 -0
  111. package/dist/main.d.ts +9 -0
  112. package/dist/main.js +135 -0
  113. package/dist/modules/format.d.ts +3 -0
  114. package/dist/modules/format.js +28 -0
  115. package/dist/modules/index.d.ts +7 -0
  116. package/dist/modules/index.js +12 -0
  117. package/dist/modules/integrations.d.ts +8 -0
  118. package/dist/modules/integrations.js +54 -0
  119. package/dist/modules/leaderstats.d.ts +3 -0
  120. package/dist/modules/leaderstats.js +104 -0
  121. package/dist/modules/lint.d.ts +5 -0
  122. package/dist/modules/lint.js +43 -0
  123. package/dist/modules/networking.d.ts +23 -0
  124. package/dist/modules/networking.js +246 -0
  125. package/dist/modules/player-data.d.ts +19 -0
  126. package/dist/modules/player-data.js +176 -0
  127. package/dist/modules/types.d.ts +96 -0
  128. package/dist/modules/types.js +2 -0
  129. package/dist/modules/ui.d.ts +3 -0
  130. package/dist/modules/ui.js +67 -0
  131. package/dist/plugins/api.d.ts +127 -0
  132. package/dist/plugins/api.js +21 -0
  133. package/dist/plugins/loader.d.ts +20 -0
  134. package/dist/plugins/loader.js +128 -0
  135. package/dist/process/log-mux.d.ts +13 -0
  136. package/dist/process/log-mux.js +31 -0
  137. package/dist/process/supervisor.d.ts +48 -0
  138. package/dist/process/supervisor.js +141 -0
  139. package/dist/process/tree-kill.d.ts +25 -0
  140. package/dist/process/tree-kill.js +70 -0
  141. package/dist/templates/engine.d.ts +18 -0
  142. package/dist/templates/engine.js +54 -0
  143. package/dist/ui/logger.d.ts +5 -0
  144. package/dist/ui/logger.js +53 -0
  145. package/dist/ui/prompt.d.ts +9 -0
  146. package/dist/ui/prompt.js +24 -0
  147. package/package.json +69 -0
  148. package/templates/init/README.md.tmpl +37 -0
  149. package/templates/init/_gitignore.tmpl +23 -0
  150. package/templates/init/default.project.json.tmpl +57 -0
  151. package/templates/init/package.json.tmpl +12 -0
  152. package/templates/init/rokit.toml.tmpl +5 -0
  153. package/templates/init/src/client/controllers/ExampleController.ts.tmpl +12 -0
  154. package/templates/init/src/client/runtime.client.ts.tmpl +6 -0
  155. package/templates/init/src/server/runtime.server.ts.tmpl +12 -0
  156. package/templates/init/src/server/services/ExampleService.ts.tmpl +12 -0
  157. package/templates/init/src/shared/_gitkeep.tmpl +0 -0
  158. package/templates/init/tsconfig.json.tmpl +28 -0
  159. package/templates/integrations/data-replication/DataReplicationService.ts.tmpl +33 -0
  160. package/templates/integrations/data-replication/PlayerDataController.ts.tmpl +40 -0
  161. package/templates/integrations/data-replication/dataEvents.ts.tmpl +21 -0
  162. package/templates/make/component.ts.tmpl +8 -0
  163. package/templates/make/controller.ts.tmpl +6 -0
  164. package/templates/make/event-handler.ts.tmpl +22 -0
  165. package/templates/make/screen.tsx.tmpl +35 -0
  166. package/templates/make/service.ts.tmpl +6 -0
  167. package/templates/make/stat-service.ts.tmpl +26 -0
  168. package/templates/make/ui-button.tsx.tmpl +22 -0
  169. package/templates/make/ui-image.tsx.tmpl +11 -0
  170. package/templates/make/ui-label.tsx.tmpl +18 -0
  171. package/templates/make/ui-panel.tsx.tmpl +33 -0
  172. package/templates/make/ui-story.tsx.tmpl +13 -0
  173. package/templates/modules/format/prettierignore.tmpl +9 -0
  174. package/templates/modules/format/prettierrc.json.tmpl +4 -0
  175. package/templates/modules/leaderstats/LeaderstatsService.ts.tmpl +69 -0
  176. package/templates/modules/lint/eslint.config.mjs.tmpl +17 -0
  177. package/templates/modules/networking/client-network.ts.tmpl +9 -0
  178. package/templates/modules/networking/networking.ts.tmpl +22 -0
  179. package/templates/modules/networking/rate-limit.ts.tmpl +52 -0
  180. package/templates/modules/networking/server-network.ts.tmpl +20 -0
  181. package/templates/modules/player-data/PlayerData.ts.tmpl +15 -0
  182. package/templates/modules/player-data/PlayerDataService.ts.tmpl +127 -0
  183. package/templates/modules/ui/App.tsx.tmpl +19 -0
  184. package/templates/modules/ui/Button.story.tsx.tmpl +13 -0
  185. package/templates/modules/ui/Button.tsx.tmpl +21 -0
  186. package/templates/modules/ui/UiController.tsx.tmpl +21 -0
  187. package/templates/modules/ui/screens/HomeScreen.tsx.tmpl +20 -0
@@ -0,0 +1,67 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { readFileSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { getRelease } from "./github-release.js";
5
+ /**
6
+ * Used only when GitHub cannot be reached while creating a project (offline,
7
+ * rate limited). It is the one version Rowork writes down itself, so the weekly
8
+ * upstream check reports it when it falls behind.
9
+ */
10
+ export const FALLBACK_ROJO_VERSION = "7.7.0";
11
+ /**
12
+ * The Rojo version to use: the one forced by `ROWORK_ROJO_VERSION` if set (a team
13
+ * that wants every project on the same version, and no lookup), otherwise the
14
+ * newest release, or undefined when GitHub cannot be reached. GitHub allows 60
15
+ * anonymous requests per hour per address, so a shared network can hit its limit:
16
+ * the caller then falls back with a warning.
17
+ */
18
+ export async function latestRojoVersion() {
19
+ const forced = process.env["ROWORK_ROJO_VERSION"]?.trim().replace(/^v/, "");
20
+ if (forced !== undefined && /^\d+\.\d+\.\d+/.test(forced))
21
+ return forced;
22
+ try {
23
+ return (await getRelease("rojo-rbx/rojo")).tag_name.replace(/^v/, "");
24
+ }
25
+ catch {
26
+ return undefined;
27
+ }
28
+ }
29
+ /** The newest version of an npm package, or undefined if the registry cannot be reached. */
30
+ export function latestNpmVersion(name) {
31
+ const result = spawnSync("npm", ["view", name, "version"], {
32
+ encoding: "utf8",
33
+ shell: process.platform === "win32",
34
+ });
35
+ const version = result.stdout.trim();
36
+ return result.status === 0 && version !== "" ? version : undefined;
37
+ }
38
+ const ROJO_LINE = /^(\s*rojo\s*=\s*"rojo-rbx\/rojo@)([^"]+)(")/m;
39
+ export function readRojoPin(projectRoot) {
40
+ try {
41
+ return ROJO_LINE.exec(readFileSync(join(projectRoot, "rokit.toml"), "utf8"))?.[2];
42
+ }
43
+ catch {
44
+ return undefined;
45
+ }
46
+ }
47
+ /** Rewrites the Rojo version in rokit.toml, leaving the rest of the file alone. */
48
+ export function writeRojoPin(projectRoot, version) {
49
+ const file = join(projectRoot, "rokit.toml");
50
+ const source = readFileSync(file, "utf8");
51
+ if (!ROJO_LINE.test(source))
52
+ return false;
53
+ writeFileSync(file, source.replace(ROJO_LINE, `$1${version}$3`), "utf8");
54
+ return true;
55
+ }
56
+ /** `^3.0.0` and `~1.2.3` both mean `3.0.0` / `1.2.3` for comparison. */
57
+ export function baseVersion(range) {
58
+ return range.replace(/^[\^~>=<\s]+/, "");
59
+ }
60
+ /** True when `version` (like "7.7.0") is at least `major.minor`. An unknown version is not. */
61
+ export function rojoIsAtLeast(version, major, minor) {
62
+ if (version === undefined)
63
+ return false;
64
+ const [m = 0, n = 0] = version.split(".").map(Number);
65
+ return m > major || (m === major && n >= minor);
66
+ }
67
+ //# sourceMappingURL=versions.js.map
@@ -0,0 +1,3 @@
1
+ /** Opens an address in the default browser. Best effort: the address is always printed too. */
2
+ export declare function openBrowser(url: string): void;
3
+ //# sourceMappingURL=open.d.ts.map
@@ -0,0 +1,18 @@
1
+ import { spawn } from "node:child_process";
2
+ /** Opens an address in the default browser. Best effort: the address is always printed too. */
3
+ export function openBrowser(url) {
4
+ const [command, args] = process.platform === "darwin"
5
+ ? ["open", [url]]
6
+ : process.platform === "win32"
7
+ ? ["cmd", ["/c", "start", "", url]]
8
+ : ["xdg-open", [url]];
9
+ try {
10
+ const child = spawn(command, args, { stdio: "ignore", detached: true });
11
+ child.on("error", () => { });
12
+ child.unref();
13
+ }
14
+ catch {
15
+ // No browser to open: the printed address is enough.
16
+ }
17
+ }
18
+ //# sourceMappingURL=open.js.map
@@ -0,0 +1,28 @@
1
+ export interface DashboardOptions {
2
+ projectRoot: string;
3
+ roworkVersion: string;
4
+ /** 0 lets the system pick a free port. */
5
+ port: number;
6
+ }
7
+ export interface RunningDashboard {
8
+ url: string;
9
+ port: number;
10
+ close(): Promise<void>;
11
+ }
12
+ /**
13
+ * A local web dashboard for one project.
14
+ *
15
+ * This server can show logs and, later, run commands and use an API key, so it is
16
+ * built like something a hostile web page will try to reach:
17
+ *
18
+ * - It listens on 127.0.0.1 only, never on a network interface.
19
+ * - A random token, printed in the terminal, is required. It is exchanged once for a
20
+ * SameSite=Strict, HttpOnly cookie and removed from the URL, so it does not stay
21
+ * in the address bar, the history or a Referer header.
22
+ * - The Host header must be this very address. Without that check, a web page can
23
+ * point its own domain at 127.0.0.1 ("DNS rebinding") and talk to this server as
24
+ * if it were the same site.
25
+ * - Only GET is accepted, and only fixed files and fixed API routes exist.
26
+ */
27
+ export declare function startDashboard(options: DashboardOptions): Promise<RunningDashboard>;
28
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1,191 @@
1
+ import { randomBytes, timingSafeEqual } from "node:crypto";
2
+ import { existsSync, readFileSync, statSync } from "node:fs";
3
+ import { createReadStream } from "node:fs";
4
+ import { createServer } from "node:http";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { logFile, tailLog } from "../core/background.js";
8
+ import { loadConfig } from "../core/config.js";
9
+ import { collectInfo } from "../core/info.js";
10
+ /** Root of the shipped dashboard files, relative to the compiled dist/. */
11
+ function dashboardRoot() {
12
+ return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "dashboard");
13
+ }
14
+ /** The only files served: a fixed list, so no request can name a path of its own. */
15
+ const STATIC_FILES = {
16
+ "/": { file: "index.html", type: "text/html; charset=utf-8" },
17
+ "/app.js": { file: "app.js", type: "text/javascript; charset=utf-8" },
18
+ "/app.css": { file: "app.css", type: "text/css; charset=utf-8" },
19
+ };
20
+ const COOKIE = "rowork_dashboard";
21
+ function sameToken(given, expected) {
22
+ if (given === undefined)
23
+ return false;
24
+ const a = Buffer.from(given);
25
+ const b = Buffer.from(expected);
26
+ return a.length === b.length && timingSafeEqual(a, b);
27
+ }
28
+ function cookieToken(request) {
29
+ const header = request.headers.cookie;
30
+ if (header === undefined)
31
+ return undefined;
32
+ for (const part of header.split(";")) {
33
+ const [name, ...value] = part.trim().split("=");
34
+ if (name === COOKIE)
35
+ return value.join("=");
36
+ }
37
+ return undefined;
38
+ }
39
+ /**
40
+ * A local web dashboard for one project.
41
+ *
42
+ * This server can show logs and, later, run commands and use an API key, so it is
43
+ * built like something a hostile web page will try to reach:
44
+ *
45
+ * - It listens on 127.0.0.1 only, never on a network interface.
46
+ * - A random token, printed in the terminal, is required. It is exchanged once for a
47
+ * SameSite=Strict, HttpOnly cookie and removed from the URL, so it does not stay
48
+ * in the address bar, the history or a Referer header.
49
+ * - The Host header must be this very address. Without that check, a web page can
50
+ * point its own domain at 127.0.0.1 ("DNS rebinding") and talk to this server as
51
+ * if it were the same site.
52
+ * - Only GET is accepted, and only fixed files and fixed API routes exist.
53
+ */
54
+ export function startDashboard(options) {
55
+ const token = randomBytes(24).toString("hex");
56
+ const sockets = new Set();
57
+ let boundPort = 0;
58
+ const allowedHosts = () => [`127.0.0.1:${boundPort}`, `localhost:${boundPort}`];
59
+ const send = (response, status, body, type = "text/plain; charset=utf-8") => {
60
+ response.writeHead(status, {
61
+ "content-type": type,
62
+ "cache-control": "no-store",
63
+ "x-content-type-options": "nosniff",
64
+ "referrer-policy": "no-referrer",
65
+ "content-security-policy": "default-src 'self'; frame-ancestors 'none'",
66
+ });
67
+ response.end(body);
68
+ };
69
+ const server = createServer((request, response) => {
70
+ if (!allowedHosts().includes(request.headers.host ?? "")) {
71
+ send(response, 403, "Forbidden: unexpected Host header.");
72
+ return;
73
+ }
74
+ if (request.method !== "GET") {
75
+ send(response, 405, "Method not allowed.");
76
+ return;
77
+ }
78
+ const url = new URL(request.url ?? "/", `http://127.0.0.1:${boundPort}`);
79
+ // The first visit carries the token in the URL: trade it for a cookie and clean the address.
80
+ const queryToken = url.searchParams.get("token") ?? undefined;
81
+ if (queryToken !== undefined) {
82
+ if (!sameToken(queryToken, token)) {
83
+ send(response, 403, "Forbidden: wrong token.");
84
+ return;
85
+ }
86
+ response.writeHead(302, {
87
+ location: url.pathname,
88
+ "set-cookie": `${COOKIE}=${token}; HttpOnly; SameSite=Strict; Path=/`,
89
+ "cache-control": "no-store",
90
+ });
91
+ response.end();
92
+ return;
93
+ }
94
+ if (!sameToken(cookieToken(request), token)) {
95
+ send(response, 403, "Forbidden: open the address printed by `rowork dashboard`, it carries a one-time token.");
96
+ return;
97
+ }
98
+ const asset = STATIC_FILES[url.pathname];
99
+ if (asset !== undefined) {
100
+ const path = join(dashboardRoot(), asset.file);
101
+ if (!existsSync(path)) {
102
+ send(response, 500, `Missing dashboard file ${asset.file}.`);
103
+ return;
104
+ }
105
+ send(response, 200, readFileSync(path, "utf8"), asset.type);
106
+ return;
107
+ }
108
+ if (url.pathname === "/api/info") {
109
+ let config;
110
+ try {
111
+ config = loadConfig(options.projectRoot);
112
+ }
113
+ catch {
114
+ config = undefined;
115
+ }
116
+ const info = collectInfo({ config, projectRoot: options.projectRoot, roworkVersion: options.roworkVersion });
117
+ send(response, 200, JSON.stringify(info), "application/json; charset=utf-8");
118
+ return;
119
+ }
120
+ if (url.pathname === "/api/dev/logs") {
121
+ streamLogs(request, response, options.projectRoot);
122
+ return;
123
+ }
124
+ send(response, 404, "Not found.");
125
+ });
126
+ server.on("connection", (socket) => {
127
+ sockets.add(socket);
128
+ socket.on("close", () => sockets.delete(socket));
129
+ });
130
+ return new Promise((resolvePromise, rejectPromise) => {
131
+ server.once("error", rejectPromise);
132
+ server.listen(options.port, "127.0.0.1", () => {
133
+ const address = server.address();
134
+ boundPort = typeof address === "object" && address !== null ? address.port : options.port;
135
+ resolvePromise({
136
+ url: `http://127.0.0.1:${boundPort}/?token=${token}`,
137
+ port: boundPort,
138
+ close: () => new Promise((done) => {
139
+ // Server-sent event streams never end on their own: cut them.
140
+ for (const socket of sockets)
141
+ socket.destroy();
142
+ server.close(() => done());
143
+ }),
144
+ });
145
+ });
146
+ });
147
+ }
148
+ /**
149
+ * Sends the last lines of the background `rowork dev` log, then each new line as it
150
+ * is written, as server-sent events. Polling the file (rather than a watcher) is the
151
+ * same on every OS and survives the log being restarted by a new `dev -d`.
152
+ */
153
+ function streamLogs(request, response, projectRoot) {
154
+ response.writeHead(200, {
155
+ "content-type": "text/event-stream; charset=utf-8",
156
+ "cache-control": "no-store",
157
+ connection: "keep-alive",
158
+ "x-content-type-options": "nosniff",
159
+ });
160
+ const emit = (line) => {
161
+ response.write(`data: ${JSON.stringify(line)}\n\n`);
162
+ };
163
+ for (const line of tailLog(projectRoot, 200))
164
+ emit(line);
165
+ const file = logFile(projectRoot);
166
+ let offset = existsSync(file) ? statSync(file).size : 0;
167
+ let pending = "";
168
+ const timer = setInterval(() => {
169
+ if (!existsSync(file))
170
+ return;
171
+ const size = statSync(file).size;
172
+ if (size < offset)
173
+ offset = 0; // a new `dev -d` restarted the log
174
+ if (size === offset)
175
+ return;
176
+ const chunks = [];
177
+ const stream = createReadStream(file, { start: offset, end: size - 1 });
178
+ stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
179
+ stream.on("end", () => {
180
+ pending += Buffer.concat(chunks).toString("utf8");
181
+ const lines = pending.split("\n");
182
+ pending = lines.pop() ?? "";
183
+ for (const line of lines)
184
+ if (line !== "")
185
+ emit(line);
186
+ });
187
+ offset = size;
188
+ }, 400);
189
+ request.on("close", () => clearInterval(timer));
190
+ }
191
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1,3 @@
1
+ /** Library entry point: re-exports the public contract and nothing else. */
2
+ export * from "./plugins/api.js";
3
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ /** Library entry point: re-exports the public contract and nothing else. */
2
+ export * from "./plugins/api.js";
3
+ //# sourceMappingURL=index.js.map
package/dist/main.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Lets `rowork -d dev` mean `rowork dev -d`.
3
+ *
4
+ * Commander would treat a `-d` before the command as a global option, which
5
+ * does not exist. It is moved to just after the command name instead.
6
+ */
7
+ export declare function hoistDetach(argv: string[]): string[];
8
+ export declare function run(rawArgv: string[]): Promise<void>;
9
+ //# sourceMappingURL=main.d.ts.map
package/dist/main.js ADDED
@@ -0,0 +1,135 @@
1
+ import { createRequire } from "node:module";
2
+ import { resolve } from "node:path";
3
+ import pc from "picocolors";
4
+ import { isRoworkError } from "./cli/errors.js";
5
+ import { createProgram } from "./cli/program.js";
6
+ import { CommandRegistry, setActiveRegistry } from "./cli/registry.js";
7
+ import { findProjectRoot, loadConfig } from "./core/config.js";
8
+ import { coreCommands } from "./commands/index.js";
9
+ import { loadPlugins } from "./plugins/loader.js";
10
+ import { logger, setLogLevel } from "./ui/logger.js";
11
+ const require = createRequire(import.meta.url);
12
+ function roworkVersion() {
13
+ try {
14
+ return require("../package.json").version;
15
+ }
16
+ catch {
17
+ return "0.0.0";
18
+ }
19
+ }
20
+ /**
21
+ * Pre-reads the global flags.
22
+ *
23
+ * Commander only exposes them once parsing has started, but we need `--cwd`
24
+ * and `--no-plugins` BEFORE building the command list, since plugins add to it.
25
+ */
26
+ function readGlobalFlags(argv) {
27
+ let cwd = process.cwd();
28
+ let plugins = true;
29
+ for (let index = 0; index < argv.length; index += 1) {
30
+ const token = argv[index];
31
+ if (token === "--cwd") {
32
+ const value = argv[index + 1];
33
+ if (value !== undefined)
34
+ cwd = resolve(value);
35
+ }
36
+ else if (token !== undefined && token.startsWith("--cwd=")) {
37
+ cwd = resolve(token.slice("--cwd=".length));
38
+ }
39
+ else if (token === "--no-plugins") {
40
+ plugins = false;
41
+ }
42
+ else if (token === "--verbose") {
43
+ setLogLevel("debug");
44
+ }
45
+ else if (token === "--quiet") {
46
+ setLogLevel("error");
47
+ }
48
+ }
49
+ return { cwd, plugins };
50
+ }
51
+ /**
52
+ * Lets `rowork -d dev` mean `rowork dev -d`.
53
+ *
54
+ * Commander would treat a `-d` before the command as a global option, which
55
+ * does not exist. It is moved to just after the command name instead.
56
+ */
57
+ export function hoistDetach(argv) {
58
+ const head = argv.slice(0, 2);
59
+ const rest = argv.slice(2);
60
+ let detach = false;
61
+ const kept = [];
62
+ let commandIndex = -1;
63
+ for (let index = 0; index < rest.length; index += 1) {
64
+ const token = rest[index];
65
+ if (commandIndex === -1 && (token === "-d" || token === "--detach")) {
66
+ detach = true;
67
+ continue;
68
+ }
69
+ kept.push(token);
70
+ if (commandIndex === -1 && token === "--cwd") {
71
+ // The value of --cwd is not the command.
72
+ const value = rest[index + 1];
73
+ if (value !== undefined) {
74
+ kept.push(value);
75
+ index += 1;
76
+ }
77
+ }
78
+ else if (commandIndex === -1 && !token.startsWith("-")) {
79
+ commandIndex = kept.length - 1;
80
+ }
81
+ }
82
+ if (detach && commandIndex !== -1)
83
+ kept.splice(commandIndex + 1, 0, "--detach");
84
+ else if (detach)
85
+ kept.push("--detach");
86
+ return [...head, ...kept];
87
+ }
88
+ export async function run(rawArgv) {
89
+ const argv = hoistDetach(rawArgv);
90
+ const version = roworkVersion();
91
+ const { cwd, plugins: pluginsEnabled } = readGlobalFlags(argv.slice(2));
92
+ const registry = new CommandRegistry();
93
+ for (const definition of coreCommands) {
94
+ registry.register(definition, { kind: "core" });
95
+ }
96
+ const projectRoot = findProjectRoot(cwd);
97
+ let config;
98
+ if (projectRoot !== undefined) {
99
+ try {
100
+ config = loadConfig(projectRoot);
101
+ }
102
+ catch (error) {
103
+ // A broken config must not make the CLI unusable: `rowork --help` and
104
+ // project-independent commands have to keep working.
105
+ logger.warn(error instanceof Error ? error.message : String(error));
106
+ }
107
+ }
108
+ if (pluginsEnabled) {
109
+ await loadPlugins({ registry, projectRoot, config, cwd, roworkVersion: version });
110
+ }
111
+ else {
112
+ logger.debug("Plugin loading disabled (--no-plugins).");
113
+ }
114
+ setActiveRegistry(registry);
115
+ const program = createProgram({ registry, cwd, projectRoot, config, roworkVersion: version });
116
+ try {
117
+ await program.parseAsync(argv);
118
+ }
119
+ catch (error) {
120
+ reportFailure(error);
121
+ }
122
+ }
123
+ function reportFailure(error) {
124
+ if (isRoworkError(error)) {
125
+ logger.error(error.message);
126
+ if (error.hint !== undefined) {
127
+ process.stderr.write(`${pc.dim(" " + error.hint)}\n`);
128
+ }
129
+ process.exit(error.exitCode);
130
+ }
131
+ logger.error("Internal Rowork error. Please open an issue with the trace below.");
132
+ console.error(error);
133
+ process.exit(1);
134
+ }
135
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1,3 @@
1
+ import type { ModuleDefinition } from "./types.js";
2
+ export declare const formatModule: ModuleDefinition;
3
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1,28 @@
1
+ export const formatModule = {
2
+ name: "format",
3
+ title: "Formatter",
4
+ description: "Format the code the same way for everyone, automatically, with Prettier.",
5
+ dependencies: ["prettier"],
6
+ agentGuide: [
7
+ "Run `npm run format` after editing code: Prettier rewrites `src/` in the project's style (tabs, 100 columns). `npm run format:check` only reports. Do not argue with the formatter or hand-format around it.",
8
+ "Settings live in `.prettierrc.json`; what is never formatted (build output) is in `.prettierignore`.",
9
+ ],
10
+ plan() {
11
+ return {
12
+ files: [
13
+ { template: "format/prettierrc.json", directory: ".", fileName: ".prettierrc.json", variables: {} },
14
+ { template: "format/prettierignore", directory: ".", fileName: ".prettierignore", variables: {} },
15
+ ],
16
+ register: [],
17
+ scripts: {
18
+ format: "prettier --write src",
19
+ "format:check": "prettier --check src",
20
+ },
21
+ notes: [
22
+ "Format your code: npm run format Only check: npm run format:check",
23
+ "Tabs and 100 columns, to match the code Rowork generates. Change it in .prettierrc.json.",
24
+ ],
25
+ };
26
+ },
27
+ };
28
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1,7 @@
1
+ import type { ModuleDefinition } from "./types.js";
2
+ /**
3
+ * Modules shipped with Rowork, listed explicitly like the core commands.
4
+ * Rule for adding one: it must be a chore almost every game redoes.
5
+ */
6
+ export declare const coreModules: ModuleDefinition[];
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,12 @@
1
+ import { formatModule } from "./format.js";
2
+ import { leaderstatsModule } from "./leaderstats.js";
3
+ import { lintModule } from "./lint.js";
4
+ import { networkingModule } from "./networking.js";
5
+ import { playerDataModule } from "./player-data.js";
6
+ import { uiModule } from "./ui.js";
7
+ /**
8
+ * Modules shipped with Rowork, listed explicitly like the core commands.
9
+ * Rule for adding one: it must be a chore almost every game redoes.
10
+ */
11
+ export const coreModules = [playerDataModule, leaderstatsModule, networkingModule, lintModule, formatModule, uiModule];
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,8 @@
1
+ import type { IntegrationDefinition } from "./types.js";
2
+ /**
3
+ * Glue between modules. Each entry applies once every module it lists is
4
+ * installed, in whichever order they were added.
5
+ */
6
+ export declare const dataReplication: IntegrationDefinition;
7
+ export declare const coreIntegrations: IntegrationDefinition[];
8
+ //# sourceMappingURL=integrations.d.ts.map
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Glue between modules. Each entry applies once every module it lists is
3
+ * installed, in whichever order they were added.
4
+ */
5
+ export const dataReplication = {
6
+ name: "data-replication",
7
+ title: "player data on the client",
8
+ description: "Sends each player's saved data to their client, for interfaces to read.",
9
+ modules: ["player-data", "networking"],
10
+ agentGuide: [
11
+ "The server sends each player's data to that player's client when it loads and each time it changes (`DataReplicationService`, server). Do not send it by hand.",
12
+ "On the client, inject `PlayerDataController` and use `get()` (undefined until the server has sent it) or `onChanged(cb)`. It is read-only: change data on the server with `PlayerDataService.update`.",
13
+ ],
14
+ plan({ config }) {
15
+ const eventsFile = `${config.paths.shared}/data/dataEvents`;
16
+ const dataFile = `${config.paths.shared}/data/PlayerData`;
17
+ return {
18
+ files: [
19
+ {
20
+ template: "data-replication/dataEvents",
21
+ directory: `${config.paths.shared}/data`,
22
+ fileName: "dataEvents.ts",
23
+ variables: { dataImport: `@import:${dataFile}` },
24
+ },
25
+ {
26
+ template: "data-replication/DataReplicationService",
27
+ directory: config.paths.services,
28
+ fileName: "DataReplicationService.ts",
29
+ variables: {
30
+ eventsImport: `@import:${eventsFile}`,
31
+ rateLimitImport: `@import:${config.paths.source}/server/rateLimit`,
32
+ },
33
+ },
34
+ {
35
+ template: "data-replication/PlayerDataController",
36
+ directory: config.paths.controllers,
37
+ fileName: "PlayerDataController.ts",
38
+ variables: { dataImport: `@import:${dataFile}`, eventsImport: `@import:${eventsFile}` },
39
+ },
40
+ ],
41
+ register: [
42
+ { side: "server", directory: config.paths.services },
43
+ { side: "client", directory: config.paths.controllers },
44
+ ],
45
+ notes: [
46
+ "Player data now reaches the client. Client code reads it with PlayerDataController:",
47
+ " constructor(private readonly playerData: PlayerDataController) {}",
48
+ " this.playerData.onChanged((data) => { /* update the interface */ });",
49
+ ],
50
+ };
51
+ },
52
+ };
53
+ export const coreIntegrations = [dataReplication];
54
+ //# sourceMappingURL=integrations.js.map
@@ -0,0 +1,3 @@
1
+ import type { ModuleDefinition } from "./types.js";
2
+ export declare const leaderstatsModule: ModuleDefinition;
3
+ //# sourceMappingURL=leaderstats.d.ts.map