Comparing version 0.0.0 to 0.0.1-alpha.2
{ | ||
"name": "gaffer-rbx", | ||
"version": "0.0.0", | ||
"version": "0.0.1-alpha.2", | ||
"description": "A monorepo orchestrator for Roblox developers.", | ||
"keywords": [], | ||
"homepage": "https://github.com/eleanorlm/gaffer#readme", | ||
"keywords": [ | ||
"rojo", | ||
"roblox" | ||
], | ||
"homepage": "https://tools.autonor.me/gaffer", | ||
"bugs": { | ||
"url": "https://github.com/eleanorlm/gaffer/issues" | ||
"url": "https://github.com/autonordev/tools/issues" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/eleanorlm/gaffer", | ||
"directory": "cli" | ||
"url": "https://github.com/autonordev/tools", | ||
"directory": "gaffer" | ||
}, | ||
@@ -21,3 +24,3 @@ "license": "MIT", | ||
"dependencies": { | ||
"@iarna/toml": "^2.2.5", | ||
"@iarna/toml": "3.0.0", | ||
"@root/walk": "^1.1.0", | ||
@@ -28,17 +31,8 @@ "chalk": "^4.1.2", | ||
"joi": "^17.7.1", | ||
"shelljs": "^0.8.5" | ||
"shelljs": "^0.8.5", | ||
"shelljs-live": "^0.0.5" | ||
}, | ||
"engines": { | ||
"node": ">=18.12 <=19.x" | ||
}, | ||
"pkg": { | ||
"scripts": "./src/index.js", | ||
"assets": [], | ||
"targets": [ | ||
"node18-linux-x64", | ||
"node18-win-x64", | ||
"node18-macos-x64" | ||
], | ||
"outputPath": "output" | ||
} | ||
} |
@@ -1,7 +0,9 @@ | ||
# Gaffer | ||
<img src="https://i.imgur.com/7XndfZX.png" alt="Gaffer"> | ||
**A monorepo orchestrator for Roblox developers: simplify your dev experience and create more reusable, modular projects** | ||
> **Warning**: Gaffer is experimental. You probably don't want to use it yet. | ||
Visit [the Gaffer website](https://eleanorlm.github.io/gaffer/) to learn more. | ||
**A monorepo orchestrator for Roblox developers.** | ||
Gaffer is open source (MIT licensed) and [available on GitHub](https://github.com/eleanorlm/gaffer). | ||
Visit [the Gaffer website](https://gaffer.autonor.me/) to learn more. | ||
Gaffer is open source (MIT licensed) and [available on GitHub](https://github.com/autonordev/tools/tree/main/gaffer). |
@@ -14,5 +14,5 @@ const completionMessage = require('../utilities/completionMessage') | ||
.action(async (filter) => { | ||
const builtProjects = await build(filter) | ||
const { builtProjects } = await build(filter) | ||
if (builtProjects !== false) completionMessage('built', builtProjects) | ||
}) | ||
} |
module.exports = (program) => { | ||
require('./update')(program) | ||
require('./build')(program) | ||
require('./validate')(program) | ||
require('./open')(program) | ||
require('./serve')(program) | ||
require('./script')(program) | ||
require('./dev')(program) | ||
} |
@@ -0,1 +1,3 @@ | ||
const fs = require('node:fs') | ||
const path = require('node:path') | ||
const update = require('./update') | ||
@@ -6,5 +8,29 @@ const log = require('../helpers/log') | ||
const WARN_BYTES = 89 * 1e6 | ||
const NOTICE_BYTES = 86 * 1e6 | ||
const execute = (projectPath, buildPath) => | ||
exec(`rojo build --output "${escape(buildPath)}" "${escape(projectPath)}"`) | ||
const sizeWarning = (projectName, size, buildPath) => { | ||
const isXML = path.extname(buildPath).toLowerCase().endsWith('x') | ||
const addendum = isXML | ||
? ' Consider using a binary file instead of an XML file to reduce file sizes.' | ||
: '' | ||
if (size > WARN_BYTES) { | ||
const overBy = size - WARN_BYTES | ||
log.warn( | ||
`[G012] Built file \`${projectName}\` exceeds Roblox's maximum file size (${WARN_BYTES} bytes) by ${overBy} bytes.` + | ||
addendum | ||
) | ||
} else if (size > NOTICE_BYTES) { | ||
const remaining = WARN_BYTES - size | ||
log.notice( | ||
`[G012] Built file \`${projectName}\` is ${remaining} bytes away from exceeding Roblox's maximum file size limit (${WARN_BYTES} bytes).` + | ||
addendum | ||
) | ||
} | ||
} | ||
module.exports = async (filter) => { | ||
@@ -17,3 +43,3 @@ const state = await update(filter).catch((err) => log.error(err.message)) | ||
for (const projectName of state.projectNames) { | ||
const project = state.index.get(projectName) | ||
const project = state.schemes.get(projectName) | ||
const buildPath = project.outputs.build | ||
@@ -29,11 +55,21 @@ const projectPath = project.outputs.project | ||
// TODO: Maybe use concurrently here to reduce run times, especially for larger workspaces | ||
const err = execute(projectPath, buildPath) | ||
if (err) | ||
log.error( | ||
`[G010] Project \`${projectName}\` could not be built: ${err.message}` | ||
await execute(projectPath, buildPath) | ||
.then(() => { | ||
builtProjects.push(projectName) | ||
// eslint-disable-next-line security/detect-non-literal-fs-filename | ||
const { size } = fs.statSync(buildPath) | ||
sizeWarning(projectName, size, buildPath) | ||
}) | ||
.catch((err) => | ||
log.error( | ||
`[G010] Project \`${projectName}\` could not be built: ${err}` | ||
) | ||
) | ||
else builtProjects.push(projectName) | ||
} | ||
return builtProjects | ||
return { | ||
builtProjects, | ||
state | ||
} | ||
} |
@@ -33,3 +33,10 @@ // A basic Rojo tree with sensible defaults, used as the base for Gaffer projects | ||
} | ||
}, | ||
// do not remove or everything will break | ||
// not literally; but this is here to prevent a few glitches that can occur if the other is not specified | ||
"StarterPlayer": { | ||
"StarterCharacterScripts": {}, | ||
"StarterPlayerScripts": {} | ||
} | ||
} |
@@ -1,11 +0,9 @@ | ||
const init = require('./init') | ||
const discover = require('./discover') | ||
const reduce = require('./reduce') | ||
const setup = require('../setup') | ||
const reduce = require('../reduce') | ||
const transform = require('./transform') | ||
module.exports = async (filter) => { | ||
const state = await init() | ||
await discover(state, filter) | ||
const state = await setup(filter) | ||
await reduce(state) | ||
return await transform(state) | ||
} |
@@ -27,2 +27,10 @@ /* eslint-disable security/detect-object-injection, security/detect-non-literal-fs-filename */ | ||
const hasObjectChildren = (node) => { | ||
if (typeof node !== 'object') return false | ||
for (const key in node) { | ||
if (!key.startsWith('$')) return true | ||
} | ||
return false | ||
} | ||
const transformProperties = (properties) => { | ||
@@ -43,5 +51,4 @@ for (const name in properties) { | ||
const transformNode = (parentNode, projectPath, includePath, rootPath) => { | ||
const divergentPaths = projectPath !== includePath | ||
const relativePath = divergentPaths && path.relative(projectPath, includePath) | ||
const relativeRoot = path.relative(projectPath, rootPath) | ||
const outputPath = path.dirname(projectPath) | ||
const divergentPaths = outputPath !== includePath | ||
@@ -62,6 +69,9 @@ for (const nodeKey in parentNode) { | ||
// If we create a node called 'HttpService', but don't give it a path or className | ||
// then we assume that to be the node name (HttpService in this case) | ||
if (node.$className === undefined && node.$path === undefined) { | ||
node.$className = nodeKey | ||
// Rojo will infer className from the node key if it's a service. Otherwise, we'll need to infer | ||
// it ourselves if it's a childless node. Previously, this used to be a lot of complicated code | ||
// to identify if it was a service or not and only put the $className if it was necessary | ||
// But this code was super fragile and had a lot of unnecessary moving parts. So this is here | ||
// instead; I'd rather redundant className definitions than broken ones lol | ||
if (node.$path === undefined && !hasObjectChildren(node)) { | ||
if (node.$className === undefined) node.$className = nodeKey | ||
} | ||
@@ -71,7 +81,8 @@ | ||
// we need to reconcile these paths so that way it all still works | ||
if (node.$path && node.$path.startsWith('//')) | ||
node.$path = path.join(relativeRoot, node.$path.replace('//', './')) | ||
if (node.$path && divergentPaths && !path.isAbsolute(node.$path)) { | ||
node.$path = path.join(relativePath, node.$path) | ||
if (node.$path && node.$path.startsWith('//')) { | ||
const absolutePath = path.join(rootPath, node.$path.replace('//', './')) | ||
node.$path = path.relative(outputPath, absolutePath) | ||
} else if (node.$path && divergentPaths && !path.isAbsolute(node.$path)) { | ||
const absolutePath = path.join(includePath, node.$path) | ||
node.$path = path.relative(outputPath, absolutePath) | ||
} | ||
@@ -95,8 +106,18 @@ | ||
const trees = [] | ||
const project = state.index.get(projectName) | ||
const project = state.schemes.get(projectName) | ||
// Include the base tree and project specific tree | ||
trees.push(transformNode(baseTree, project.path, __dirname, state.root)) | ||
// Include the base tree (default) | ||
if (project.use_base_tree) | ||
trees.push( | ||
transformNode(baseTree, project.outputs.project, __dirname, state.root) | ||
) | ||
// And now include the project specific tree | ||
trees.push( | ||
transformNode(importTree(project), project.path, project.path, state.root) | ||
transformNode( | ||
importTree(project), | ||
project.outputs.project, | ||
project.path, | ||
state.root | ||
) | ||
) | ||
@@ -106,7 +127,7 @@ | ||
for (const includeName of project.includes) { | ||
const include = state.index.get(includeName) | ||
const include = state.schemes.get(includeName) | ||
trees.push( | ||
transformNode( | ||
importTree(include), | ||
project.path, | ||
project.outputs.project, | ||
include.path, | ||
@@ -113,0 +134,0 @@ state.root |
@@ -7,4 +7,4 @@ /* eslint-disable security/detect-unsafe-regex */ | ||
// Regex allows for alphanumeric characters (lowercase) | ||
// and (except for at the start) dashes, underscores, and slashes | ||
.regex(/^[a-z0-9]+(?:[-_/][A-Za-z0-9]+)*$/) | ||
// and (except for at the start) dashes, underscores, dots, and slashes | ||
.regex(/^[A-Za-z0-9]+(?:[-_/.][A-Za-z0-9]+)*$/) | ||
.required() | ||
@@ -14,6 +14,19 @@ const includes = Joi.array().items(Joi.string()).default([]) | ||
const scriptItem = Joi.alternatives( | ||
Joi.string(), | ||
Joi.object().keys({ | ||
cmd: Joi.string().required(), | ||
dir: Joi.string().default('.') | ||
}) | ||
) | ||
const scripts = Joi.object().pattern(/./, [ | ||
scriptItem, | ||
Joi.array().items(scriptItem) | ||
]) | ||
const workspaceSchema = Joi.object().keys({ | ||
name, | ||
edition, | ||
includes | ||
includes, | ||
scripts | ||
}) | ||
@@ -25,2 +38,4 @@ | ||
edition, | ||
scripts, | ||
use_base_tree: Joi.bool().default(true), | ||
outputs: Joi.object() | ||
@@ -37,3 +52,4 @@ .keys({ | ||
includes, | ||
edition | ||
edition, | ||
scripts | ||
}) | ||
@@ -40,0 +56,0 @@ |
@@ -8,4 +8,6 @@ const log = require('../helpers/log') | ||
) | ||
else if (projectNames.length === 1) | ||
log.success(`Project \`${projectNames.join(', ')}\` has been ${action}.`) | ||
else | ||
log.success(`Projects \`${projectNames.join(', ')}\` have been ${action}.`) | ||
} |
@@ -0,14 +1,11 @@ | ||
const process = require('node:process') | ||
const shell = require('shelljs') | ||
module.exports = (command) => { | ||
try { | ||
const out = shell.exec(command, { silent: true }) | ||
module.exports = (command, silent = true, cwd = process.cwd()) => { | ||
return new Promise((resolve, reject) => { | ||
const out = shell.exec(command, { silent, cwd }) | ||
if (out.stderr) { | ||
throw new Error(out.stderr.trim()) | ||
} | ||
return undefined | ||
} catch (err) { | ||
return err | ||
} | ||
if (out.stderr) reject(out.stderr.trim()) | ||
else resolve(out.stdout.trim()) | ||
}) | ||
} |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
32954
31
859
10
8
2
+ Addedshelljs-live@^0.0.5
+ Added@iarna/toml@3.0.0(transitive)
+ Addedcross-spawn@7.0.6(transitive)
+ Addedisexe@2.0.0(transitive)
+ Addedpath-key@3.1.1(transitive)
+ Addedshebang-command@2.0.0(transitive)
+ Addedshebang-regex@3.0.0(transitive)
+ Addedshelljs-live@0.0.5(transitive)
+ Addedwhich@2.0.2(transitive)
- Removed@iarna/toml@2.2.5(transitive)
Updated@iarna/toml@3.0.0