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.
- package/LICENSE +21 -0
- package/README.md +95 -0
- package/bin/rowork.js +18 -0
- package/dashboard/app.css +234 -0
- package/dashboard/app.js +117 -0
- package/dashboard/index.html +67 -0
- package/dist/cli/errors.d.ts +15 -0
- package/dist/cli/errors.js +18 -0
- package/dist/cli/program.d.ts +19 -0
- package/dist/cli/program.js +59 -0
- package/dist/cli/registry.d.ts +37 -0
- package/dist/cli/registry.js +61 -0
- package/dist/commands/add.d.ts +8 -0
- package/dist/commands/add.js +77 -0
- package/dist/commands/assets-setup.d.ts +18 -0
- package/dist/commands/assets-setup.js +136 -0
- package/dist/commands/assets.d.ts +2 -0
- package/dist/commands/assets.js +153 -0
- package/dist/commands/console.d.ts +4 -0
- package/dist/commands/console.js +141 -0
- package/dist/commands/dashboard.d.ts +2 -0
- package/dist/commands/dashboard.js +50 -0
- package/dist/commands/dev-background.d.ts +3 -0
- package/dist/commands/dev-background.js +87 -0
- package/dist/commands/dev.d.ts +2 -0
- package/dist/commands/dev.js +270 -0
- package/dist/commands/eject.d.ts +2 -0
- package/dist/commands/eject.js +136 -0
- package/dist/commands/index.d.ts +8 -0
- package/dist/commands/index.js +54 -0
- package/dist/commands/info.d.ts +3 -0
- package/dist/commands/info.js +50 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +55 -0
- package/dist/commands/links.d.ts +32 -0
- package/dist/commands/links.js +152 -0
- package/dist/commands/make-event.d.ts +2 -0
- package/dist/commands/make-event.js +160 -0
- package/dist/commands/make-menu.d.ts +3 -0
- package/dist/commands/make-menu.js +36 -0
- package/dist/commands/make-stat.d.ts +2 -0
- package/dist/commands/make-stat.js +199 -0
- package/dist/commands/make-tool.d.ts +2 -0
- package/dist/commands/make-tool.js +124 -0
- package/dist/commands/make-ui.d.ts +3 -0
- package/dist/commands/make-ui.js +199 -0
- package/dist/commands/make.d.ts +21 -0
- package/dist/commands/make.js +181 -0
- package/dist/commands/next-steps.d.ts +5 -0
- package/dist/commands/next-steps.js +18 -0
- package/dist/commands/start.d.ts +2 -0
- package/dist/commands/start.js +144 -0
- package/dist/commands/studio.d.ts +3 -0
- package/dist/commands/studio.js +45 -0
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.js +173 -0
- package/dist/commands/wire.d.ts +2 -0
- package/dist/commands/wire.js +34 -0
- package/dist/core/agent-docs.d.ts +23 -0
- package/dist/core/agent-docs.js +151 -0
- package/dist/core/assets.d.ts +96 -0
- package/dist/core/assets.js +268 -0
- package/dist/core/background.d.ts +47 -0
- package/dist/core/background.js +149 -0
- package/dist/core/config.d.ts +9 -0
- package/dist/core/config.js +62 -0
- package/dist/core/exec.d.ts +22 -0
- package/dist/core/exec.js +38 -0
- package/dist/core/format-generated.d.ts +12 -0
- package/dist/core/format-generated.js +28 -0
- package/dist/core/generate.d.ts +37 -0
- package/dist/core/generate.js +94 -0
- package/dist/core/github-release.d.ts +28 -0
- package/dist/core/github-release.js +38 -0
- package/dist/core/info.d.ts +77 -0
- package/dist/core/info.js +65 -0
- package/dist/core/modules.d.ts +32 -0
- package/dist/core/modules.js +265 -0
- package/dist/core/naming.d.ts +6 -0
- package/dist/core/naming.js +26 -0
- package/dist/core/open-cloud.d.ts +58 -0
- package/dist/core/open-cloud.js +170 -0
- package/dist/core/project-index.d.ts +26 -0
- package/dist/core/project-index.js +59 -0
- package/dist/core/rojo-edit.d.ts +18 -0
- package/dist/core/rojo-edit.js +53 -0
- package/dist/core/rokit-installer.d.ts +15 -0
- package/dist/core/rokit-installer.js +77 -0
- package/dist/core/scaffold.d.ts +44 -0
- package/dist/core/scaffold.js +163 -0
- package/dist/core/schema-edit.d.ts +46 -0
- package/dist/core/schema-edit.js +125 -0
- package/dist/core/source-scan.d.ts +24 -0
- package/dist/core/source-scan.js +84 -0
- package/dist/core/studio.d.ts +83 -0
- package/dist/core/studio.js +225 -0
- package/dist/core/toolchain.d.ts +31 -0
- package/dist/core/toolchain.js +98 -0
- package/dist/core/tsconfig-edit.d.ts +11 -0
- package/dist/core/tsconfig-edit.js +47 -0
- package/dist/core/ui-edit.d.ts +31 -0
- package/dist/core/ui-edit.js +89 -0
- package/dist/core/versions.d.ts +24 -0
- package/dist/core/versions.js +67 -0
- package/dist/dashboard/open.d.ts +3 -0
- package/dist/dashboard/open.js +18 -0
- package/dist/dashboard/server.d.ts +28 -0
- package/dist/dashboard/server.js +191 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/main.d.ts +9 -0
- package/dist/main.js +135 -0
- package/dist/modules/format.d.ts +3 -0
- package/dist/modules/format.js +28 -0
- package/dist/modules/index.d.ts +7 -0
- package/dist/modules/index.js +12 -0
- package/dist/modules/integrations.d.ts +8 -0
- package/dist/modules/integrations.js +54 -0
- package/dist/modules/leaderstats.d.ts +3 -0
- package/dist/modules/leaderstats.js +104 -0
- package/dist/modules/lint.d.ts +5 -0
- package/dist/modules/lint.js +43 -0
- package/dist/modules/networking.d.ts +23 -0
- package/dist/modules/networking.js +246 -0
- package/dist/modules/player-data.d.ts +19 -0
- package/dist/modules/player-data.js +176 -0
- package/dist/modules/types.d.ts +96 -0
- package/dist/modules/types.js +2 -0
- package/dist/modules/ui.d.ts +3 -0
- package/dist/modules/ui.js +67 -0
- package/dist/plugins/api.d.ts +127 -0
- package/dist/plugins/api.js +21 -0
- package/dist/plugins/loader.d.ts +20 -0
- package/dist/plugins/loader.js +128 -0
- package/dist/process/log-mux.d.ts +13 -0
- package/dist/process/log-mux.js +31 -0
- package/dist/process/supervisor.d.ts +48 -0
- package/dist/process/supervisor.js +141 -0
- package/dist/process/tree-kill.d.ts +25 -0
- package/dist/process/tree-kill.js +70 -0
- package/dist/templates/engine.d.ts +18 -0
- package/dist/templates/engine.js +54 -0
- package/dist/ui/logger.d.ts +5 -0
- package/dist/ui/logger.js +53 -0
- package/dist/ui/prompt.d.ts +9 -0
- package/dist/ui/prompt.js +24 -0
- package/package.json +69 -0
- package/templates/init/README.md.tmpl +37 -0
- package/templates/init/_gitignore.tmpl +23 -0
- package/templates/init/default.project.json.tmpl +57 -0
- package/templates/init/package.json.tmpl +12 -0
- package/templates/init/rokit.toml.tmpl +5 -0
- package/templates/init/src/client/controllers/ExampleController.ts.tmpl +12 -0
- package/templates/init/src/client/runtime.client.ts.tmpl +6 -0
- package/templates/init/src/server/runtime.server.ts.tmpl +12 -0
- package/templates/init/src/server/services/ExampleService.ts.tmpl +12 -0
- package/templates/init/src/shared/_gitkeep.tmpl +0 -0
- package/templates/init/tsconfig.json.tmpl +28 -0
- package/templates/integrations/data-replication/DataReplicationService.ts.tmpl +33 -0
- package/templates/integrations/data-replication/PlayerDataController.ts.tmpl +40 -0
- package/templates/integrations/data-replication/dataEvents.ts.tmpl +21 -0
- package/templates/make/component.ts.tmpl +8 -0
- package/templates/make/controller.ts.tmpl +6 -0
- package/templates/make/event-handler.ts.tmpl +22 -0
- package/templates/make/screen.tsx.tmpl +35 -0
- package/templates/make/service.ts.tmpl +6 -0
- package/templates/make/stat-service.ts.tmpl +26 -0
- package/templates/make/ui-button.tsx.tmpl +22 -0
- package/templates/make/ui-image.tsx.tmpl +11 -0
- package/templates/make/ui-label.tsx.tmpl +18 -0
- package/templates/make/ui-panel.tsx.tmpl +33 -0
- package/templates/make/ui-story.tsx.tmpl +13 -0
- package/templates/modules/format/prettierignore.tmpl +9 -0
- package/templates/modules/format/prettierrc.json.tmpl +4 -0
- package/templates/modules/leaderstats/LeaderstatsService.ts.tmpl +69 -0
- package/templates/modules/lint/eslint.config.mjs.tmpl +17 -0
- package/templates/modules/networking/client-network.ts.tmpl +9 -0
- package/templates/modules/networking/networking.ts.tmpl +22 -0
- package/templates/modules/networking/rate-limit.ts.tmpl +52 -0
- package/templates/modules/networking/server-network.ts.tmpl +20 -0
- package/templates/modules/player-data/PlayerData.ts.tmpl +15 -0
- package/templates/modules/player-data/PlayerDataService.ts.tmpl +127 -0
- package/templates/modules/ui/App.tsx.tmpl +19 -0
- package/templates/modules/ui/Button.story.tsx.tmpl +13 -0
- package/templates/modules/ui/Button.tsx.tmpl +21 -0
- package/templates/modules/ui/UiController.tsx.tmpl +21 -0
- package/templates/modules/ui/screens/HomeScreen.tsx.tmpl +20 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { CommandOption, Logger, RoworkConfig } from "../plugins/api.js";
|
|
2
|
+
/** One file a module writes into the project. */
|
|
3
|
+
export interface ModuleFile {
|
|
4
|
+
/** Template under `templates/modules/`, without the `.ts.tmpl` suffix. */
|
|
5
|
+
template: string;
|
|
6
|
+
/** Destination directory, relative to the project root. */
|
|
7
|
+
directory: string;
|
|
8
|
+
fileName: string;
|
|
9
|
+
variables: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
/** Everything a module decided to do, once its options are known. */
|
|
12
|
+
export interface ModulePlan {
|
|
13
|
+
files: ModuleFile[];
|
|
14
|
+
/** Flamework directories the module's classes live in, to be registered. */
|
|
15
|
+
register: {
|
|
16
|
+
side: "server" | "client";
|
|
17
|
+
directory: string;
|
|
18
|
+
}[];
|
|
19
|
+
/** tsconfig.json `compilerOptions` to set (JSX, for the UI module). See `core/tsconfig-edit.ts`. */
|
|
20
|
+
compilerOptions?: Record<string, string>;
|
|
21
|
+
/** npm scopes to map into the game in default.project.json (`@rbxts-js` for React). See `core/rojo-edit.ts`. */
|
|
22
|
+
nodeModuleScopes?: string[];
|
|
23
|
+
/** npm scripts to add to package.json. One that already exists is never overwritten. */
|
|
24
|
+
scripts?: Record<string, string>;
|
|
25
|
+
/** Short lines printed after installation: what was added, what to do next. */
|
|
26
|
+
notes: string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* What two or more modules generate together once all of them are installed:
|
|
30
|
+
* the glue that would otherwise be written by hand. It applies whichever module
|
|
31
|
+
* was added last, so the order of installation never changes the result.
|
|
32
|
+
*/
|
|
33
|
+
export interface IntegrationDefinition {
|
|
34
|
+
/** Unique, kebab-case. Recorded in rowork.json once applied. */
|
|
35
|
+
name: string;
|
|
36
|
+
title: string;
|
|
37
|
+
description: string;
|
|
38
|
+
/** Every one of these modules must be installed. */
|
|
39
|
+
modules: string[];
|
|
40
|
+
/** How to use what it adds, for the project's AGENTS.md. */
|
|
41
|
+
agentGuide?: string[];
|
|
42
|
+
plan(input: {
|
|
43
|
+
config: RoworkConfig;
|
|
44
|
+
projectRoot: string;
|
|
45
|
+
}): ModulePlan;
|
|
46
|
+
}
|
|
47
|
+
export interface PlanInput {
|
|
48
|
+
/** True when the user ran the command with no options: ask the questions. */
|
|
49
|
+
guided: boolean;
|
|
50
|
+
options: Readonly<Record<string, unknown>>;
|
|
51
|
+
config: RoworkConfig;
|
|
52
|
+
/** Root of the project, for modules that read what is already there. */
|
|
53
|
+
projectRoot: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A module: a small, readable set of files copied into the user's project.
|
|
57
|
+
*
|
|
58
|
+
* The user owns those files afterwards and is expected to read and edit them,
|
|
59
|
+
* which is why a module never installs an opaque runtime of its own.
|
|
60
|
+
*/
|
|
61
|
+
export interface ModuleDefinition {
|
|
62
|
+
/** Kebab-case identifier, also the command suffix: `add:<name>`. */
|
|
63
|
+
name: string;
|
|
64
|
+
/** Human name shown in menus. */
|
|
65
|
+
title: string;
|
|
66
|
+
description: string;
|
|
67
|
+
/** Other modules that must be installed first. */
|
|
68
|
+
requires?: string[];
|
|
69
|
+
/** npm packages to install. Versions are resolved by npm, never hardcoded. */
|
|
70
|
+
dependencies?: string[];
|
|
71
|
+
/**
|
|
72
|
+
* How to use the module once installed, written for whoever reads the
|
|
73
|
+
* project's AGENTS.md next: a person learning it, or an AI working in it.
|
|
74
|
+
* Short imperative lines: where the code is, what to call, what to edit.
|
|
75
|
+
*/
|
|
76
|
+
agentGuide?: string[];
|
|
77
|
+
/**
|
|
78
|
+
* Whether running it with no option asks questions. Defaults to "it has options".
|
|
79
|
+
* A module whose options are only switches (`--no-plugin`) sets this to false so
|
|
80
|
+
* it still runs without a terminal.
|
|
81
|
+
*/
|
|
82
|
+
asksQuestions?: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Work that is not files or npm packages, run after everything else. Its failure
|
|
85
|
+
* never undoes the install: it is reported and the user can finish by hand.
|
|
86
|
+
*/
|
|
87
|
+
postInstall?(input: {
|
|
88
|
+
logger: Logger;
|
|
89
|
+
projectRoot: string;
|
|
90
|
+
options: Readonly<Record<string, unknown>>;
|
|
91
|
+
}): Promise<void>;
|
|
92
|
+
/** Flags for the scripted form. */
|
|
93
|
+
options?: CommandOption[];
|
|
94
|
+
plan(input: PlanInput): Promise<ModulePlan> | ModulePlan;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { RoworkError } from "../cli/errors.js";
|
|
2
|
+
import { findStudioDataDirectories, installReleasePlugin, needsStudioSetup, UI_LABS } from "../core/studio.js";
|
|
3
|
+
export const uiModule = {
|
|
4
|
+
name: "ui",
|
|
5
|
+
title: "User interface (React)",
|
|
6
|
+
description: "Build your interface with React, and preview each component in Studio without running the game.",
|
|
7
|
+
dependencies: ["@rbxts/react", "@rbxts/react-roblox", "@rbxts/ui-labs"],
|
|
8
|
+
asksQuestions: false,
|
|
9
|
+
options: [{ flags: "--no-plugin", description: "do not install the UI Labs preview plugin in Studio" }],
|
|
10
|
+
agentGuide: [
|
|
11
|
+
"The interface is React (`@rbxts/react`), in `src/client/ui/`. One component per file: a function that returns JSX. Lowercase tags (`frame`, `textbutton`, `textlabel`...) are Roblox instances; your own components start with a capital. Use hooks (`useState`, `useEffect`); do not create or change Instances by hand inside a component.",
|
|
12
|
+
"Roblox events in JSX use `Event={{ Activated: () => ... }}`; properties are the Roblox property names (`Size`, `Position`, `BackgroundColor3`...).",
|
|
13
|
+
"The interface is made of screens and elements. A **screen** is a component that returns a `screengui` (`src/client/ui/screens/<Name>Screen.tsx`); `App` lists them between the `{/* rowork:screens */}` marker, and `UiController` draws `App` into the player's PlayerGui. An **element** is a reusable component (`src/client/ui/<Name>.tsx`) with a `.story.tsx` next to it that shows it alone in Studio through the UI Labs plugin.",
|
|
14
|
+
"Create them with `rowork make:screen <name>` and `rowork make:ui <name> --kind button|label|panel|image --in <Screen>`: they put the file in the right place and add it to `App` or to the screen for you. Do not edit `App.tsx` by hand to add a screen, and keep the `{/* rowork:screens */}` and `{/* rowork:elements */}` markers: they are how Rowork knows where to add.",
|
|
15
|
+
"The interface reads data from the server: with the `player-data` and `networking` modules, use `PlayerDataController` (`get()`, `onChanged`) rather than asking again. Never trust the client for game rules: a button sends an event, the server decides.",
|
|
16
|
+
],
|
|
17
|
+
plan({ config }) {
|
|
18
|
+
const ui = `${config.paths.source}/client/ui`;
|
|
19
|
+
return {
|
|
20
|
+
files: [
|
|
21
|
+
{ template: "ui/App.tsx", directory: ui, fileName: "App.tsx", variables: {} },
|
|
22
|
+
{ template: "ui/screens/HomeScreen.tsx", directory: `${ui}/screens`, fileName: "HomeScreen.tsx", variables: {} },
|
|
23
|
+
{ template: "ui/Button.tsx", directory: ui, fileName: "Button.tsx", variables: {} },
|
|
24
|
+
{ template: "ui/Button.story.tsx", directory: ui, fileName: "Button.story.tsx", variables: {} },
|
|
25
|
+
{
|
|
26
|
+
template: "ui/UiController.tsx",
|
|
27
|
+
directory: config.paths.controllers,
|
|
28
|
+
fileName: "UiController.tsx",
|
|
29
|
+
variables: { appImport: `@import:${ui}/App` },
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
register: [{ side: "client", directory: config.paths.controllers }],
|
|
33
|
+
// roblox-ts turns JSX into calls to this factory: it must be React's.
|
|
34
|
+
compilerOptions: { jsx: "react", jsxFactory: "React.createElement", jsxFragmentFactory: "React.Fragment" },
|
|
35
|
+
// React is built on the @rbxts-js packages: if the game does not contain them, the interface never starts.
|
|
36
|
+
nodeModuleScopes: ["@rbxts-js"],
|
|
37
|
+
notes: [
|
|
38
|
+
`Your interface is a list of screens: ${ui}/App.tsx shows them, and UiController puts them on the player's screen. Try it: Play in Studio.`,
|
|
39
|
+
"Add a screen with `rowork make:screen`, and buttons, text, panels or images with `rowork make:ui`. To see one element without running the game, open the UI Labs plugin in Studio: it lists every *.story.tsx.",
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
async postInstall({ logger, options }) {
|
|
44
|
+
if (options["plugin"] === false)
|
|
45
|
+
return;
|
|
46
|
+
if (!needsStudioSetup()) {
|
|
47
|
+
logger.info(`Install the UI Labs plugin in Studio to preview components: ${UI_LABS.store}`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const directories = findStudioDataDirectories();
|
|
51
|
+
if (directories.length === 0) {
|
|
52
|
+
logger.warn("Studio has not been launched yet, so the UI Labs plugin was not installed.");
|
|
53
|
+
logger.info("Run `rowork studio`, sign in, close Studio, then `rowork studio:setup`: it places the plugin.");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
await installReleasePlugin(directories, UI_LABS, logger);
|
|
58
|
+
logger.info("Restart Studio to load the UI Labs plugin.");
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
throw new RoworkError(error instanceof Error ? error.message : String(error), {
|
|
62
|
+
hint: `Install it from the Creator Store instead: ${UI_LABS.store}`,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=ui.js.map
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PUBLIC PLUGIN API CONTRACT.
|
|
3
|
+
*
|
|
4
|
+
* Everything exported here is exposed to third-party plugins through
|
|
5
|
+
* `rowork/plugin`.
|
|
6
|
+
*
|
|
7
|
+
* Non-negotiable rule (see CLAUDE.md): any breaking change to this file bumps
|
|
8
|
+
* ROWORK_PLUGIN_API_VERSION. Plugins declaring a different version are skipped
|
|
9
|
+
* with a warning, never loaded anyway.
|
|
10
|
+
*/
|
|
11
|
+
/** Plugin contract version. Bump on every breaking change. */
|
|
12
|
+
export declare const ROWORK_PLUGIN_API_VERSION = 1;
|
|
13
|
+
export interface Logger {
|
|
14
|
+
debug(message: string): void;
|
|
15
|
+
info(message: string): void;
|
|
16
|
+
success(message: string): void;
|
|
17
|
+
warn(message: string): void;
|
|
18
|
+
error(message: string): void;
|
|
19
|
+
/** Indented progress line, for the steps of a running command. */
|
|
20
|
+
step(message: string): void;
|
|
21
|
+
blank(): void;
|
|
22
|
+
}
|
|
23
|
+
export interface CommandArgument {
|
|
24
|
+
/** Name used as the key in `CommandContext.args`. */
|
|
25
|
+
name: string;
|
|
26
|
+
description: string;
|
|
27
|
+
/** Defaults to true. */
|
|
28
|
+
required?: boolean;
|
|
29
|
+
/** Collects every remaining argument into an array. */
|
|
30
|
+
variadic?: boolean;
|
|
31
|
+
defaultValue?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface CommandOption {
|
|
34
|
+
/** Commander syntax, e.g. `-f, --force` or `--path <dir>`. */
|
|
35
|
+
flags: string;
|
|
36
|
+
description: string;
|
|
37
|
+
defaultValue?: string | boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface CommandContext {
|
|
40
|
+
/** Positional arguments, keyed by `CommandArgument.name`. */
|
|
41
|
+
readonly args: Readonly<Record<string, string | string[] | undefined>>;
|
|
42
|
+
readonly options: Readonly<Record<string, unknown>>;
|
|
43
|
+
/** Effective working directory, honouring `--cwd`. */
|
|
44
|
+
readonly cwd: string;
|
|
45
|
+
/** Root of the Rowork project (the directory holding rowork.json), if any. */
|
|
46
|
+
readonly projectRoot: string | undefined;
|
|
47
|
+
readonly config: RoworkConfig | undefined;
|
|
48
|
+
readonly logger: Logger;
|
|
49
|
+
readonly roworkVersion: string;
|
|
50
|
+
}
|
|
51
|
+
export interface CommandDefinition {
|
|
52
|
+
/** Invocation name, e.g. `init` or `make:service`. */
|
|
53
|
+
name: string;
|
|
54
|
+
description: string;
|
|
55
|
+
aliases?: string[];
|
|
56
|
+
arguments?: CommandArgument[];
|
|
57
|
+
options?: CommandOption[];
|
|
58
|
+
/**
|
|
59
|
+
* True when running the command without its arguments opens interactive
|
|
60
|
+
* questions. Listed by `rowork info --json` so a script or an AI knows to
|
|
61
|
+
* pass every argument instead of waiting on a prompt that never comes.
|
|
62
|
+
*/
|
|
63
|
+
guided?: boolean;
|
|
64
|
+
run(context: CommandContext): Promise<void> | void;
|
|
65
|
+
}
|
|
66
|
+
export interface PluginContext {
|
|
67
|
+
registerCommand(definition: CommandDefinition): void;
|
|
68
|
+
readonly logger: Logger;
|
|
69
|
+
readonly cwd: string;
|
|
70
|
+
readonly projectRoot: string | undefined;
|
|
71
|
+
readonly config: RoworkConfig | undefined;
|
|
72
|
+
readonly roworkVersion: string;
|
|
73
|
+
}
|
|
74
|
+
export interface RoworkPlugin {
|
|
75
|
+
/** Name shown in logs and command conflict warnings. */
|
|
76
|
+
name: string;
|
|
77
|
+
/** Must equal ROWORK_PLUGIN_API_VERSION. */
|
|
78
|
+
apiVersion: number;
|
|
79
|
+
/** Statically declared commands. */
|
|
80
|
+
commands?: CommandDefinition[];
|
|
81
|
+
/** Dynamic registration and initialisation. */
|
|
82
|
+
setup?(context: PluginContext): Promise<void> | void;
|
|
83
|
+
}
|
|
84
|
+
export interface RoworkConfig {
|
|
85
|
+
$schema?: string;
|
|
86
|
+
/** Game name. */
|
|
87
|
+
name: string;
|
|
88
|
+
/** Contract version this project expects. */
|
|
89
|
+
roworkApiVersion: number;
|
|
90
|
+
/** Only `roblox-ts` is supported in v1 (see CLAUDE.md, v1 scope). */
|
|
91
|
+
language: "roblox-ts";
|
|
92
|
+
paths: {
|
|
93
|
+
/** TypeScript sources. */
|
|
94
|
+
source: string;
|
|
95
|
+
/** roblox-ts compiler output. */
|
|
96
|
+
out: string;
|
|
97
|
+
/** Rojo project file. */
|
|
98
|
+
rojoProject: string;
|
|
99
|
+
/** Flamework services directory (target of make:service). */
|
|
100
|
+
services: string;
|
|
101
|
+
/** Flamework controllers directory (target of make:controller). */
|
|
102
|
+
controllers: string;
|
|
103
|
+
/** Shared code directory. */
|
|
104
|
+
shared: string;
|
|
105
|
+
};
|
|
106
|
+
/** Plugin module specifiers to load explicitly. */
|
|
107
|
+
plugins: string[];
|
|
108
|
+
/** Names of the modules installed with `rowork add`. */
|
|
109
|
+
modules?: string[];
|
|
110
|
+
/** Names of the integrations between modules already applied. */
|
|
111
|
+
integrations?: string[];
|
|
112
|
+
/** Settings of `rowork assets`, the upload of files to Roblox. */
|
|
113
|
+
assets?: {
|
|
114
|
+
/** Folder holding the files to upload, relative to the project. Defaults to `assets`. */
|
|
115
|
+
folder?: string;
|
|
116
|
+
/** Who owns the uploaded assets. */
|
|
117
|
+
creator?: {
|
|
118
|
+
type: "user" | "group";
|
|
119
|
+
id: string;
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/** Type-inference helper for command authors. */
|
|
124
|
+
export declare function defineCommand(definition: CommandDefinition): CommandDefinition;
|
|
125
|
+
/** Type-inference helper for plugin authors. */
|
|
126
|
+
export declare function definePlugin(plugin: RoworkPlugin): RoworkPlugin;
|
|
127
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PUBLIC PLUGIN API CONTRACT.
|
|
3
|
+
*
|
|
4
|
+
* Everything exported here is exposed to third-party plugins through
|
|
5
|
+
* `rowork/plugin`.
|
|
6
|
+
*
|
|
7
|
+
* Non-negotiable rule (see CLAUDE.md): any breaking change to this file bumps
|
|
8
|
+
* ROWORK_PLUGIN_API_VERSION. Plugins declaring a different version are skipped
|
|
9
|
+
* with a warning, never loaded anyway.
|
|
10
|
+
*/
|
|
11
|
+
/** Plugin contract version. Bump on every breaking change. */
|
|
12
|
+
export const ROWORK_PLUGIN_API_VERSION = 1;
|
|
13
|
+
/** Type-inference helper for command authors. */
|
|
14
|
+
export function defineCommand(definition) {
|
|
15
|
+
return definition;
|
|
16
|
+
}
|
|
17
|
+
/** Type-inference helper for plugin authors. */
|
|
18
|
+
export function definePlugin(plugin) {
|
|
19
|
+
return plugin;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CommandRegistry } from "../cli/registry.js";
|
|
2
|
+
import { type RoworkConfig } from "./api.js";
|
|
3
|
+
export interface LoadPluginsOptions {
|
|
4
|
+
registry: CommandRegistry;
|
|
5
|
+
projectRoot: string | undefined;
|
|
6
|
+
config: RoworkConfig | undefined;
|
|
7
|
+
cwd: string;
|
|
8
|
+
roworkVersion: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Loads plugins from three sources, in order:
|
|
12
|
+
* 1. `plugins` declared in rowork.json (explicit, deterministic)
|
|
13
|
+
* 2. project dependencies whose name matches `rowork-plugin-*`
|
|
14
|
+
* 3. files in `.rowork/commands/`
|
|
15
|
+
*
|
|
16
|
+
* Invariant: a failing plugin must NEVER stop the CLI from running. Every load
|
|
17
|
+
* is isolated, and an error degrades to a warning.
|
|
18
|
+
*/
|
|
19
|
+
export declare function loadPlugins(options: LoadPluginsOptions): Promise<void>;
|
|
20
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { logger } from "../ui/logger.js";
|
|
6
|
+
import { ROWORK_PLUGIN_API_VERSION, } from "./api.js";
|
|
7
|
+
/**
|
|
8
|
+
* Directory for one-off, project-specific commands that do not warrant an npm
|
|
9
|
+
* package.
|
|
10
|
+
*
|
|
11
|
+
* Prefer the .mjs extension: a roblox-ts package.json is not
|
|
12
|
+
* `"type": "module"`, so Node emits a MODULE_TYPELESS_PACKAGE_JSON warning for
|
|
13
|
+
* every .js file loaded from here.
|
|
14
|
+
*/
|
|
15
|
+
const LOCAL_COMMANDS_DIR = join(".rowork", "commands");
|
|
16
|
+
const PLUGIN_NAME_PATTERN = /^(@[^/]+\/)?rowork-plugin-/;
|
|
17
|
+
/**
|
|
18
|
+
* Loads plugins from three sources, in order:
|
|
19
|
+
* 1. `plugins` declared in rowork.json (explicit, deterministic)
|
|
20
|
+
* 2. project dependencies whose name matches `rowork-plugin-*`
|
|
21
|
+
* 3. files in `.rowork/commands/`
|
|
22
|
+
*
|
|
23
|
+
* Invariant: a failing plugin must NEVER stop the CLI from running. Every load
|
|
24
|
+
* is isolated, and an error degrades to a warning.
|
|
25
|
+
*/
|
|
26
|
+
export async function loadPlugins(options) {
|
|
27
|
+
const { projectRoot } = options;
|
|
28
|
+
if (projectRoot === undefined)
|
|
29
|
+
return;
|
|
30
|
+
const specifiers = new Set(options.config?.plugins ?? []);
|
|
31
|
+
for (const name of autoDetectedPlugins(projectRoot))
|
|
32
|
+
specifiers.add(name);
|
|
33
|
+
for (const specifier of specifiers) {
|
|
34
|
+
await loadPluginModule(specifier, projectRoot, options);
|
|
35
|
+
}
|
|
36
|
+
await loadLocalCommands(projectRoot, options.registry);
|
|
37
|
+
}
|
|
38
|
+
/** Picks up `rowork-plugin-*` packages from the project manifest. */
|
|
39
|
+
function autoDetectedPlugins(projectRoot) {
|
|
40
|
+
const manifestPath = join(projectRoot, "package.json");
|
|
41
|
+
if (!existsSync(manifestPath))
|
|
42
|
+
return [];
|
|
43
|
+
try {
|
|
44
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
45
|
+
return [
|
|
46
|
+
...Object.keys(manifest.dependencies ?? {}),
|
|
47
|
+
...Object.keys(manifest.devDependencies ?? {}),
|
|
48
|
+
].filter((name) => PLUGIN_NAME_PATTERN.test(name));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
logger.debug(`Unreadable manifest in ${projectRoot}, skipping plugin auto-detection.`);
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function loadPluginModule(specifier, projectRoot, options) {
|
|
56
|
+
try {
|
|
57
|
+
// Resolve from the user project, not from the node_modules of Rowork
|
|
58
|
+
// itself: the CLI may well be installed globally.
|
|
59
|
+
const requireFromProject = createRequire(join(projectRoot, "package.json"));
|
|
60
|
+
const resolved = requireFromProject.resolve(specifier);
|
|
61
|
+
const module = (await import(pathToFileURL(resolved).href));
|
|
62
|
+
const plugin = (module.default ?? module.plugin);
|
|
63
|
+
if (plugin === undefined || typeof plugin !== "object") {
|
|
64
|
+
logger.warn(`Skipping plugin \`${specifier}\`: no valid default export.`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (plugin.apiVersion !== ROWORK_PLUGIN_API_VERSION) {
|
|
68
|
+
logger.warn(`Skipping plugin \`${plugin.name || specifier}\`: targets API v${String(plugin.apiVersion)}, ` +
|
|
69
|
+
`this CLI provides v${ROWORK_PLUGIN_API_VERSION}.`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const origin = { kind: "plugin", pluginName: plugin.name || specifier };
|
|
73
|
+
for (const definition of plugin.commands ?? []) {
|
|
74
|
+
options.registry.register(definition, origin);
|
|
75
|
+
}
|
|
76
|
+
if (typeof plugin.setup === "function") {
|
|
77
|
+
const context = {
|
|
78
|
+
registerCommand: (definition) => options.registry.register(definition, origin),
|
|
79
|
+
logger,
|
|
80
|
+
cwd: options.cwd,
|
|
81
|
+
projectRoot: options.projectRoot,
|
|
82
|
+
config: options.config,
|
|
83
|
+
roworkVersion: options.roworkVersion,
|
|
84
|
+
};
|
|
85
|
+
await plugin.setup(context);
|
|
86
|
+
}
|
|
87
|
+
logger.debug(`Loaded plugin: ${plugin.name || specifier}`);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
logger.warn(`Could not load plugin \`${specifier}\`: ${error instanceof Error ? error.message : String(error)}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Loads ad hoc commands from `.rowork/commands/`. */
|
|
94
|
+
async function loadLocalCommands(projectRoot, registry) {
|
|
95
|
+
const directory = join(projectRoot, LOCAL_COMMANDS_DIR);
|
|
96
|
+
if (!existsSync(directory))
|
|
97
|
+
return;
|
|
98
|
+
let entries;
|
|
99
|
+
try {
|
|
100
|
+
entries = readdirSync(directory).filter((file) => /\.(?:js|mjs)$/.test(file));
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
logger.warn(`Unreadable ${LOCAL_COMMANDS_DIR} directory: ${error instanceof Error ? error.message : String(error)}`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
for (const file of entries) {
|
|
107
|
+
const fullPath = join(directory, file);
|
|
108
|
+
try {
|
|
109
|
+
const module = (await import(pathToFileURL(fullPath).href));
|
|
110
|
+
const exported = module.default;
|
|
111
|
+
const definitions = (Array.isArray(exported) ? exported : [exported]).filter((value) => typeof value === "object" &&
|
|
112
|
+
value !== null &&
|
|
113
|
+
typeof value.name === "string" &&
|
|
114
|
+
typeof value.run === "function");
|
|
115
|
+
if (definitions.length === 0) {
|
|
116
|
+
logger.warn(`${join(LOCAL_COMMANDS_DIR, file)} exports no valid command.`);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
for (const definition of definitions) {
|
|
120
|
+
registry.register(definition, { kind: "local", file });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
logger.warn(`Could not load ${join(LOCAL_COMMANDS_DIR, file)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splits a child process stream into whole lines.
|
|
3
|
+
*
|
|
4
|
+
* A child writes in chunks that do not respect line boundaries: a single TypeScript
|
|
5
|
+
* diagnostic can arrive split across two `data` events. Prefixing raw chunks
|
|
6
|
+
* would tear messages in half and interleave them with other tasks, which is
|
|
7
|
+
* exactly what makes unified logs unreadable.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createLineSplitter(onLine: (line: string) => void): {
|
|
10
|
+
push(chunk: Buffer | string): void;
|
|
11
|
+
flush(): void;
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=log-mux.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splits a child process stream into whole lines.
|
|
3
|
+
*
|
|
4
|
+
* A child writes in chunks that do not respect line boundaries: a single TypeScript
|
|
5
|
+
* diagnostic can arrive split across two `data` events. Prefixing raw chunks
|
|
6
|
+
* would tear messages in half and interleave them with other tasks, which is
|
|
7
|
+
* exactly what makes unified logs unreadable.
|
|
8
|
+
*/
|
|
9
|
+
export function createLineSplitter(onLine) {
|
|
10
|
+
let buffer = "";
|
|
11
|
+
return {
|
|
12
|
+
push(chunk) {
|
|
13
|
+
buffer += chunk.toString();
|
|
14
|
+
let newline = buffer.indexOf("\n");
|
|
15
|
+
while (newline !== -1) {
|
|
16
|
+
const line = buffer.slice(0, newline).replace(/\r$/, "");
|
|
17
|
+
buffer = buffer.slice(newline + 1);
|
|
18
|
+
onLine(line);
|
|
19
|
+
newline = buffer.indexOf("\n");
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
flush() {
|
|
23
|
+
if (buffer.length === 0)
|
|
24
|
+
return;
|
|
25
|
+
const line = buffer.replace(/\r$/, "");
|
|
26
|
+
buffer = "";
|
|
27
|
+
onLine(line);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=log-mux.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Logger } from "../plugins/api.js";
|
|
2
|
+
export interface TaskDefinition {
|
|
3
|
+
/** Short label shown as the log prefix. */
|
|
4
|
+
name: string;
|
|
5
|
+
command: string;
|
|
6
|
+
args: string[];
|
|
7
|
+
/** Colourises this task's prefix. */
|
|
8
|
+
paint: (text: string) => string;
|
|
9
|
+
}
|
|
10
|
+
export interface SupervisorOptions {
|
|
11
|
+
tasks: TaskDefinition[];
|
|
12
|
+
cwd: string;
|
|
13
|
+
logger: Logger;
|
|
14
|
+
/** Receives, verbatim, every line the supervisor prints for a task (prefix and colours included). */
|
|
15
|
+
onOutput?: (text: string) => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Runs several long-lived tools side by side under one terminal.
|
|
19
|
+
*
|
|
20
|
+
* Two rules drive the design:
|
|
21
|
+
*
|
|
22
|
+
* - A task dying is a hard stop. A `rowork dev` that keeps a Rojo server alive
|
|
23
|
+
* after the compiler died would serve stale code while looking healthy,
|
|
24
|
+
* which is worse than exiting.
|
|
25
|
+
* - Nothing outlives the supervisor. Every exit path, including Ctrl+C and a
|
|
26
|
+
* crash of the CLI itself, goes through a process-tree kill.
|
|
27
|
+
*/
|
|
28
|
+
export declare class Supervisor {
|
|
29
|
+
private readonly options;
|
|
30
|
+
private readonly running;
|
|
31
|
+
private shuttingDown;
|
|
32
|
+
private exitCode;
|
|
33
|
+
constructor(options: SupervisorOptions);
|
|
34
|
+
run(): Promise<number>;
|
|
35
|
+
private start;
|
|
36
|
+
private waitFor;
|
|
37
|
+
/**
|
|
38
|
+
* Surfaces the failure loudly and replays the task's last lines.
|
|
39
|
+
*
|
|
40
|
+
* In a unified log the actual cause has usually scrolled past, buried under
|
|
41
|
+
* output from the tasks that are still healthy. Repeating it at the bottom
|
|
42
|
+
* is the difference between a usable orchestrator and a wall of text.
|
|
43
|
+
*/
|
|
44
|
+
private reportUnexpectedExit;
|
|
45
|
+
private shutdown;
|
|
46
|
+
private installSignalHandlers;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=supervisor.d.ts.map
|