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,181 @@
1
+ import { join } from "node:path";
2
+ import { RoworkError } from "../cli/errors.js";
3
+ import { resolveProjectPath } from "../core/config.js";
4
+ import { ensureFlameworkPath, generateFile, toClassBase, withSuffix } from "../core/generate.js";
5
+ import { defineCommand, } from "../plugins/api.js";
6
+ import { listStats } from "../core/project-index.js";
7
+ import { answered, prompts, requireInteractive } from "../ui/prompt.js";
8
+ import { askStatLink, resolveStats, serviceMembers } from "./links.js";
9
+ const NAME_ARGUMENT = {
10
+ name: "name",
11
+ description: "name of the class, e.g. Inventory (omit it for the guided version)",
12
+ required: false,
13
+ };
14
+ const FORCE_OPTION = { flags: "-f, --force", description: "overwrite the file if it exists" };
15
+ export function requireProject(context, command) {
16
+ if (context.projectRoot === undefined || context.config === undefined) {
17
+ throw new RoworkError(`\`rowork ${command}\` must run inside a Rowork project.`, {
18
+ hint: "No usable rowork.json found here or in any parent directory. Create a project with `rowork start`.",
19
+ });
20
+ }
21
+ return { root: context.projectRoot, config: context.config };
22
+ }
23
+ /** Validates a name typed in a prompt, using the same rules as the command line. */
24
+ export function validateName(value) {
25
+ try {
26
+ toClassBase((value ?? "").trim());
27
+ return undefined;
28
+ }
29
+ catch (error) {
30
+ return error instanceof RoworkError ? error.message : String(error);
31
+ }
32
+ }
33
+ /**
34
+ * The name from the command line, or, in a terminal with none given, from a
35
+ * question. `undefined` guided means the caller should also ask its other
36
+ * questions.
37
+ */
38
+ export async function nameOrAsk(context, command, message, placeholder) {
39
+ const given = context.args["name"];
40
+ if (typeof given === "string")
41
+ return { name: given, guided: false };
42
+ requireInteractive(command, `rowork ${command} <name>`);
43
+ const name = answered(await prompts.text({ message, placeholder, validate: validateName })).trim();
44
+ return { name, guided: true };
45
+ }
46
+ /**
47
+ * Generates one Flamework class, then makes sure the matching runtime entry
48
+ * point scans its directory so the class is actually picked up.
49
+ */
50
+ function generate(options) {
51
+ const { context } = options;
52
+ const { root, config } = requireProject(context, options.command);
53
+ const base = toClassBase(options.name);
54
+ const className = withSuffix(base, options.suffix);
55
+ const directory = options.directory(config);
56
+ const written = generateFile({
57
+ projectRoot: root,
58
+ directory,
59
+ fileName: `${className}.ts`,
60
+ template: options.template,
61
+ variables: { className, ...options.extraVariables?.(base) },
62
+ force: context.options["force"] === true,
63
+ });
64
+ context.logger.success(`Created ${options.kind} ${className} (${written ?? directory})`);
65
+ const runtime = resolveProjectPath(root, join(config.paths.source, options.side, `runtime.${options.side}.ts`));
66
+ if (ensureFlameworkPath(runtime, directory, context.logger)) {
67
+ context.logger.step(`registered ${directory} in runtime.${options.side}.ts`);
68
+ }
69
+ }
70
+ export const makeServiceCommand = defineCommand({
71
+ name: "make:service",
72
+ guided: true,
73
+ description: "Create a service file (server logic) in the right place and register it with Flamework.",
74
+ arguments: [NAME_ARGUMENT],
75
+ options: [
76
+ { flags: "--uses <values>", description: "saved values it uses, comma separated (e.g. kills,coins): injected for you" },
77
+ FORCE_OPTION,
78
+ ],
79
+ async run(context) {
80
+ const { root, config } = requireProject(context, "make:service");
81
+ const { name, guided } = await nameOrAsk(context, "make:service", "What is the service called?", "Inventory");
82
+ // "Link it to...?": the saved values this service works with, injected for you.
83
+ const typed = typeof context.options["uses"] === "string"
84
+ ? context.options["uses"]
85
+ : guided
86
+ ? await askStatLink(root, config, "Does it use saved values? Type their names, separated by commas.", false)
87
+ : undefined;
88
+ const linked = typed === undefined ? [] : resolveStats(listStats(root, config), typed, "try again");
89
+ const injected = serviceMembers(root, config, linked);
90
+ generate({
91
+ context,
92
+ command: "make:service",
93
+ name,
94
+ kind: "service",
95
+ suffix: "Service",
96
+ template: "service",
97
+ side: "server",
98
+ directory: (cfg) => cfg.paths.services,
99
+ extraVariables: () => ({ imports: injected.imports, members: injected.members }),
100
+ });
101
+ if (linked.length > 0)
102
+ context.logger.step(`linked to ${linked.map((stat) => stat.name).join(", ")}: injected in the constructor`);
103
+ },
104
+ });
105
+ export const makeControllerCommand = defineCommand({
106
+ name: "make:controller",
107
+ guided: true,
108
+ description: "Create a controller file (client logic) in the right place and register it with Flamework.",
109
+ arguments: [NAME_ARGUMENT],
110
+ options: [FORCE_OPTION],
111
+ async run(context) {
112
+ requireProject(context, "make:controller");
113
+ const { name } = await nameOrAsk(context, "make:controller", "What is the controller called?", "Camera");
114
+ generate({
115
+ context,
116
+ command: "make:controller",
117
+ name,
118
+ kind: "controller",
119
+ suffix: "Controller",
120
+ template: "controller",
121
+ side: "client",
122
+ directory: (config) => config.paths.controllers,
123
+ });
124
+ },
125
+ });
126
+ export const makeComponentCommand = defineCommand({
127
+ name: "make:component",
128
+ guided: true,
129
+ description: "Create a component file (behaviour for tagged objects) in the right place and register it with Flamework.",
130
+ arguments: [NAME_ARGUMENT],
131
+ options: [
132
+ { flags: "--side <side>", description: "server (default) or client" },
133
+ { flags: "--tag <tag>", description: "CollectionService tag (default: the name)" },
134
+ FORCE_OPTION,
135
+ ],
136
+ async run(context) {
137
+ requireProject(context, "make:component");
138
+ const { name, guided } = await nameOrAsk(context, "make:component", "What is the component called?", "Door");
139
+ let side = context.options["side"] ?? "server";
140
+ let tag = context.options["tag"];
141
+ if (guided) {
142
+ side = answered(await prompts.select({
143
+ message: "Where does it run?",
144
+ options: [
145
+ { value: "server", label: "Server", hint: "game rules, data, anything players must not cheat" },
146
+ { value: "client", label: "Client", hint: "visuals, input, UI" },
147
+ ],
148
+ initialValue: "server",
149
+ }));
150
+ tag = answered(await prompts.text({
151
+ message: "Which tag makes it attach to an instance?",
152
+ placeholder: toClassBase(name),
153
+ defaultValue: toClassBase(name),
154
+ validate: (value) => /^[A-Za-z0-9_.-]*$/.test(value ?? "") ? undefined : "Letters, digits, _ - . only.",
155
+ }));
156
+ }
157
+ if (side !== "server" && side !== "client") {
158
+ throw new RoworkError(`Unknown side \`${String(side)}\`.`, {
159
+ hint: "Use --side server or --side client.",
160
+ });
161
+ }
162
+ if (typeof tag === "string" && !/^[A-Za-z0-9_.-]+$/.test(tag)) {
163
+ throw new RoworkError(`\`${tag}\` is not a valid tag.`, {
164
+ hint: "Use letters, digits, `_`, `-` or `.`.",
165
+ });
166
+ }
167
+ const chosenSide = side;
168
+ generate({
169
+ context,
170
+ command: "make:component",
171
+ name,
172
+ kind: "component",
173
+ suffix: "Component",
174
+ template: "component",
175
+ side: chosenSide,
176
+ directory: (config) => `${config.paths.source}/${chosenSide}/components`,
177
+ extraVariables: (base) => ({ tag: typeof tag === "string" && tag !== "" ? tag : base }),
178
+ });
179
+ },
180
+ });
181
+ //# sourceMappingURL=make.js.map
@@ -0,0 +1,5 @@
1
+ import type { ScaffoldResult } from "../core/scaffold.js";
2
+ import type { Logger } from "../plugins/api.js";
3
+ /** Same closing message for `init` and `start`, so they can never drift apart. */
4
+ export declare function printNextSteps(logger: Logger, directory: string, result: ScaffoldResult): void;
5
+ //# sourceMappingURL=next-steps.d.ts.map
@@ -0,0 +1,18 @@
1
+ import pc from "picocolors";
2
+ /** Same closing message for `init` and `start`, so they can never drift apart. */
3
+ export function printNextSteps(logger, directory, result) {
4
+ logger.blank();
5
+ logger.success(`${result.displayName} is ready.`);
6
+ logger.blank();
7
+ logger.info("Next steps:");
8
+ logger.info(` cd ${directory}`);
9
+ if (!result.installed)
10
+ logger.info(" npm install");
11
+ if (result.lintPending)
12
+ logger.info(" rowork add lint, rowork add format (they need npm: run them after npm install)");
13
+ if (!result.toolchainReady)
14
+ logger.info(" rokit install");
15
+ logger.info(` ${pc.bold("rowork dev")}`);
16
+ logger.blank();
17
+ }
18
+ //# sourceMappingURL=next-steps.js.map
@@ -0,0 +1,2 @@
1
+ export declare const startCommand: import("../plugins/api.js").CommandDefinition;
2
+ //# sourceMappingURL=start.d.ts.map
@@ -0,0 +1,144 @@
1
+ import { resolve } from "node:path";
2
+ import pc from "picocolors";
3
+ import { RoworkError } from "../cli/errors.js";
4
+ import { findExecutable } from "../core/toolchain.js";
5
+ import { assertValidProjectName, toPascalCase } from "../core/naming.js";
6
+ import { resolveTarget, scaffoldProject } from "../core/scaffold.js";
7
+ import { isVinegarInstalled, needsStudioSetup, setupStudio } from "../core/studio.js";
8
+ import { installModulesByName } from "../core/modules.js";
9
+ import { coreModules } from "../modules/index.js";
10
+ import { defineCommand } from "../plugins/api.js";
11
+ import { answered, isInteractive, prompts } from "../ui/prompt.js";
12
+ import { printNextSteps } from "./next-steps.js";
13
+ export const startCommand = defineCommand({
14
+ name: "start",
15
+ guided: true,
16
+ description: "Guided setup: answer a few questions and get a ready-to-run project.",
17
+ options: [{ flags: "--path <dir>", description: "parent directory to create the project in" }],
18
+ async run(context) {
19
+ if (!isInteractive()) {
20
+ throw new RoworkError("`rowork start` is interactive and needs a terminal.", {
21
+ hint: "In scripts and CI use `rowork init <name>` with its flags instead.",
22
+ });
23
+ }
24
+ prompts.intro(pc.bgCyan(pc.black(" rowork ")) + pc.dim(` v${context.roworkVersion}`));
25
+ const name = answered(await prompts.text({
26
+ message: "What is your game called?",
27
+ placeholder: "MyGame",
28
+ validate(value) {
29
+ try {
30
+ assertValidProjectName((value ?? "").trim());
31
+ }
32
+ catch (error) {
33
+ return error instanceof RoworkError ? error.message : String(error);
34
+ }
35
+ return undefined;
36
+ },
37
+ })).trim();
38
+ const parent = resolve(context.cwd, typeof context.options["path"] === "string"
39
+ ? context.options["path"]
40
+ : answered(await prompts.text({
41
+ message: "Where should it be created?",
42
+ initialValue: ".",
43
+ defaultValue: ".",
44
+ })));
45
+ // Fail on an existing directory now, not after five more questions.
46
+ try {
47
+ resolveTarget({ name, parent, force: false });
48
+ }
49
+ catch (error) {
50
+ prompts.cancel(error instanceof Error ? error.message : String(error));
51
+ process.exit(1);
52
+ }
53
+ const examples = answered(await prompts.confirm({
54
+ message: "Include an example service and controller?",
55
+ initialValue: true,
56
+ }));
57
+ const git = answered(await prompts.confirm({ message: "Initialise a git repository?", initialValue: true }));
58
+ const install = answered(await prompts.confirm({
59
+ message: "Install npm dependencies now? (roblox-ts, Flamework)",
60
+ initialValue: true,
61
+ }));
62
+ // Ready-made features, chosen up front. They install npm packages, so they need npm.
63
+ let features = [];
64
+ if (install) {
65
+ features = answered(await prompts.multiselect({
66
+ message: "Which ready-made features do you want from the start? (space to tick, enter to confirm. You can add more later with `rowork add`)",
67
+ // The linter and the formatter are not offered: they are always included.
68
+ options: coreModules
69
+ .filter((module) => module.name !== "lint" && module.name !== "format")
70
+ .map((module) => ({ value: module.name, label: module.title, hint: module.description })),
71
+ required: false,
72
+ }));
73
+ }
74
+ const rokitFound = findExecutable("rokit", context.cwd) !== undefined;
75
+ let rokit = true;
76
+ let installRokit = false;
77
+ if (rokitFound) {
78
+ rokit = answered(await prompts.confirm({
79
+ message: "Install the pinned Roblox toolchain with Rokit? (Rojo)",
80
+ initialValue: true,
81
+ }));
82
+ }
83
+ else {
84
+ installRokit = answered(await prompts.confirm({
85
+ message: "Rokit (the Roblox toolchain manager, provides Rojo) is not installed. Download and install it for you? (from github.com/rojo-rbx/rokit into ~/.rokit)",
86
+ initialValue: true,
87
+ }));
88
+ rokit = installRokit;
89
+ }
90
+ // Studio has no Linux build: offer Vinegar, which runs the real one.
91
+ const studio = needsStudioSetup() && !isVinegarInstalled()
92
+ ? answered(await prompts.confirm({
93
+ message: "Roblox Studio has no Linux version. Install Vinegar, which runs it through Wine? (Flatpak, current user only)",
94
+ initialValue: true,
95
+ }))
96
+ : false;
97
+ prompts.note([
98
+ `Name ${toPascalCase(name)}`,
99
+ `Location ${resolve(parent, name)}`,
100
+ `Examples ${examples ? "yes" : "no"}`,
101
+ `Git ${git ? "yes" : "no"}`,
102
+ `npm ${install ? "install" : "skip"}`,
103
+ `Quality ${install ? "linter (ESLint) and formatter (Prettier) included" : "skipped (needs npm)"}`,
104
+ `Features ${features.length > 0 ? features.join(", ") : install ? "none" : "skipped (needs npm)"}`,
105
+ ...(studio ? ["Studio install Vinegar"] : []),
106
+ `Rokit ${installRokit ? "install Rokit, then the toolchain" : rokit ? "install the toolchain" : "skip"}`,
107
+ ].join("\n"), "Summary");
108
+ if (!answered(await prompts.confirm({ message: "Create it?", initialValue: true }))) {
109
+ prompts.cancel("Cancelled, nothing was created.");
110
+ return;
111
+ }
112
+ prompts.log.step("Scaffolding");
113
+ const result = await scaffoldProject({
114
+ name,
115
+ parent,
116
+ install,
117
+ lint: true,
118
+ format: true,
119
+ rokit,
120
+ installRokit,
121
+ git,
122
+ examples,
123
+ force: false,
124
+ roworkVersion: context.roworkVersion,
125
+ }, context.logger);
126
+ if (features.length > 0 && result.installed) {
127
+ prompts.log.step("Adding features");
128
+ await installModulesByName(context, result.target, features);
129
+ }
130
+ if (studio) {
131
+ try {
132
+ await setupStudio({ logger: context.logger, projectRoot: result.target, plugin: true });
133
+ }
134
+ catch (error) {
135
+ // The project is already created: a Studio problem must not undo that.
136
+ context.logger.warn(`Studio setup did not finish: ${error instanceof Error ? error.message : String(error)}`);
137
+ context.logger.info("Retry with `rowork studio:setup`.");
138
+ }
139
+ }
140
+ printNextSteps(context.logger, name, result);
141
+ prompts.outro("Happy building.");
142
+ },
143
+ });
144
+ //# sourceMappingURL=start.js.map
@@ -0,0 +1,3 @@
1
+ export declare const studioCommand: import("../plugins/api.js").CommandDefinition;
2
+ export declare const studioSetupCommand: import("../plugins/api.js").CommandDefinition;
3
+ //# sourceMappingURL=studio.d.ts.map
@@ -0,0 +1,45 @@
1
+ import { RoworkError } from "../cli/errors.js";
2
+ import { isVinegarInstalled, launchStudio, needsStudioSetup, setupStudio, } from "../core/studio.js";
3
+ import { defineCommand } from "../plugins/api.js";
4
+ function requireLinux(command) {
5
+ if (!needsStudioSetup()) {
6
+ throw new RoworkError(`\`rowork ${command}\` is only needed on Linux.`, {
7
+ hint: "Roblox Studio runs natively on Windows and macOS: install it from roblox.com/create.",
8
+ });
9
+ }
10
+ }
11
+ export const studioCommand = defineCommand({
12
+ name: "studio",
13
+ description: "Launch Roblox Studio on Linux (through Vinegar).",
14
+ run(context) {
15
+ requireLinux("studio");
16
+ if (!isVinegarInstalled()) {
17
+ throw new RoworkError("Vinegar (Roblox Studio for Linux) is not installed.", {
18
+ hint: "Run `rowork studio:setup` first.",
19
+ });
20
+ }
21
+ launchStudio();
22
+ context.logger.success("Launching Roblox Studio through Vinegar.");
23
+ context.logger.info("The first launch downloads Studio, and you sign in to Roblox in the browser.");
24
+ },
25
+ });
26
+ export const studioSetupCommand = defineCommand({
27
+ name: "studio:setup",
28
+ description: "Install Roblox Studio for Linux (Vinegar) and the Rojo plugin.",
29
+ options: [{ flags: "--no-plugin", description: "skip installing the Rojo plugin" }],
30
+ async run(context) {
31
+ requireLinux("studio:setup");
32
+ const result = await setupStudio({
33
+ logger: context.logger,
34
+ projectRoot: context.projectRoot,
35
+ plugin: context.options["plugin"] !== false,
36
+ });
37
+ context.logger.blank();
38
+ context.logger.success("Vinegar is ready.");
39
+ context.logger.info("Next: `rowork studio` to launch it, then `rowork dev` in your project.");
40
+ if (!result.pluginInstalled && context.options["plugin"] !== false) {
41
+ context.logger.info("The Rojo plugin will be installed by re-running `rowork studio:setup` after the first launch.");
42
+ }
43
+ },
44
+ });
45
+ //# sourceMappingURL=studio.js.map
@@ -0,0 +1,2 @@
1
+ export declare const updateCommand: import("../plugins/api.js").CommandDefinition;
2
+ //# sourceMappingURL=update.d.ts.map
@@ -0,0 +1,173 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import pc from "picocolors";
4
+ import { RoworkError } from "../cli/errors.js";
5
+ import { run as runBinary } from "../core/exec.js";
6
+ import { pinnedTypescriptVersion } from "../core/scaffold.js";
7
+ import { findStoreRojoPlugin, findStudioDataDirectories, installRojoPlugin, isVinegarInstalled, needsStudioSetup, warnAboutStorePlugin, } from "../core/studio.js";
8
+ import { findExecutable } from "../core/toolchain.js";
9
+ import { baseVersion, latestNpmVersion, latestRojoVersion, readRojoPin, writeRojoPin, } from "../core/versions.js";
10
+ import { defineCommand } from "../plugins/api.js";
11
+ import { answered, isInteractive, prompts } from "../ui/prompt.js";
12
+ import { requireProject } from "./make.js";
13
+ export const updateCommand = defineCommand({
14
+ name: "update",
15
+ description: "Move the project to the latest Rojo, npm packages and Studio plugin.",
16
+ guided: true,
17
+ options: [
18
+ { flags: "--dry-run", description: "show what would change and change nothing" },
19
+ { flags: "--yes", description: "do not ask for confirmation" },
20
+ { flags: "--no-npm", description: "leave the npm packages alone" },
21
+ { flags: "--no-build", description: "do not compile afterwards to check the result" },
22
+ ],
23
+ async run(context) {
24
+ const { root } = requireProject(context, "update");
25
+ const { logger } = context;
26
+ // ---- what is behind
27
+ const rojoNow = readRojoPin(root);
28
+ const rojoLatest = await latestRojoVersion();
29
+ const rojoBehind = rojoNow !== undefined && rojoLatest !== undefined && rojoNow !== rojoLatest;
30
+ const npmChanges = [];
31
+ if (context.options["npm"] !== false) {
32
+ const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
33
+ const groups = [
34
+ [manifest.dependencies, false],
35
+ [manifest.devDependencies, true],
36
+ ];
37
+ for (const [packages, dev] of groups) {
38
+ for (const [name, range] of Object.entries(packages ?? {})) {
39
+ // roblox-ts pins one exact TypeScript and patches it: it is set from roblox-ts below.
40
+ if (name === "typescript")
41
+ continue;
42
+ const latest = latestNpmVersion(name);
43
+ if (latest !== undefined && baseVersion(range) !== latest) {
44
+ npmChanges.push({ name, from: baseVersion(range), to: latest, dev });
45
+ }
46
+ }
47
+ }
48
+ }
49
+ if (rojoLatest === undefined)
50
+ logger.warn("Could not look up the latest Rojo (is GitHub reachable?): skipping it.");
51
+ if (!rojoBehind && npmChanges.length === 0) {
52
+ logger.success("Everything is already up to date.");
53
+ return;
54
+ }
55
+ logger.info(pc.bold("Updates available:"));
56
+ if (rojoBehind)
57
+ logger.step(`Rojo ${pc.dim(rojoNow ?? "")} -> ${pc.green(rojoLatest ?? "")} (rokit.toml, and the Studio plugin)`);
58
+ for (const change of npmChanges)
59
+ logger.step(`${change.name} ${pc.dim(change.from)} -> ${pc.green(change.to)}`);
60
+ logger.blank();
61
+ if (context.options["dryRun"] === true) {
62
+ logger.info("Dry run: nothing was changed.");
63
+ return;
64
+ }
65
+ if (context.options["yes"] !== true) {
66
+ if (!isInteractive()) {
67
+ throw new RoworkError("`rowork update` asks for confirmation.", {
68
+ hint: "Outside a terminal, add --yes (and --dry-run first to preview).",
69
+ });
70
+ }
71
+ const confirmed = answered(await prompts.confirm({
72
+ message: "Apply these updates? (commit or back up your project first: `git diff` shows what changed)",
73
+ initialValue: true,
74
+ }));
75
+ if (!confirmed) {
76
+ prompts.cancel("Nothing changed.");
77
+ return;
78
+ }
79
+ }
80
+ // ---- Rojo
81
+ // All or nothing. A pin that names a Rojo that is not installed breaks
82
+ // `rowork dev` (the Rokit shim has nothing to run), so the pin only stays
83
+ // changed if the install worked, and the Studio plugin, which must match
84
+ // the server, is only touched then too.
85
+ let rojoMoved = false;
86
+ if (rojoBehind && rojoLatest !== undefined && rojoNow !== undefined) {
87
+ writeRojoPin(root, rojoLatest);
88
+ const rokit = findExecutable("rokit", root);
89
+ if (rokit === undefined) {
90
+ // No Rokit means no shim to break: keep the pin, the user installs later.
91
+ rojoMoved = true;
92
+ logger.step(`rokit.toml now pins Rojo ${rojoLatest}`);
93
+ logger.warn("Rokit was not found: install it, then run `rokit install` to get the new Rojo.");
94
+ }
95
+ else {
96
+ try {
97
+ await runBinary(rokit, ["trust", "rojo-rbx/rojo"], { cwd: root, stdio: "ignore" });
98
+ await runBinary(rokit, ["install"], { cwd: root });
99
+ rojoMoved = true;
100
+ logger.step(`Rojo ${rojoLatest} installed and pinned in rokit.toml`);
101
+ }
102
+ catch {
103
+ writeRojoPin(root, rojoNow);
104
+ logger.warn(`Could not install Rojo ${rojoLatest} (usually a network problem): kept ${rojoNow}, so your project still works.`);
105
+ logger.info("Run `rowork update` again later. The Studio plugin was left as it is, to keep matching your Rojo.");
106
+ process.exitCode = 1;
107
+ }
108
+ }
109
+ }
110
+ // ---- npm packages
111
+ if (npmChanges.length > 0) {
112
+ for (const dev of [false, true]) {
113
+ const names = npmChanges.filter((change) => change.dev === dev).map((change) => `${change.name}@latest`);
114
+ if (names.length === 0)
115
+ continue;
116
+ logger.step(`installing ${names.length} ${dev ? "dev " : ""}package${names.length > 1 ? "s" : ""} (npm)`);
117
+ await runBinary("npm", ["install", ...(dev ? ["--save-dev"] : []), ...names], { cwd: root });
118
+ }
119
+ // roblox-ts may now want a different TypeScript.
120
+ const typescript = pinnedTypescriptVersion(root);
121
+ if (typescript !== undefined) {
122
+ await runBinary("npm", ["install", "--save-dev", `typescript@${typescript}`], { cwd: root });
123
+ }
124
+ }
125
+ // ---- Studio plugin: it must match the Rojo server
126
+ if (rojoMoved) {
127
+ if (needsStudioSetup()) {
128
+ const directories = findStudioDataDirectories();
129
+ if (isVinegarInstalled() && directories.length > 0) {
130
+ try {
131
+ await installRojoPlugin(directories, root, logger);
132
+ logger.info("Restart Studio to load the new plugin.");
133
+ if (findStoreRojoPlugin() !== undefined)
134
+ warnAboutStorePlugin(logger);
135
+ }
136
+ catch (error) {
137
+ logger.warn(`Could not update the Rojo plugin: ${error instanceof Error ? error.message : String(error)}. Run \`rowork studio:setup\`.`);
138
+ }
139
+ }
140
+ else {
141
+ logger.warn("Studio's Rojo plugin was not updated (Studio has not been launched yet). Run `rowork studio:setup` after launching it.");
142
+ }
143
+ }
144
+ else if (findExecutable("rojo", root) !== undefined) {
145
+ try {
146
+ await runBinary("rojo", ["plugin", "install"], { cwd: root });
147
+ logger.info("Restart Studio to load the new plugin.");
148
+ }
149
+ catch {
150
+ logger.warn("`rojo plugin install` did not finish. Run it yourself, then restart Studio.");
151
+ }
152
+ }
153
+ }
154
+ // ---- did it break anything?
155
+ if (context.options["build"] !== false && npmChanges.length > 0) {
156
+ logger.step("compiling to check the updated packages");
157
+ try {
158
+ await runBinary("npm", ["run", "build"], { cwd: root });
159
+ logger.success("Updated, and the project still compiles.");
160
+ }
161
+ catch {
162
+ logger.blank();
163
+ logger.warn("The project does not compile with the updated packages.");
164
+ logger.info("A newer package changed something. `git diff package.json` shows what moved; `git checkout package.json package-lock.json && npm install` goes back.");
165
+ process.exitCode = 1;
166
+ }
167
+ }
168
+ else {
169
+ logger.success("Updated.");
170
+ }
171
+ },
172
+ });
173
+ //# sourceMappingURL=update.js.map
@@ -0,0 +1,2 @@
1
+ export declare const wireCommand: import("../plugins/api.js").CommandDefinition;
2
+ //# sourceMappingURL=wire.d.ts.map
@@ -0,0 +1,34 @@
1
+ import { applyPendingIntegrations, pendingIntegrations } from "../core/modules.js";
2
+ import { syncAgentDocs } from "../core/agent-docs.js";
3
+ import { loadConfig } from "../core/config.js";
4
+ import { defineCommand } from "../plugins/api.js";
5
+ import { requireProject } from "./make.js";
6
+ export const wireCommand = defineCommand({
7
+ name: "wire",
8
+ description: "Connect the installed modules that work together (done automatically by `rowork add`).",
9
+ options: [{ flags: "--dry-run", description: "list what would be wired and change nothing" }],
10
+ async run(context) {
11
+ const { root, config } = requireProject(context, "wire");
12
+ const pending = pendingIntegrations(config);
13
+ if (pending.length === 0) {
14
+ context.logger.info("Everything that can be wired together already is.");
15
+ return;
16
+ }
17
+ if (context.options["dryRun"] === true) {
18
+ for (const integration of pending) {
19
+ context.logger.step(`${integration.title}: ${integration.description} (${integration.modules.join(" + ")})`);
20
+ }
21
+ context.logger.info("Dry run: nothing was changed.");
22
+ return;
23
+ }
24
+ const applied = await applyPendingIntegrations(context, root);
25
+ if (applied.length > 0) {
26
+ syncAgentDocs(root, loadConfig(root), context.roworkVersion);
27
+ context.logger.success(`Wired: ${applied.join(", ")}.`);
28
+ }
29
+ else {
30
+ process.exitCode = 1;
31
+ }
32
+ },
33
+ });
34
+ //# sourceMappingURL=wire.js.map
@@ -0,0 +1,23 @@
1
+ import type { RoworkConfig } from "../plugins/api.js";
2
+ export declare const AGENTS_FILE = "AGENTS.md";
3
+ export declare const CLAUDE_FILE = "CLAUDE.md";
4
+ /**
5
+ * The block Rowork owns in AGENTS.md.
6
+ *
7
+ * Built from live data (the registered commands, the installed modules) so it
8
+ * cannot drift from what the project really offers. It is written for two
9
+ * readers at once: a person learning the project, and an AI about to change it.
10
+ */
11
+ export declare function renderManagedSection(config: RoworkConfig, roworkVersion: string): string;
12
+ /**
13
+ * Creates or refreshes AGENTS.md (and a CLAUDE.md that points to it).
14
+ *
15
+ * Only the block between the markers is rewritten: anything the user wrote
16
+ * around it stays. An existing CLAUDE.md is never touched. Claude Code reads
17
+ * CLAUDE.md and most other agents read AGENTS.md, so one file holds the content
18
+ * and the other imports it.
19
+ */
20
+ export declare function syncAgentDocs(projectRoot: string, config: RoworkConfig, roworkVersion: string): string[];
21
+ /** Removes the Rowork block when leaving Rowork; deletes files that held nothing else. */
22
+ export declare function removeAgentDocs(projectRoot: string): string[];
23
+ //# sourceMappingURL=agent-docs.d.ts.map