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,141 @@
1
+ import spawn from "cross-spawn";
2
+ import pc from "picocolors";
3
+ import { pathWithLocalBinaries } from "../core/toolchain.js";
4
+ import { createLineSplitter } from "./log-mux.js";
5
+ import { killTree, killTreeSync } from "./tree-kill.js";
6
+ const RECENT_OUTPUT_LINES = 12;
7
+ /**
8
+ * Runs several long-lived tools side by side under one terminal.
9
+ *
10
+ * Two rules drive the design:
11
+ *
12
+ * - A task dying is a hard stop. A `rowork dev` that keeps a Rojo server alive
13
+ * after the compiler died would serve stale code while looking healthy,
14
+ * which is worse than exiting.
15
+ * - Nothing outlives the supervisor. Every exit path, including Ctrl+C and a
16
+ * crash of the CLI itself, goes through a process-tree kill.
17
+ */
18
+ export class Supervisor {
19
+ options;
20
+ running = [];
21
+ shuttingDown = false;
22
+ exitCode = 0;
23
+ constructor(options) {
24
+ this.options = options;
25
+ }
26
+ async run() {
27
+ const width = Math.max(...this.options.tasks.map((task) => task.name.length));
28
+ this.installSignalHandlers();
29
+ for (const definition of this.options.tasks) {
30
+ this.start(definition, width);
31
+ }
32
+ await Promise.all(this.running.map((task) => this.waitFor(task)));
33
+ return this.exitCode;
34
+ }
35
+ start(definition, width) {
36
+ const prefix = definition.paint(definition.name.padEnd(width));
37
+ const child = spawn(definition.command, definition.args, {
38
+ cwd: this.options.cwd,
39
+ stdio: ["ignore", "pipe", "pipe"],
40
+ // A process group of its own, so the whole tree can be signalled at
41
+ // once. Not on Windows, where it would open a separate console window.
42
+ detached: process.platform !== "win32",
43
+ env: {
44
+ ...process.env,
45
+ // Children write to a pipe, not a TTY, so they would drop their
46
+ // colours. Most respect FORCE_COLOR, which keeps compiler
47
+ // diagnostics readable.
48
+ FORCE_COLOR: "1",
49
+ PATH: pathWithLocalBinaries(this.options.cwd),
50
+ },
51
+ });
52
+ const task = { definition, child, recentOutput: [], exited: false };
53
+ this.running.push(task);
54
+ const emit = (line, isStderr) => {
55
+ task.recentOutput.push(line);
56
+ if (task.recentOutput.length > RECENT_OUTPUT_LINES)
57
+ task.recentOutput.shift();
58
+ const text = `${prefix} ${pc.dim("|")} ${isStderr ? pc.yellow(line) : line}\n`;
59
+ process.stderr.write(text);
60
+ this.options.onOutput?.(text);
61
+ };
62
+ const stdout = createLineSplitter((line) => emit(line, false));
63
+ const stderr = createLineSplitter((line) => emit(line, true));
64
+ child.stdout?.on("data", (chunk) => stdout.push(chunk));
65
+ child.stderr?.on("data", (chunk) => stderr.push(chunk));
66
+ child.on("error", (error) => {
67
+ emit(`could not start \`${definition.command}\`: ${error.message}`, true);
68
+ });
69
+ child.on("close", () => {
70
+ stdout.flush();
71
+ stderr.flush();
72
+ });
73
+ }
74
+ waitFor(task) {
75
+ return new Promise((resolve) => {
76
+ const settle = (code, signal) => {
77
+ if (task.exited)
78
+ return;
79
+ task.exited = true;
80
+ if (!this.shuttingDown) {
81
+ this.reportUnexpectedExit(task, code, signal);
82
+ void this.shutdown(code ?? 1);
83
+ }
84
+ resolve();
85
+ };
86
+ task.child.on("close", settle);
87
+ task.child.on("error", () => settle(1, null));
88
+ });
89
+ }
90
+ /**
91
+ * Surfaces the failure loudly and replays the task's last lines.
92
+ *
93
+ * In a unified log the actual cause has usually scrolled past, buried under
94
+ * output from the tasks that are still healthy. Repeating it at the bottom
95
+ * is the difference between a usable orchestrator and a wall of text.
96
+ */
97
+ reportUnexpectedExit(task, code, signal) {
98
+ const reason = signal !== null ? `signal ${signal}` : `exit code ${String(code)}`;
99
+ const { logger } = this.options;
100
+ logger.blank();
101
+ logger.error(`Task \`${task.definition.name}\` stopped (${reason}). Shutting the others down.`);
102
+ if (task.recentOutput.length > 0) {
103
+ logger.info(pc.dim(` last output from ${task.definition.name}:`));
104
+ for (const line of task.recentOutput)
105
+ logger.info(pc.dim(` ${line}`));
106
+ }
107
+ logger.blank();
108
+ }
109
+ async shutdown(code) {
110
+ if (this.shuttingDown)
111
+ return;
112
+ this.shuttingDown = true;
113
+ this.exitCode = code;
114
+ await Promise.all(this.running.filter((task) => !task.exited).map((task) => killTree(task.child)));
115
+ }
116
+ installSignalHandlers() {
117
+ const onSignal = () => {
118
+ if (this.shuttingDown)
119
+ return;
120
+ this.options.logger.blank();
121
+ this.options.logger.info("Stopping...");
122
+ void this.shutdown(0);
123
+ };
124
+ // SIGHUP is what closing the terminal window sends. Without it the CLI
125
+ // dies on the spot, its "exit" handler never runs, and the tasks (each in
126
+ // a process group of their own) carry on with nobody to stop them,
127
+ // still holding the Rojo port. SIGBREAK is the Windows equivalent of
128
+ // Ctrl+Break and closing the console.
129
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP", "SIGBREAK"]) {
130
+ process.once(signal, onSignal);
131
+ }
132
+ // Last resort: if the CLI itself throws, detached children would survive.
133
+ process.once("exit", () => {
134
+ for (const task of this.running) {
135
+ if (!task.exited)
136
+ killTreeSync(task.child);
137
+ }
138
+ });
139
+ }
140
+ }
141
+ //# sourceMappingURL=supervisor.js.map
@@ -0,0 +1,25 @@
1
+ import { type ChildProcess } from "node:child_process";
2
+ /**
3
+ * Terminates a child process AND every process it spawned.
4
+ *
5
+ * This is the single most important function in the orchestrator, and the
6
+ * classic failure of tools in this category: `child.kill()` only signals the
7
+ * direct child. `rbxtsc` and `rojo` are reached through wrapper shims, so the
8
+ * real worker is a grandchild that survives and keeps holding the output
9
+ * directory or the Rojo port. The user then hits a stale server on the next
10
+ * `rowork dev` with no idea why.
11
+ *
12
+ * Windows has no process groups, so we delegate to `taskkill /T`, which walks
13
+ * the tree itself. Elsewhere the child is spawned detached, which makes it a
14
+ * process-group leader, and a negative PID signals the whole group.
15
+ */
16
+ export declare function killTree(child: ChildProcess, signal?: NodeJS.Signals): Promise<void>;
17
+ /**
18
+ * Best-effort synchronous kill, for the `exit` handler.
19
+ *
20
+ * Node runs `exit` listeners synchronously and ignores anything asynchronous,
21
+ * so the promise-based path above is useless there. Detached children would
22
+ * otherwise outlive a crash of the CLI itself.
23
+ */
24
+ export declare function killTreeSync(child: ChildProcess): void;
25
+ //# sourceMappingURL=tree-kill.d.ts.map
@@ -0,0 +1,70 @@
1
+ import { spawn } from "node:child_process";
2
+ /**
3
+ * Terminates a child process AND every process it spawned.
4
+ *
5
+ * This is the single most important function in the orchestrator, and the
6
+ * classic failure of tools in this category: `child.kill()` only signals the
7
+ * direct child. `rbxtsc` and `rojo` are reached through wrapper shims, so the
8
+ * real worker is a grandchild that survives and keeps holding the output
9
+ * directory or the Rojo port. The user then hits a stale server on the next
10
+ * `rowork dev` with no idea why.
11
+ *
12
+ * Windows has no process groups, so we delegate to `taskkill /T`, which walks
13
+ * the tree itself. Elsewhere the child is spawned detached, which makes it a
14
+ * process-group leader, and a negative PID signals the whole group.
15
+ */
16
+ export function killTree(child, signal = "SIGTERM") {
17
+ const pid = child.pid;
18
+ if (pid === undefined || child.exitCode !== null || child.signalCode !== null) {
19
+ return Promise.resolve();
20
+ }
21
+ if (process.platform === "win32") {
22
+ return new Promise((resolve) => {
23
+ // /T kills the tree, /F forces it. Windows has no graceful equivalent
24
+ // for a non-console child, so there is nothing softer to try first.
25
+ const killer = spawn("taskkill", ["/pid", String(pid), "/T", "/F"], {
26
+ stdio: "ignore",
27
+ });
28
+ killer.on("error", () => resolve());
29
+ killer.on("close", () => resolve());
30
+ });
31
+ }
32
+ try {
33
+ // Negative PID targets the process group, not just the leader.
34
+ process.kill(-pid, signal);
35
+ }
36
+ catch {
37
+ // Already gone, or never became a group leader. Fall back to the child.
38
+ try {
39
+ child.kill(signal);
40
+ }
41
+ catch {
42
+ // Nothing left to kill.
43
+ }
44
+ }
45
+ return Promise.resolve();
46
+ }
47
+ /**
48
+ * Best-effort synchronous kill, for the `exit` handler.
49
+ *
50
+ * Node runs `exit` listeners synchronously and ignores anything asynchronous,
51
+ * so the promise-based path above is useless there. Detached children would
52
+ * otherwise outlive a crash of the CLI itself.
53
+ */
54
+ export function killTreeSync(child) {
55
+ const pid = child.pid;
56
+ if (pid === undefined || child.exitCode !== null)
57
+ return;
58
+ try {
59
+ if (process.platform === "win32") {
60
+ spawn("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore", detached: true });
61
+ }
62
+ else {
63
+ process.kill(-pid, "SIGKILL");
64
+ }
65
+ }
66
+ catch {
67
+ // Exiting anyway.
68
+ }
69
+ }
70
+ //# sourceMappingURL=tree-kill.js.map
@@ -0,0 +1,18 @@
1
+ export type TemplateVariables = Readonly<Record<string, string>>;
2
+ /** Root of the bundled templates, relative to the compiled dist/. */
3
+ export declare function templatesRoot(): string;
4
+ export declare function renderString(source: string, variables: TemplateVariables): string;
5
+ /**
6
+ * Turns a template file name into its final name.
7
+ *
8
+ * - a trailing `.tmpl` is dropped
9
+ * - a leading `_` becomes `.`
10
+ *
11
+ * The `_` convention exists because npm always strips `.gitignore` files from
12
+ * published tarballs: a template literally named `.gitignore` would be missing
13
+ * from the installed package.
14
+ */
15
+ export declare function resolveTemplateName(name: string, variables: TemplateVariables): string;
16
+ /** Recursively copies a template directory, rendering both names and contents. */
17
+ export declare function renderTree(sourceDirectory: string, targetDirectory: string, variables: TemplateVariables): string[];
18
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1,54 @@
1
+ import { mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { RoworkError } from "../cli/errors.js";
5
+ const PLACEHOLDER = /\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g;
6
+ /** Root of the bundled templates, relative to the compiled dist/. */
7
+ export function templatesRoot() {
8
+ return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "templates");
9
+ }
10
+ export function renderString(source, variables) {
11
+ return source.replace(PLACEHOLDER, (match, key) => {
12
+ const value = variables[key];
13
+ if (value === undefined) {
14
+ throw new RoworkError(`Unknown template variable: \`${key}\`.`, {
15
+ hint: `Found in: ${match}`,
16
+ });
17
+ }
18
+ return value;
19
+ });
20
+ }
21
+ /**
22
+ * Turns a template file name into its final name.
23
+ *
24
+ * - a trailing `.tmpl` is dropped
25
+ * - a leading `_` becomes `.`
26
+ *
27
+ * The `_` convention exists because npm always strips `.gitignore` files from
28
+ * published tarballs: a template literally named `.gitignore` would be missing
29
+ * from the installed package.
30
+ */
31
+ export function resolveTemplateName(name, variables) {
32
+ let output = name.endsWith(".tmpl") ? name.slice(0, -".tmpl".length) : name;
33
+ if (output.startsWith("_"))
34
+ output = `.${output.slice(1)}`;
35
+ return renderString(output, variables);
36
+ }
37
+ /** Recursively copies a template directory, rendering both names and contents. */
38
+ export function renderTree(sourceDirectory, targetDirectory, variables) {
39
+ const written = [];
40
+ mkdirSync(targetDirectory, { recursive: true });
41
+ for (const entry of readdirSync(sourceDirectory)) {
42
+ const sourcePath = join(sourceDirectory, entry);
43
+ const targetPath = join(targetDirectory, resolveTemplateName(entry, variables));
44
+ if (statSync(sourcePath).isDirectory()) {
45
+ written.push(...renderTree(sourcePath, targetPath, variables));
46
+ continue;
47
+ }
48
+ mkdirSync(dirname(targetPath), { recursive: true });
49
+ writeFileSync(targetPath, renderString(readFileSync(sourcePath, "utf8"), variables), "utf8");
50
+ written.push(targetPath);
51
+ }
52
+ return written;
53
+ }
54
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1,5 @@
1
+ import type { Logger } from "../plugins/api.js";
2
+ export type LogLevel = "silent" | "error" | "warn" | "info" | "debug";
3
+ export declare function setLogLevel(level: LogLevel): void;
4
+ export declare const logger: Logger;
5
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1,53 @@
1
+ import pc from "picocolors";
2
+ const SEVERITY = {
3
+ silent: 0,
4
+ error: 1,
5
+ warn: 2,
6
+ info: 3,
7
+ debug: 4,
8
+ };
9
+ let currentLevel = "info";
10
+ export function setLogLevel(level) {
11
+ currentLevel = level;
12
+ }
13
+ function allowed(level) {
14
+ return SEVERITY[level] <= SEVERITY[currentLevel];
15
+ }
16
+ /**
17
+ * Diagnostics go to stderr, never to stdout: stdout stays reserved for output
18
+ * meant to be piped (lists, paths, JSON).
19
+ */
20
+ function write(line) {
21
+ process.stderr.write(`${line}\n`);
22
+ }
23
+ export const logger = {
24
+ debug(message) {
25
+ if (allowed("debug"))
26
+ write(`${pc.dim("debug")} ${pc.dim(message)}`);
27
+ },
28
+ info(message) {
29
+ if (allowed("info"))
30
+ write(message);
31
+ },
32
+ success(message) {
33
+ if (allowed("info"))
34
+ write(`${pc.green("done")} ${message}`);
35
+ },
36
+ step(message) {
37
+ if (allowed("info"))
38
+ write(`${pc.dim(" -")} ${message}`);
39
+ },
40
+ warn(message) {
41
+ if (allowed("warn"))
42
+ write(`${pc.yellow("warn")} ${message}`);
43
+ },
44
+ error(message) {
45
+ if (allowed("error"))
46
+ write(`${pc.red("error")} ${message}`);
47
+ },
48
+ blank() {
49
+ if (allowed("info"))
50
+ write("");
51
+ },
52
+ };
53
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1,9 @@
1
+ import * as prompts from "@clack/prompts";
2
+ export { prompts };
3
+ /** True when a person can answer questions: both ends are a terminal. */
4
+ export declare function isInteractive(): boolean;
5
+ /** Fails with the non-interactive form when the command cannot ask its questions. */
6
+ export declare function requireInteractive(command: string, usage: string): void;
7
+ /** Ends a guided flow cleanly on Ctrl+C or Escape instead of throwing a stack trace. */
8
+ export declare function answered<T>(value: T): Exclude<T, symbol>;
9
+ //# sourceMappingURL=prompt.d.ts.map
@@ -0,0 +1,24 @@
1
+ import * as prompts from "@clack/prompts";
2
+ import { RoworkError } from "../cli/errors.js";
3
+ export { prompts };
4
+ /** True when a person can answer questions: both ends are a terminal. */
5
+ export function isInteractive() {
6
+ return process.stdin.isTTY === true && process.stdout.isTTY === true;
7
+ }
8
+ /** Fails with the non-interactive form when the command cannot ask its questions. */
9
+ export function requireInteractive(command, usage) {
10
+ if (!isInteractive()) {
11
+ throw new RoworkError(`\`rowork ${command}\` needs a terminal to ask its questions.`, {
12
+ hint: `Outside a terminal, give everything on the command line: ${usage}`,
13
+ });
14
+ }
15
+ }
16
+ /** Ends a guided flow cleanly on Ctrl+C or Escape instead of throwing a stack trace. */
17
+ export function answered(value) {
18
+ if (prompts.isCancel(value)) {
19
+ prompts.cancel("Cancelled, nothing was created.");
20
+ process.exit(0);
21
+ }
22
+ return value;
23
+ }
24
+ //# sourceMappingURL=prompt.js.map
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "rowork",
3
+ "version": "0.1.0",
4
+ "description": "The meta-framework CLI for Roblox game development. Orchestrates Rojo, roblox-ts and Flamework, and scaffolds your game architecture.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/nokogoat/Rowork.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/nokogoat/Rowork/issues"
13
+ },
14
+ "homepage": "https://github.com/nokogoat/Rowork#readme",
15
+ "bin": {
16
+ "rowork": "bin/rowork.js"
17
+ },
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": "./dist/index.js",
22
+ "./plugin": "./dist/plugins/api.js"
23
+ },
24
+ "files": [
25
+ "bin",
26
+ "dist",
27
+ "dashboard",
28
+ "templates",
29
+ "README.md",
30
+ "LICENSE",
31
+ "!dist/**/*.map"
32
+ ],
33
+ "engines": {
34
+ "node": ">=20"
35
+ },
36
+ "scripts": {
37
+ "build": "tsc -p tsconfig.json",
38
+ "watch": "tsc -p tsconfig.json --watch",
39
+ "test:smoke": "node scripts/smoke-test.mjs",
40
+ "test": "npm run build && npm run test:smoke && npm run test:orphan && npm run test:assets && npm run test:integration",
41
+ "prepublishOnly": "npm run test",
42
+ "rowork": "node bin/rowork.js",
43
+ "test:orphan": "node scripts/dev-orphan-test.mjs",
44
+ "test:assets": "node scripts/assets-test.mjs",
45
+ "test:integration": "node scripts/integration-test.mjs"
46
+ },
47
+ "keywords": [
48
+ "roblox",
49
+ "roblox-ts",
50
+ "flamework",
51
+ "rojo",
52
+ "cli",
53
+ "framework",
54
+ "scaffolding",
55
+ "gamedev"
56
+ ],
57
+ "dependencies": {
58
+ "@clack/prompts": "^1.8.1",
59
+ "commander": "^12.1.0",
60
+ "cross-spawn": "^7.0.6",
61
+ "fflate": "^0.8.3",
62
+ "picocolors": "^1.1.1"
63
+ },
64
+ "devDependencies": {
65
+ "@types/cross-spawn": "^6.0.6",
66
+ "@types/node": "^22.10.2",
67
+ "typescript": "^5.7.2"
68
+ }
69
+ }
@@ -0,0 +1,37 @@
1
+ # {{ name }}
2
+
3
+ Roblox project generated by [Rowork](https://github.com/nokogoat/Rowork) v{{ roworkVersion }}.
4
+
5
+ ## Stack
6
+
7
+ - **roblox-ts**: TypeScript compiled to Luau
8
+ - **Flamework**: services, controllers, dependency injection
9
+ - **Rojo**: file syncing into Roblox Studio
10
+ - **Rokit**: pinned tool versions
11
+
12
+ ## Getting started
13
+
14
+ `rowork init` already installed the npm dependencies and the pinned toolchain,
15
+ so there is nothing left to set up.
16
+
17
+ ```bash
18
+ rowork dev # compiler, Rojo server and sourcemap watcher, together
19
+ ```
20
+
21
+ ## Layout
22
+
23
+ ```
24
+ src/
25
+ server/services/ Flamework services (server-side logic)
26
+ client/controllers/ Flamework controllers (client-side logic)
27
+ shared/ code shared between server and client
28
+ ```
29
+
30
+ `runtime.server.ts` and `runtime.client.ts` are the entry points: they declare
31
+ which directories to scan, then call `Flamework.ignite()`.
32
+
33
+ ## Rowork configuration
34
+
35
+ `rowork.json` describes the project paths and the active plugins. `tsconfig.json`,
36
+ `default.project.json` and `package.json` remain standard files you can edit
37
+ directly: Rowork never hides them.
@@ -0,0 +1,23 @@
1
+ node_modules/
2
+ out/
3
+ # roblox-ts runtime and Flamework build cache: both are regenerated by the compiler.
4
+ include/
5
+ flamework.build
6
+ *.tsbuildinfo
7
+
8
+ # Rojo / build artefacts
9
+ *.rbxl
10
+ *.rbxlx
11
+ *.rbxl.lock
12
+ *.rbxlx.lock
13
+ sourcemap.json
14
+
15
+ # Background `rowork dev`: its pid and log.
16
+ .rowork/run/
17
+
18
+ # Secrets: never commit an Open Cloud key or a Roblox cookie.
19
+ .env
20
+ .env.*
21
+
22
+ .DS_Store
23
+ Thumbs.db
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "{{ name }}",
3
+ "globIgnorePaths": ["**/package.json", "**/tsconfig.json"],
4
+ "tree": {
5
+ "$className": "DataModel",
6
+
7
+ "ServerScriptService": {
8
+ "TS": {
9
+ "$path": "out/server"
10
+ }
11
+ },
12
+
13
+ "ReplicatedStorage": {
14
+ "rbxts_include": {
15
+ "$path": "include",
16
+ "node_modules": {
17
+ "$className": "Folder",
18
+ "@flamework": {
19
+ "$path": "node_modules/@flamework"
20
+ },
21
+ "@rbxts": {
22
+ "$path": "node_modules/@rbxts"
23
+ }
24
+ }
25
+ },
26
+ "TS": {
27
+ "$path": "out/shared"
28
+ }
29
+ },
30
+
31
+ "StarterPlayer": {
32
+ "StarterPlayerScripts": {
33
+ "TS": {
34
+ "$path": "out/client"
35
+ }
36
+ }
37
+ },
38
+
39
+ "Workspace": {
40
+ "$properties": {
41
+ "FilteringEnabled": true
42
+ }
43
+ },
44
+
45
+ "SoundService": {
46
+ "$properties": {
47
+ "RespectFilteringEnabled": true
48
+ }
49
+ },
50
+
51
+ "HttpService": {
52
+ "$properties": {
53
+ "HttpEnabled": true
54
+ }
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "{{ packageName }}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "rowork dev",
7
+ "build": "rbxtsc",
8
+ "watch": "rbxtsc -w"
9
+ },
10
+ "dependencies": {},
11
+ "devDependencies": {}
12
+ }
@@ -0,0 +1,5 @@
1
+ # Pinned toolchain, managed by Rokit: https://github.com/rojo-rbx/rokit
2
+ # `rokit install` gives every contributor these exact versions.
3
+ # Move to a newer Rojo with `rowork update`.
4
+ [tools]
5
+ rojo = "rojo-rbx/rojo@{{ rojoVersion }}"
@@ -0,0 +1,12 @@
1
+ import { Controller, OnStart } from "@flamework/core";
2
+
3
+ /**
4
+ * A controller is the client-side counterpart of a service.
5
+ * Generate more with `rowork make:controller <Name>`.
6
+ */
7
+ @Controller()
8
+ export class ExampleController implements OnStart {
9
+ onStart(): void {
10
+ print("[{{ name }}] ExampleController started.");
11
+ }
12
+ }
@@ -0,0 +1,6 @@
1
+ import { Flamework } from "@flamework/core";
2
+
3
+ /** Client entry point. See runtime.server.ts for how addPaths works. */
4
+ Flamework.addPaths("src/client/controllers");
5
+
6
+ Flamework.ignite();
@@ -0,0 +1,12 @@
1
+ import { Flamework } from "@flamework/core";
2
+
3
+ /**
4
+ * Server entry point.
5
+ *
6
+ * `addPaths` takes paths relative to the project root. They are resolved at
7
+ * compile time by the Flamework transformer, which relies on the Rojo project:
8
+ * any directory added here must exist and be exposed in default.project.json.
9
+ */
10
+ Flamework.addPaths("src/server/services");
11
+
12
+ Flamework.ignite();
@@ -0,0 +1,12 @@
1
+ import { OnStart, Service } from "@flamework/core";
2
+
3
+ /**
4
+ * A service is a server-side singleton, instantiated and injected by Flamework.
5
+ * Generate more with `rowork make:service <Name>`.
6
+ */
7
+ @Service()
8
+ export class ExampleService implements OnStart {
9
+ onStart(): void {
10
+ print("[{{ name }}] ExampleService started.");
11
+ }
12
+ }
File without changes