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,83 @@
|
|
|
1
|
+
import type { Logger } from "../plugins/api.js";
|
|
2
|
+
/**
|
|
3
|
+
* Roblox publishes Studio for Windows and macOS only. On Linux the working
|
|
4
|
+
* route is Vinegar, a Flatpak that runs the real Studio under Wine/Proton.
|
|
5
|
+
*/
|
|
6
|
+
export declare const VINEGAR_ID = "org.vinegarhq.Vinegar";
|
|
7
|
+
export declare function needsStudioSetup(): boolean;
|
|
8
|
+
export declare function hasFlatpak(): boolean;
|
|
9
|
+
export declare function isVinegarInstalled(): boolean;
|
|
10
|
+
/** Where Vinegar keeps Studio and its Wine prefix. */
|
|
11
|
+
export declare function vinegarDataDirectory(): string;
|
|
12
|
+
/**
|
|
13
|
+
* Installs Vinegar for the current user only, so no root access is needed and
|
|
14
|
+
* nothing outside the user's home is modified.
|
|
15
|
+
*/
|
|
16
|
+
export declare function installVinegar(logger: Logger): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Finds the `Roblox` local-data directories of every Studio Wine prefix.
|
|
19
|
+
*
|
|
20
|
+
* They exist only after Studio has been launched once, because that is when
|
|
21
|
+
* Vinegar creates the prefix. Explicit descent rather than a recursive search:
|
|
22
|
+
* a Wine prefix holds a whole fake Windows tree.
|
|
23
|
+
*/
|
|
24
|
+
export declare function findStudioDataDirectories(dataDirectory?: string): string[];
|
|
25
|
+
/** Reads the Rojo version pinned in the project's rokit.toml, if any. */
|
|
26
|
+
export declare function pinnedRojoVersion(projectRoot: string | undefined): string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Puts the Rojo plugin where Studio loads plugins from.
|
|
29
|
+
*
|
|
30
|
+
* `rojo plugin install` answers "platform not supported" on Linux, so Rowork
|
|
31
|
+
* places the release's `Rojo.rbxm` itself. The plugin has to match the Rojo
|
|
32
|
+
* server it talks to, hence the version pinned in rokit.toml. Older releases
|
|
33
|
+
* publish no checksum: the file still comes from the official repository over
|
|
34
|
+
* HTTPS, and the user is told it could not be verified.
|
|
35
|
+
*/
|
|
36
|
+
export declare function installRojoPlugin(dataDirectories: readonly string[], projectRoot: string | undefined, logger: Logger): Promise<void>;
|
|
37
|
+
/** Where a Studio plugin can be found, for the ones Rowork places itself. */
|
|
38
|
+
export declare const UI_LABS: {
|
|
39
|
+
readonly repository: "PepeElToro41/ui-labs";
|
|
40
|
+
readonly asset: "Plugin.rbxm";
|
|
41
|
+
readonly file: "UILabs.rbxm";
|
|
42
|
+
readonly store: "https://create.roblox.com/store/asset/14293316215/";
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Places a plugin published as a GitHub release asset into Studio's plugins folder.
|
|
46
|
+
*
|
|
47
|
+
* Same reasoning as the Rojo plugin, and the same limit: on Linux the Wine prefix
|
|
48
|
+
* only exists after Studio has been launched once. Unlike Rojo's older releases,
|
|
49
|
+
* UI Labs publishes a SHA-256 for its plugin, so it is verified here.
|
|
50
|
+
*/
|
|
51
|
+
export declare function installReleasePlugin(dataDirectories: readonly string[], plugin: {
|
|
52
|
+
repository: string;
|
|
53
|
+
asset: string;
|
|
54
|
+
file: string;
|
|
55
|
+
}, logger: Logger): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Finds the Creator Store copy of the Rojo plugin, if Studio installed one.
|
|
58
|
+
*
|
|
59
|
+
* Studio keeps store plugins under `Roblox/<user id>/InstalledPlugins/<asset id>`.
|
|
60
|
+
* Rowork places its own copy, matching the Rojo it runs, in `Roblox/Plugins`: with
|
|
61
|
+
* both, Studio shows two Rojo buttons, and the store one is usually older.
|
|
62
|
+
*/
|
|
63
|
+
export declare function findStoreRojoPlugin(dataDirectory?: string): string | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* Says why two Rojo plugins is a problem, and what to do about it.
|
|
66
|
+
*
|
|
67
|
+
* Rojo 7.7 replaced JSON by MessagePack for its whole API. A plugin older than the
|
|
68
|
+
* server cannot read it and Studio reports "Can't parse JSON", which says nothing
|
|
69
|
+
* about the real cause. The Creator Store copy lags behind Rojo's releases.
|
|
70
|
+
*/
|
|
71
|
+
export declare function warnAboutStorePlugin(logger: Logger): void;
|
|
72
|
+
export interface StudioSetupResult {
|
|
73
|
+
pluginInstalled: boolean;
|
|
74
|
+
}
|
|
75
|
+
/** Everything `rowork studio:setup` does, reusable from `rowork start`. */
|
|
76
|
+
export declare function setupStudio(options: {
|
|
77
|
+
logger: Logger;
|
|
78
|
+
projectRoot: string | undefined;
|
|
79
|
+
plugin: boolean;
|
|
80
|
+
}): Promise<StudioSetupResult>;
|
|
81
|
+
/** Starts Studio through Vinegar and returns immediately. */
|
|
82
|
+
export declare function launchStudio(): void;
|
|
83
|
+
//# sourceMappingURL=studio.d.ts.map
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { spawn as spawnDetached } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import spawn from "cross-spawn";
|
|
6
|
+
import { RoworkError } from "../cli/errors.js";
|
|
7
|
+
import { loadConfig } from "./config.js";
|
|
8
|
+
import { run as runBinary } from "./exec.js";
|
|
9
|
+
import { downloadAsset, getRelease } from "./github-release.js";
|
|
10
|
+
import { findExecutable } from "./toolchain.js";
|
|
11
|
+
/**
|
|
12
|
+
* Roblox publishes Studio for Windows and macOS only. On Linux the working
|
|
13
|
+
* route is Vinegar, a Flatpak that runs the real Studio under Wine/Proton.
|
|
14
|
+
*/
|
|
15
|
+
export const VINEGAR_ID = "org.vinegarhq.Vinegar";
|
|
16
|
+
const FLATHUB_REPO = "https://dl.flathub.org/repo/flathub.flatpakrepo";
|
|
17
|
+
export function needsStudioSetup() {
|
|
18
|
+
return process.platform === "linux";
|
|
19
|
+
}
|
|
20
|
+
export function hasFlatpak() {
|
|
21
|
+
return findExecutable("flatpak", process.cwd()) !== undefined;
|
|
22
|
+
}
|
|
23
|
+
export function isVinegarInstalled() {
|
|
24
|
+
if (!hasFlatpak())
|
|
25
|
+
return false;
|
|
26
|
+
return spawn.sync("flatpak", ["info", VINEGAR_ID], { stdio: "ignore" }).status === 0;
|
|
27
|
+
}
|
|
28
|
+
/** Where Vinegar keeps Studio and its Wine prefix. */
|
|
29
|
+
export function vinegarDataDirectory() {
|
|
30
|
+
return join(homedir(), ".var", "app", VINEGAR_ID, "data", "vinegar");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Installs Vinegar for the current user only, so no root access is needed and
|
|
34
|
+
* nothing outside the user's home is modified.
|
|
35
|
+
*/
|
|
36
|
+
export async function installVinegar(logger) {
|
|
37
|
+
if (!hasFlatpak()) {
|
|
38
|
+
throw new RoworkError("Flatpak is not installed.", {
|
|
39
|
+
hint: "Install it with your package manager (e.g. `sudo pacman -S flatpak`, `sudo apt install flatpak`), then run this again. https://flatpak.org/setup/",
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
logger.step("adding the Flathub remote for this user (if missing)");
|
|
43
|
+
await runBinary("flatpak", ["remote-add", "--user", "--if-not-exists", "flathub", FLATHUB_REPO], {
|
|
44
|
+
cwd: process.cwd(),
|
|
45
|
+
stdio: "ignore",
|
|
46
|
+
});
|
|
47
|
+
logger.step(`installing ${VINEGAR_ID} (Roblox Studio for Linux), this downloads a few hundred MB`);
|
|
48
|
+
await runBinary("flatpak", ["install", "--user", "-y", "flathub", VINEGAR_ID], {
|
|
49
|
+
cwd: process.cwd(),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Finds the `Roblox` local-data directories of every Studio Wine prefix.
|
|
54
|
+
*
|
|
55
|
+
* They exist only after Studio has been launched once, because that is when
|
|
56
|
+
* Vinegar creates the prefix. Explicit descent rather than a recursive search:
|
|
57
|
+
* a Wine prefix holds a whole fake Windows tree.
|
|
58
|
+
*/
|
|
59
|
+
export function findStudioDataDirectories(dataDirectory = vinegarDataDirectory()) {
|
|
60
|
+
const found = [];
|
|
61
|
+
const subdirectories = (path) => {
|
|
62
|
+
try {
|
|
63
|
+
return readdirSync(path, { withFileTypes: true })
|
|
64
|
+
.filter((entry) => entry.isDirectory())
|
|
65
|
+
.map((entry) => join(path, entry.name));
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
for (const prefix of subdirectories(join(dataDirectory, "prefixes"))) {
|
|
72
|
+
for (const user of subdirectories(join(prefix, "drive_c", "users"))) {
|
|
73
|
+
const roblox = join(user, "AppData", "Local", "Roblox");
|
|
74
|
+
if (existsSync(roblox))
|
|
75
|
+
found.push(roblox);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return found;
|
|
79
|
+
}
|
|
80
|
+
/** Reads the Rojo version pinned in the project's rokit.toml, if any. */
|
|
81
|
+
export function pinnedRojoVersion(projectRoot) {
|
|
82
|
+
if (projectRoot === undefined)
|
|
83
|
+
return undefined;
|
|
84
|
+
try {
|
|
85
|
+
const toml = readFileSync(join(projectRoot, "rokit.toml"), "utf8");
|
|
86
|
+
return /^\s*rojo\s*=\s*"rojo-rbx\/rojo@([^"]+)"/m.exec(toml)?.[1];
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Puts the Rojo plugin where Studio loads plugins from.
|
|
94
|
+
*
|
|
95
|
+
* `rojo plugin install` answers "platform not supported" on Linux, so Rowork
|
|
96
|
+
* places the release's `Rojo.rbxm` itself. The plugin has to match the Rojo
|
|
97
|
+
* server it talks to, hence the version pinned in rokit.toml. Older releases
|
|
98
|
+
* publish no checksum: the file still comes from the official repository over
|
|
99
|
+
* HTTPS, and the user is told it could not be verified.
|
|
100
|
+
*/
|
|
101
|
+
export async function installRojoPlugin(dataDirectories, projectRoot, logger) {
|
|
102
|
+
const version = pinnedRojoVersion(projectRoot);
|
|
103
|
+
logger.step(`downloading the Rojo plugin (${version === undefined ? "latest" : `v${version}`})`);
|
|
104
|
+
const release = await getRelease("rojo-rbx/rojo", version === undefined ? undefined : `v${version}`);
|
|
105
|
+
const asset = release.assets?.find((candidate) => candidate.name === "Rojo.rbxm");
|
|
106
|
+
if (asset === undefined)
|
|
107
|
+
throw new Error(`no Rojo.rbxm in Rojo ${release.tag_name}`);
|
|
108
|
+
const { data, verified } = await downloadAsset(asset, { allowUnverified: true });
|
|
109
|
+
if (!verified) {
|
|
110
|
+
logger.warn(`GitHub publishes no checksum for Rojo ${release.tag_name}: the plugin could not be verified.`);
|
|
111
|
+
}
|
|
112
|
+
for (const dataDirectory of dataDirectories) {
|
|
113
|
+
const plugins = join(dataDirectory, "Plugins");
|
|
114
|
+
mkdirSync(plugins, { recursive: true });
|
|
115
|
+
writeFileSync(join(plugins, "Rojo.rbxm"), data);
|
|
116
|
+
logger.step(`installed ${join(plugins, "Rojo.rbxm")}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** Where a Studio plugin can be found, for the ones Rowork places itself. */
|
|
120
|
+
export const UI_LABS = {
|
|
121
|
+
repository: "PepeElToro41/ui-labs",
|
|
122
|
+
asset: "Plugin.rbxm",
|
|
123
|
+
file: "UILabs.rbxm",
|
|
124
|
+
store: "https://create.roblox.com/store/asset/14293316215/",
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Places a plugin published as a GitHub release asset into Studio's plugins folder.
|
|
128
|
+
*
|
|
129
|
+
* Same reasoning as the Rojo plugin, and the same limit: on Linux the Wine prefix
|
|
130
|
+
* only exists after Studio has been launched once. Unlike Rojo's older releases,
|
|
131
|
+
* UI Labs publishes a SHA-256 for its plugin, so it is verified here.
|
|
132
|
+
*/
|
|
133
|
+
export async function installReleasePlugin(dataDirectories, plugin, logger) {
|
|
134
|
+
logger.step(`downloading the ${plugin.file.replace(/\.rbxm$/, "")} plugin`);
|
|
135
|
+
const release = await getRelease(plugin.repository);
|
|
136
|
+
const asset = release.assets?.find((candidate) => candidate.name === plugin.asset);
|
|
137
|
+
if (asset === undefined)
|
|
138
|
+
throw new Error(`no ${plugin.asset} in ${plugin.repository} ${release.tag_name}`);
|
|
139
|
+
const { data, verified } = await downloadAsset(asset, { allowUnverified: true });
|
|
140
|
+
if (!verified)
|
|
141
|
+
logger.warn(`GitHub publishes no checksum for ${plugin.repository} ${release.tag_name}: the plugin could not be verified.`);
|
|
142
|
+
for (const dataDirectory of dataDirectories) {
|
|
143
|
+
const plugins = join(dataDirectory, "Plugins");
|
|
144
|
+
mkdirSync(plugins, { recursive: true });
|
|
145
|
+
writeFileSync(join(plugins, plugin.file), data);
|
|
146
|
+
logger.step(`installed ${join(plugins, plugin.file)}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/** The Rojo plugin in the Creator Store, as Studio names its folder for it. */
|
|
150
|
+
const STORE_ROJO_PLUGIN_ID = "13916111004";
|
|
151
|
+
/**
|
|
152
|
+
* Finds the Creator Store copy of the Rojo plugin, if Studio installed one.
|
|
153
|
+
*
|
|
154
|
+
* Studio keeps store plugins under `Roblox/<user id>/InstalledPlugins/<asset id>`.
|
|
155
|
+
* Rowork places its own copy, matching the Rojo it runs, in `Roblox/Plugins`: with
|
|
156
|
+
* both, Studio shows two Rojo buttons, and the store one is usually older.
|
|
157
|
+
*/
|
|
158
|
+
export function findStoreRojoPlugin(dataDirectory = vinegarDataDirectory()) {
|
|
159
|
+
for (const roblox of findStudioDataDirectories(dataDirectory)) {
|
|
160
|
+
let users;
|
|
161
|
+
try {
|
|
162
|
+
users = readdirSync(roblox, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
for (const user of users) {
|
|
168
|
+
const candidate = join(roblox, user, "InstalledPlugins", STORE_ROJO_PLUGIN_ID);
|
|
169
|
+
if (existsSync(candidate))
|
|
170
|
+
return candidate;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Says why two Rojo plugins is a problem, and what to do about it.
|
|
177
|
+
*
|
|
178
|
+
* Rojo 7.7 replaced JSON by MessagePack for its whole API. A plugin older than the
|
|
179
|
+
* server cannot read it and Studio reports "Can't parse JSON", which says nothing
|
|
180
|
+
* about the real cause. The Creator Store copy lags behind Rojo's releases.
|
|
181
|
+
*/
|
|
182
|
+
export function warnAboutStorePlugin(logger) {
|
|
183
|
+
logger.warn("Studio has TWO Rojo plugins: the Creator Store one, and the one Rowork installed.");
|
|
184
|
+
logger.info(" The Store copy is usually older than the Rojo Rowork runs. Since Rojo 7.7 it cannot talk to the server");
|
|
185
|
+
logger.info(" and Studio only says \"Can't parse JSON\". Disable it: Plugins > Manage Plugins > Rojo (Creator Store).");
|
|
186
|
+
}
|
|
187
|
+
/** Everything `rowork studio:setup` does, reusable from `rowork start`. */
|
|
188
|
+
export async function setupStudio(options) {
|
|
189
|
+
const { logger } = options;
|
|
190
|
+
if (isVinegarInstalled()) {
|
|
191
|
+
logger.step("Vinegar is already installed");
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
await installVinegar(logger);
|
|
195
|
+
}
|
|
196
|
+
if (!options.plugin)
|
|
197
|
+
return { pluginInstalled: false };
|
|
198
|
+
const directories = findStudioDataDirectories();
|
|
199
|
+
if (directories.length === 0) {
|
|
200
|
+
logger.warn("Studio has not been launched yet, so there is nowhere to put the Rojo plugin.");
|
|
201
|
+
logger.info("Run `rowork studio`, sign in to Roblox, close Studio, then `rowork studio:setup` again.");
|
|
202
|
+
return { pluginInstalled: false };
|
|
203
|
+
}
|
|
204
|
+
await installRojoPlugin(directories, options.projectRoot, logger);
|
|
205
|
+
if (findStoreRojoPlugin() !== undefined)
|
|
206
|
+
warnAboutStorePlugin(logger);
|
|
207
|
+
// The UI module previews components with the UI Labs plugin: place it too.
|
|
208
|
+
if (options.projectRoot !== undefined) {
|
|
209
|
+
try {
|
|
210
|
+
if (loadConfig(options.projectRoot).modules?.includes("ui")) {
|
|
211
|
+
await installReleasePlugin(directories, UI_LABS, logger);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
logger.warn(`Could not place the UI Labs plugin: ${error instanceof Error ? error.message : String(error)}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return { pluginInstalled: true };
|
|
219
|
+
}
|
|
220
|
+
/** Starts Studio through Vinegar and returns immediately. */
|
|
221
|
+
export function launchStudio() {
|
|
222
|
+
const child = spawnDetached("flatpak", ["run", VINEGAR_ID], { detached: true, stdio: "ignore" });
|
|
223
|
+
child.unref();
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=studio.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Where a tool comes from, which decides the advice given when it is missing. */
|
|
2
|
+
export type ToolSource = "npm" | "rokit";
|
|
3
|
+
export interface ToolRequirement {
|
|
4
|
+
/** Executable name, without any Windows extension. */
|
|
5
|
+
command: string;
|
|
6
|
+
source: ToolSource;
|
|
7
|
+
}
|
|
8
|
+
/** PATH as the orchestrated tools will see it: project binaries first. */
|
|
9
|
+
export declare function searchPath(projectRoot: string): string[];
|
|
10
|
+
/**
|
|
11
|
+
* Looks a tool up on PATH without running it.
|
|
12
|
+
*
|
|
13
|
+
* Running `--version` would be more thorough, but it starts a real process per
|
|
14
|
+
* check, and a lookup is enough to turn `spawn rojo ENOENT` into an actionable
|
|
15
|
+
* message. On Windows the shims are `.cmd` files, hence PATHEXT.
|
|
16
|
+
*/
|
|
17
|
+
export declare function findExecutable(command: string, projectRoot: string): string | undefined;
|
|
18
|
+
export declare function findMissing(requirements: readonly ToolRequirement[], projectRoot: string): ToolRequirement[];
|
|
19
|
+
/** One line of advice per missing tool, grouped by where the fix lives. */
|
|
20
|
+
export declare function installAdvice(missing: readonly ToolRequirement[]): string;
|
|
21
|
+
/** PATH string with the project's own binaries first, the way npm scripts see it. */
|
|
22
|
+
export declare function pathWithLocalBinaries(projectRoot: string): string;
|
|
23
|
+
/** True when nothing is listening on the local port, checked by trying to take it. */
|
|
24
|
+
export declare function isPortFree(port: number): Promise<boolean>;
|
|
25
|
+
/**
|
|
26
|
+
* True when `command` is on the plain PATH, the way a shell or an npm script
|
|
27
|
+
* sees it: no project binaries first, and none of Rowork's own fallback
|
|
28
|
+
* directories. Once a project has left Rowork, that is the only lookup left.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isOnPlainPath(command: string): boolean;
|
|
31
|
+
//# sourceMappingURL=toolchain.d.ts.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { accessSync, constants, statSync } from "node:fs";
|
|
2
|
+
import { createServer } from "node:net";
|
|
3
|
+
import { delimiter, join } from "node:path";
|
|
4
|
+
import { rokitBinDirectory } from "./rokit-installer.js";
|
|
5
|
+
/** PATH as the orchestrated tools will see it: project binaries first. */
|
|
6
|
+
export function searchPath(projectRoot) {
|
|
7
|
+
const current = process.env["PATH"] ?? "";
|
|
8
|
+
// Rokit's own directory comes last: right after a self-install the current
|
|
9
|
+
// shell has not picked up the PATH change yet, and it should not need to.
|
|
10
|
+
return [
|
|
11
|
+
join(projectRoot, "node_modules", ".bin"),
|
|
12
|
+
...current.split(delimiter),
|
|
13
|
+
rokitBinDirectory(),
|
|
14
|
+
].filter((entry) => entry.length > 0);
|
|
15
|
+
}
|
|
16
|
+
function isFile(path) {
|
|
17
|
+
try {
|
|
18
|
+
return statSync(path).isFile();
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function isExecutable(path) {
|
|
25
|
+
if (!isFile(path))
|
|
26
|
+
return false;
|
|
27
|
+
if (process.platform === "win32")
|
|
28
|
+
return true;
|
|
29
|
+
try {
|
|
30
|
+
accessSync(path, constants.X_OK);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Looks a tool up on PATH without running it.
|
|
39
|
+
*
|
|
40
|
+
* Running `--version` would be more thorough, but it starts a real process per
|
|
41
|
+
* check, and a lookup is enough to turn `spawn rojo ENOENT` into an actionable
|
|
42
|
+
* message. On Windows the shims are `.cmd` files, hence PATHEXT.
|
|
43
|
+
*/
|
|
44
|
+
export function findExecutable(command, projectRoot) {
|
|
45
|
+
const extensions = process.platform === "win32"
|
|
46
|
+
? ["", ...(process.env["PATHEXT"] ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean)]
|
|
47
|
+
: [""];
|
|
48
|
+
for (const directory of searchPath(projectRoot)) {
|
|
49
|
+
for (const extension of extensions) {
|
|
50
|
+
const candidate = join(directory, `${command}${extension}`);
|
|
51
|
+
if (isExecutable(candidate))
|
|
52
|
+
return candidate;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
export function findMissing(requirements, projectRoot) {
|
|
58
|
+
return requirements.filter((requirement) => findExecutable(requirement.command, projectRoot) === undefined);
|
|
59
|
+
}
|
|
60
|
+
/** One line of advice per missing tool, grouped by where the fix lives. */
|
|
61
|
+
export function installAdvice(missing) {
|
|
62
|
+
const lines = [];
|
|
63
|
+
if (missing.some((tool) => tool.source === "npm")) {
|
|
64
|
+
lines.push("Run `npm install` in the project (provides roblox-ts).");
|
|
65
|
+
}
|
|
66
|
+
if (missing.some((tool) => tool.source === "rokit")) {
|
|
67
|
+
lines.push("Run `rokit install` in the project (provides Rojo). Rokit itself: https://github.com/rojo-rbx/rokit", "If Rokit is installed but the tool is still not found, add ~/.rokit/bin to your PATH and reopen the terminal.");
|
|
68
|
+
}
|
|
69
|
+
return lines.join("\n ");
|
|
70
|
+
}
|
|
71
|
+
/** PATH string with the project's own binaries first, the way npm scripts see it. */
|
|
72
|
+
export function pathWithLocalBinaries(projectRoot) {
|
|
73
|
+
return searchPath(projectRoot).join(delimiter);
|
|
74
|
+
}
|
|
75
|
+
/** True when nothing is listening on the local port, checked by trying to take it. */
|
|
76
|
+
export function isPortFree(port) {
|
|
77
|
+
return new Promise((resolve) => {
|
|
78
|
+
const server = createServer();
|
|
79
|
+
server.once("error", () => resolve(false));
|
|
80
|
+
server.once("listening", () => server.close(() => resolve(true)));
|
|
81
|
+
server.listen(port, "127.0.0.1");
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* True when `command` is on the plain PATH, the way a shell or an npm script
|
|
86
|
+
* sees it: no project binaries first, and none of Rowork's own fallback
|
|
87
|
+
* directories. Once a project has left Rowork, that is the only lookup left.
|
|
88
|
+
*/
|
|
89
|
+
export function isOnPlainPath(command) {
|
|
90
|
+
const extensions = process.platform === "win32"
|
|
91
|
+
? ["", ...(process.env["PATHEXT"] ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean)]
|
|
92
|
+
: [""];
|
|
93
|
+
return (process.env["PATH"] ?? "")
|
|
94
|
+
.split(delimiter)
|
|
95
|
+
.filter((entry) => entry.length > 0)
|
|
96
|
+
.some((directory) => extensions.some((extension) => isExecutable(join(directory, `${command}${extension}`))));
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=toolchain.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sets `compilerOptions` entries in tsconfig.json by editing the text.
|
|
3
|
+
*
|
|
4
|
+
* tsconfig.json belongs to the user and is often JSON with comments, which a
|
|
5
|
+
* parse-and-rewrite would destroy. So the value of an existing entry is replaced
|
|
6
|
+
* in place and a missing entry is inserted as a new line, leaving everything else,
|
|
7
|
+
* comments and layout included, exactly as it was. A value the user set to
|
|
8
|
+
* something else is never overwritten: it is reported, and nothing is written.
|
|
9
|
+
*/
|
|
10
|
+
export declare function setCompilerOptions(source: string, file: string, options: Record<string, string>): string;
|
|
11
|
+
//# sourceMappingURL=tsconfig-edit.d.ts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { RoworkError } from "../cli/errors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Values Rowork itself used to write, that are safe to replace: the project
|
|
4
|
+
* template shipped the abandoned Roact JSX factory before React was chosen.
|
|
5
|
+
*/
|
|
6
|
+
const REPLACEABLE = {
|
|
7
|
+
jsxFactory: ["Roact.createElement"],
|
|
8
|
+
jsxFragmentFactory: ["Roact.Fragment"],
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Sets `compilerOptions` entries in tsconfig.json by editing the text.
|
|
12
|
+
*
|
|
13
|
+
* tsconfig.json belongs to the user and is often JSON with comments, which a
|
|
14
|
+
* parse-and-rewrite would destroy. So the value of an existing entry is replaced
|
|
15
|
+
* in place and a missing entry is inserted as a new line, leaving everything else,
|
|
16
|
+
* comments and layout included, exactly as it was. A value the user set to
|
|
17
|
+
* something else is never overwritten: it is reported, and nothing is written.
|
|
18
|
+
*/
|
|
19
|
+
export function setCompilerOptions(source, file, options) {
|
|
20
|
+
let result = source;
|
|
21
|
+
for (const [key, wanted] of Object.entries(options)) {
|
|
22
|
+
const existing = new RegExp(`("${key}"\\s*:\\s*)"([^"]*)"`).exec(result);
|
|
23
|
+
if (existing !== null) {
|
|
24
|
+
const current = existing[2];
|
|
25
|
+
if (current === wanted)
|
|
26
|
+
continue;
|
|
27
|
+
if (!(REPLACEABLE[key] ?? []).includes(current)) {
|
|
28
|
+
throw new RoworkError(`${file} sets \`${key}\` to "${current}", and this needs "${wanted}".`, {
|
|
29
|
+
hint: `Change it to "${wanted}" yourself if you are sure nothing else depends on it, then run the command again.`,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
result = `${result.slice(0, existing.index)}${existing[1]}"${wanted}"${result.slice(existing.index + existing[0].length)}`;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const opening = /("compilerOptions"\s*:\s*\{)([ \t]*\n)([ \t]*)/.exec(result);
|
|
36
|
+
if (opening === null) {
|
|
37
|
+
throw new RoworkError(`Cannot find \`compilerOptions\` in ${file}.`, {
|
|
38
|
+
hint: `Add "${key}": "${wanted}" to it by hand.`,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
const indent = opening[3];
|
|
42
|
+
const at = opening.index + opening[1].length + opening[2].length;
|
|
43
|
+
result = `${result.slice(0, at)}"${key}": "${wanted}",\n${indent}${result.slice(at)}`;
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=tsconfig-edit.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { RoworkConfig } from "../plugins/api.js";
|
|
2
|
+
/**
|
|
3
|
+
* Small, careful edits to the interface files the `ui` module generated and the user
|
|
4
|
+
* may since have changed. Same rules as `schema-edit.ts`: work on the text, refuse
|
|
5
|
+
* instead of guessing, and return the new text without touching the disk so the
|
|
6
|
+
* caller can write everything at once, or nothing.
|
|
7
|
+
*
|
|
8
|
+
* Two marker comments say where new things go. They are visible in the generated
|
|
9
|
+
* files, and moving them (or the lines above them) is how a user reorders the interface.
|
|
10
|
+
*/
|
|
11
|
+
export declare const SCREENS_MARKER = "{/* rowork:screens */}";
|
|
12
|
+
export declare const ELEMENTS_MARKER = "{/* rowork:elements */}";
|
|
13
|
+
/** Where the interface lives: `<source>/client/ui`. */
|
|
14
|
+
export declare function uiDirectory(config: RoworkConfig): string;
|
|
15
|
+
export declare function screensDirectory(config: RoworkConfig): string;
|
|
16
|
+
/** The screens that exist, by name (`Shop` for `screens/ShopScreen.tsx`). */
|
|
17
|
+
export declare function listScreens(root: string, config: RoworkConfig): string[];
|
|
18
|
+
/** Shows a new screen from `App`. Refuses when it is already there. */
|
|
19
|
+
export declare function addScreenToApp(source: string, file: string, className: string, importPath: string): string;
|
|
20
|
+
/** Places a component inside a screen. Refuses when the screen already uses it. */
|
|
21
|
+
export declare function addElementToScreen(source: string, file: string, componentName: string, importPath: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* True when the interface has the shape screens need: `App` lists screens between markers
|
|
24
|
+
* and `UiController` draws into `PlayerGui`. A project whose `ui` module predates screens
|
|
25
|
+
* does not, and is told what to change instead of being rewritten.
|
|
26
|
+
*/
|
|
27
|
+
export declare function screensSupported(root: string, config: RoworkConfig, read: (path: string) => string | undefined): {
|
|
28
|
+
ok: boolean;
|
|
29
|
+
reason?: string;
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=ui-edit.d.ts.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { RoworkError } from "../cli/errors.js";
|
|
4
|
+
import { resolveProjectPath } from "./config.js";
|
|
5
|
+
/**
|
|
6
|
+
* Small, careful edits to the interface files the `ui` module generated and the user
|
|
7
|
+
* may since have changed. Same rules as `schema-edit.ts`: work on the text, refuse
|
|
8
|
+
* instead of guessing, and return the new text without touching the disk so the
|
|
9
|
+
* caller can write everything at once, or nothing.
|
|
10
|
+
*
|
|
11
|
+
* Two marker comments say where new things go. They are visible in the generated
|
|
12
|
+
* files, and moving them (or the lines above them) is how a user reorders the interface.
|
|
13
|
+
*/
|
|
14
|
+
export const SCREENS_MARKER = "{/* rowork:screens */}";
|
|
15
|
+
export const ELEMENTS_MARKER = "{/* rowork:elements */}";
|
|
16
|
+
/** Where the interface lives: `<source>/client/ui`. */
|
|
17
|
+
export function uiDirectory(config) {
|
|
18
|
+
return `${config.paths.source}/client/ui`;
|
|
19
|
+
}
|
|
20
|
+
export function screensDirectory(config) {
|
|
21
|
+
return `${uiDirectory(config)}/screens`;
|
|
22
|
+
}
|
|
23
|
+
/** The screens that exist, by name (`Shop` for `screens/ShopScreen.tsx`). */
|
|
24
|
+
export function listScreens(root, config) {
|
|
25
|
+
const directory = resolveProjectPath(root, screensDirectory(config));
|
|
26
|
+
if (!existsSync(directory))
|
|
27
|
+
return [];
|
|
28
|
+
return readdirSync(directory)
|
|
29
|
+
.filter((file) => /Screen\.tsx$/.test(file))
|
|
30
|
+
.map((file) => file.replace(/Screen\.tsx$/, ""))
|
|
31
|
+
.sort();
|
|
32
|
+
}
|
|
33
|
+
function cannotFind(file, what) {
|
|
34
|
+
return new RoworkError(`Cannot find ${what} in ${file}.`, {
|
|
35
|
+
hint: `${file} no longer has the shape Rowork generated, so it was not touched. Put the marker back (or add the line by hand), then run the command again.`,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/** Adds `import { name } from "path";` after the last import, keeping the file's layout. */
|
|
39
|
+
function addImport(source, name, path) {
|
|
40
|
+
const line = `import { ${name} } from "${path}";`;
|
|
41
|
+
const imports = [...source.matchAll(/^import[^;]*;[ \t]*$/gm)];
|
|
42
|
+
const last = imports.at(-1);
|
|
43
|
+
if (last === undefined)
|
|
44
|
+
return `${line}\n${source}`;
|
|
45
|
+
const end = last.index + last[0].length;
|
|
46
|
+
return `${source.slice(0, end)}\n${line}${source.slice(end)}`;
|
|
47
|
+
}
|
|
48
|
+
/** Puts `<Name />` on its own line just before the marker, at the marker's indentation. */
|
|
49
|
+
function addBeforeMarker(source, file, marker, element) {
|
|
50
|
+
const at = source.indexOf(marker);
|
|
51
|
+
if (at < 0)
|
|
52
|
+
throw cannotFind(file, `the \`${marker}\` marker`);
|
|
53
|
+
const lineStart = source.lastIndexOf("\n", at) + 1;
|
|
54
|
+
const indent = /^[ \t]*/.exec(source.slice(lineStart, at))?.[0] ?? "";
|
|
55
|
+
return `${source.slice(0, lineStart)}${indent}${element}\n${source.slice(lineStart)}`;
|
|
56
|
+
}
|
|
57
|
+
/** Shows a new screen from `App`. Refuses when it is already there. */
|
|
58
|
+
export function addScreenToApp(source, file, className, importPath) {
|
|
59
|
+
if (new RegExp(`<${className}[\\s/>]`).test(source)) {
|
|
60
|
+
throw new RoworkError(`${className} is already shown by ${file}.`, { hint: "Each screen is listed once." });
|
|
61
|
+
}
|
|
62
|
+
const withElement = addBeforeMarker(source, file, SCREENS_MARKER, `<${className} />`);
|
|
63
|
+
return addImport(withElement, className, importPath);
|
|
64
|
+
}
|
|
65
|
+
/** Places a component inside a screen. Refuses when the screen already uses it. */
|
|
66
|
+
export function addElementToScreen(source, file, componentName, importPath) {
|
|
67
|
+
if (new RegExp(`<${componentName}[\\s/>]`).test(source)) {
|
|
68
|
+
throw new RoworkError(`${file} already uses ${componentName}.`, { hint: "Place a second one by hand if you need it." });
|
|
69
|
+
}
|
|
70
|
+
const withElement = addBeforeMarker(source, file, ELEMENTS_MARKER, `<${componentName} />`);
|
|
71
|
+
return addImport(withElement, componentName, importPath);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* True when the interface has the shape screens need: `App` lists screens between markers
|
|
75
|
+
* and `UiController` draws into `PlayerGui`. A project whose `ui` module predates screens
|
|
76
|
+
* does not, and is told what to change instead of being rewritten.
|
|
77
|
+
*/
|
|
78
|
+
export function screensSupported(root, config, read) {
|
|
79
|
+
const app = read(resolveProjectPath(root, join(uiDirectory(config), "App.tsx")));
|
|
80
|
+
const controller = read(resolveProjectPath(root, join(config.paths.controllers, "UiController.tsx")));
|
|
81
|
+
if (app === undefined || !app.includes(SCREENS_MARKER)) {
|
|
82
|
+
return { ok: false, reason: `${uiDirectory(config)}/App.tsx has no \`${SCREENS_MARKER}\` marker` };
|
|
83
|
+
}
|
|
84
|
+
if (controller === undefined || !controller.includes("createPortal")) {
|
|
85
|
+
return { ok: false, reason: `${config.paths.controllers}/UiController.tsx does not draw into PlayerGui with createPortal` };
|
|
86
|
+
}
|
|
87
|
+
return { ok: true };
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=ui-edit.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Used only when GitHub cannot be reached while creating a project (offline,
|
|
3
|
+
* rate limited). It is the one version Rowork writes down itself, so the weekly
|
|
4
|
+
* upstream check reports it when it falls behind.
|
|
5
|
+
*/
|
|
6
|
+
export declare const FALLBACK_ROJO_VERSION = "7.7.0";
|
|
7
|
+
/**
|
|
8
|
+
* The Rojo version to use: the one forced by `ROWORK_ROJO_VERSION` if set (a team
|
|
9
|
+
* that wants every project on the same version, and no lookup), otherwise the
|
|
10
|
+
* newest release, or undefined when GitHub cannot be reached. GitHub allows 60
|
|
11
|
+
* anonymous requests per hour per address, so a shared network can hit its limit:
|
|
12
|
+
* the caller then falls back with a warning.
|
|
13
|
+
*/
|
|
14
|
+
export declare function latestRojoVersion(): Promise<string | undefined>;
|
|
15
|
+
/** The newest version of an npm package, or undefined if the registry cannot be reached. */
|
|
16
|
+
export declare function latestNpmVersion(name: string): string | undefined;
|
|
17
|
+
export declare function readRojoPin(projectRoot: string): string | undefined;
|
|
18
|
+
/** Rewrites the Rojo version in rokit.toml, leaving the rest of the file alone. */
|
|
19
|
+
export declare function writeRojoPin(projectRoot: string, version: string): boolean;
|
|
20
|
+
/** `^3.0.0` and `~1.2.3` both mean `3.0.0` / `1.2.3` for comparison. */
|
|
21
|
+
export declare function baseVersion(range: string): string;
|
|
22
|
+
/** True when `version` (like "7.7.0") is at least `major.minor`. An unknown version is not. */
|
|
23
|
+
export declare function rojoIsAtLeast(version: string | undefined, major: number, minor: number): boolean;
|
|
24
|
+
//# sourceMappingURL=versions.d.ts.map
|