Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 26 additions & 18 deletions commands/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,40 @@ import { copyDeskToUrbit } from '../lib/files.js'
import { isInGolemProject, isUrbitInstalled, isShipCreated, isDeskMountedOnShip } from '../lib/checks.js'
import { buildUI } from '../lib/react.js'

async function build ({ uiOnly }) {
async function build ({ uiOnly, connect }) {
try {
await isInGolemProject()

const { ships } = JSON.parse(fs.readFileSync('./ships/ships.json'))
const { pier, desks } = ships[0] // only supports zod for now

const deskName = desks[0]
const { ships } = JSON.parse(fs.readFileSync('./ships.json'))

if (!uiOnly) {
await isUrbitInstalled()
await isShipCreated(pier)
await isDeskMountedOnShip(deskName, pier)

console.log('BUILD: building desk to urbit ship')
await copyDeskToUrbit(deskName, pier)
const urbitSafeDeskName = `%${deskName}`
const clack = await Clack({ ship: `ships/${pier}` })
await clack.commitDesk(urbitSafeDeskName)
await clack.reviveDesk(urbitSafeDeskName)

for (const ship of ships) {
const { pier, desks } = ship

await isShipCreated(pier, { connect })

for (const desk of desks) {
await isDeskMountedOnShip(desk, pier)

console.log(`BUILD: building desk ${desk} to urbit ship ${pier}`)
await copyDeskToUrbit(desk, pier)
const urbitSafeDeskName = `%${desk}`
const clack = await Clack({ ship: `ships/${pier}` })
await clack.commitDesk(urbitSafeDeskName)
await clack.reviveDesk(urbitSafeDeskName)
}
}
} else {
console.log('BUILD: skipping desk build to urbit ship, because --ui-only flag set to true')
console.log('BUILD: skipping desk build to urbit ship, because --ui-only flag was set')
}

const uniqueDesks = new Set(ships.flatMap(ship => ship.desks))
for (const desk of uniqueDesks) {
console.log(`BUILD: building UI for ${desk}`)
await buildUI(desk)
}

console.log('BUILD: building UI')
await buildUI(deskName)

} catch (err) {
console.log(err)
Expand Down
19 changes: 14 additions & 5 deletions commands/create.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { fileExists, createFile, createPath, getTemplates } from '../lib/files.js'
import { installCoreDependencies } from '../lib/urbit.js'
import { installCoreDependencies, installShrubDependencies } from '../lib/urbit.js'

async function create (deskName, template, { skipDeps }) {

const useShrub = template === 'shrub'

console.log(`create: creating urbit project`)
console.log(`create: using template — ${template}`)
const templates = await getTemplates()
Expand All @@ -11,11 +14,17 @@ async function create (deskName, template, { skipDeps }) {
file.path = file.path.replace('./', '')
if (!(await fileExists(file))) await createFile(`./${deskName}/${file.path}/${file.name}`, file.content.trimStart())
}
// download code dependancues (incl. base, garden etc)
const depsPath = `./${deskName}/apps/${deskName}/desk-deps`
await createPath(depsPath)

if (!skipDeps) {
await installCoreDependencies(depsPath)
// download code dependancues (incl. base, garden etc)
const depsPath = `./${deskName}/apps/${deskName}/desk-deps`
await createPath(depsPath)
if (!useShrub) {
await installCoreDependencies(depsPath)
} else {
console.log('create: installing shrub dependencies')
await installShrubDependencies(depsPath)
}
}
}

Expand Down
24 changes: 18 additions & 6 deletions commands/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,28 @@ async function init () {
try {
await isInGolemProject()

const { ships } = JSON.parse(fs.readFileSync('./ships/ships.json'))
const { pier, desks } = ships[0] // only supports zod for now
const { ships } = JSON.parse(fs.readFileSync('./ships.json'))

await isUrbitInstalled()
await isShipCreated(pier)
await isDeskMountedOnShip(desks[0], pier)
console.log(`Initialized fakeship for ${desks[0]}`)

for (const ship of ships) {
const { pier, desks } = ship

console.log(`INIT: Creating ship for ${pier}...`)
await isShipCreated(pier)
console.log(`INIT: ${pier} created.`)

for (const desk of desks) {
console.log(`INIT: Mounting ${desk} on ${pier}...`)
await isDeskMountedOnShip(desk, pier)
console.log(`INIT: Successfully mounted desk: ${desk} on pier: ${pier}.`)
}
}

console.log(`INIT: The fakeships: ${ships.map(ship => ship.pier).join(', ')} have been initialized with ${ships.flatMap(ship => ship.desks).join(', ')}`)

} catch (err) {
console.log(err)
console.error('INIT: An error occurred while attempting to create the fakeships:', err)
return err
}
}
Expand Down
17 changes: 13 additions & 4 deletions commands/shell.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
import fs from 'fs'
import { isInGolemProject, isUrbitInstalled, isShipCreated } from "../lib/checks.js"

async function shell () {
async function shell (shipName) {
try {
await isInGolemProject()
await isUrbitInstalled()

const { ships } = JSON.parse(fs.readFileSync('./ships/ships.json'))
const { pier } = ships[0] // only supports zod for now
const { ships } = JSON.parse(fs.readFileSync('./ships.json'))

const ship = shipName
? ships.find(s => s.pier === shipName)
: ships[0]

if (!ship) {
throw new Error(`Ship with name ${shipName} does not exist.`)
}

const { pier } = ship

await isShipCreated(pier, { shell: true })
} catch (err) {
console.log(err)
console.error(`SHELL: An error occurred while attempting to connect to ${shipName}:`, err)
return err
}
}
Expand Down
38 changes: 36 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import fs from 'fs'
import { fileURLToPath } from 'url';
import { join, dirname } from 'path'
import chokidar from 'chokidar';

import { program } from 'commander'
import { closeClack } from '@archetype-org/clack'
Expand Down Expand Up @@ -36,16 +37,42 @@ program.command('new')

program.command('shell')
.description('open dojo for your current project')
.argument('[shipName]', 'name of the ship whose dojo to connect to', 'zod')
.action(shell)

program.command('init')
.description('initialize a test ship for the project')
.description('initialize test ships for the project')
.action(init)

program.command('build')
.description('build the current urbit project to it\'s test environment')
.action(build)
.option('--ui-only', 'only build the UI, do not build the desk')
.option('--connect', 'attempt to connect to a running ship, instead of starting a new one')

program.command('watch')
.description('watch the current project files and run the build command on changes')
.action(() => {
const watcher = chokidar.watch('./apps', {
ignored: /node_modules|\.git|dist/,
persistent: true
});

watcher.on('change', async (path) => {
console.log(`File ${path} has been changed. Rebuilding...`);
await build({ connect: true });
console.log('Build complete.');
console.log('Watching for changes...')
});

watcher.on('close', () => {
console.log('Watcher has been closed.');
// need to close manually because the postaction hook doesn't run for persistent commands
closeClack();
});

console.log('Watching for file changes...');
});

// Package Management

Expand Down Expand Up @@ -86,6 +113,13 @@ program.command('version')
console.log(packageJson.version);
});

program.hook('postAction', () => closeClack())
const persistentCommands = ['watch'];

program.hook('postAction', () => {
const currentCommand = process.argv[2];
if (!persistentCommands.includes(currentCommand)) {
closeClack();
}
});

program.parse()
12 changes: 8 additions & 4 deletions lib/checks.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { fileExists, folderExists } from './files.js'
import { fetchUrbitBinary, bootFakeShip, restartFakeShip } from './urbit.js'

async function isInGolemProject () {
const condition = await fileExists('ships/ships.json')
const condition = await fileExists('ships.json')
if (!condition) {
throw new Error(`Could not identify the current directory as a project. Make sure you are running the command from inside a project`)
}
Expand All @@ -21,15 +21,19 @@ async function isUrbitInstalled () {
}
}

async function isShipCreated (shipName, { shell } = { shell: false}) {
async function isShipCreated (shipName, { shell, connect } = { shell: false, connect: false }) {
try {
const condition = await folderExists(`ships/${shipName}`)
if (!condition) {
console.log(`booting ${shipName} for the first time — may take a while`)
await bootFakeShip(shipName, { shell })
} else {
console.log(`pier named ${shipName} already found, restarting`)
await restartFakeShip(shipName, { shell })
if (connect) {
console.log(`pier named ${shipName} already found, attempting to connect to running ${shipName}`)
} else {
console.log(`pier named ${shipName} already found, restarting`)
await restartFakeShip(shipName, { shell })
}
}
} catch (err) {
throw new Error(`failed to find or create ship named ${shipName}`)
Expand Down
15 changes: 11 additions & 4 deletions lib/files.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { readFileSync, readdirSync } from 'fs'
import { readFileSync, readdirSync, writeFileSync } from 'fs'
import { basename, extname, dirname} from 'path'
import { fileURLToPath } from 'url';
import execSh from "exec-sh"
Expand Down Expand Up @@ -67,20 +67,27 @@ EOF

async function copyDeskToUrbit (desk, ship) {
try {
await execShPromise(`cp -r ./apps/${desk}/desk/* ./ships/${ship}/*`, true)
await execShPromise(`rm -rf ./ships/${ship}/${desk}/*`)
await execShPromise(`cp -r ./apps/${desk}/desk/* ./ships/${ship}/${desk}`)
const packageDeps = readdirSync(`./apps/${desk}/desk-deps`)
for (const packageName of packageDeps) {
if (packageName[0] !== '@') {
await execShPromise(`cp -r ./apps/${desk}/desk-deps/${packageName}/* ./ships/${ship}/*`, true)
await execShPromise(`cp -r ./apps/${desk}/desk-deps/${packageName}/* ./ships/${ship}/${desk}`, true)
} else {
// '@' means its an org folder!
const orgName = packageName
const orgPackageDeps = readdirSync(`./apps/${desk}/desk-deps/${orgName}`)
for (const orgPackageName of orgPackageDeps) {
await execShPromise(`cp -r ./apps/${desk}/desk-deps/${orgName}/${orgPackageName}/* ./ships/${ship}/*`, true)
await execShPromise(`cp -r ./apps/${desk}/desk-deps/${orgName}/${orgPackageName}/* ./ships/${ship}/${desk}`, true)
}
}
}
if (!(desk === 'base')) { // base has no docket
const docketPath = `./apps/${desk}/desk/desk.docket-0`
let docketContent = readFileSync(docketPath, 'utf8')
docketContent = docketContent.replace(/%%SHIPNAME%%/g, `~${ship}`)
writeFileSync(`./ships/${ship}/${desk}/desk.docket-0`, docketContent)
}
} catch (err) {
console.error(err)
return err
Expand Down
18 changes: 16 additions & 2 deletions lib/react.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
import execSh from "exec-sh"
import { pathExists } from './files.js'
const execShPromise = execSh.promise

async function buildUI (name) {
return execShPromise(`npm run --prefix ./apps/${name}/ui build`)
const uiPath = `./apps/${name}/ui`
if (!(await pathExists(uiPath))) {
console.log(`BUILD: No /ui folder in ${name}, skipped building`)
return
}

const nodeModulesPath = `${uiPath}/node_modules`

if (!(await pathExists(nodeModulesPath))) {
console.log("BUILD: node_modules not found, installing...")
await execShPromise(`npm install --prefix ${uiPath}`)
}

const buildCommand = `npm run --prefix ${uiPath} build`
return execShPromise(buildCommand)
}

export {
buildUI,
}

28 changes: 26 additions & 2 deletions lib/urbit.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import execSh from 'exec-sh'
const execShPromise = execSh.promise

function getPortFromShipName(shipName) {
let hash = 0
for (let i = 0; i < shipName.length; i++) {
hash = shipName.charCodeAt(i) + ((hash << 5) - hash)
}
return 10000 + (hash % 10000) // Ensure port is within a valid range
}

async function fetchUrbitBinary () {
try {
await execShPromise(`curl -L https://urbit.org/install/macos-x86_64/latest | tar xzk -s '/.*/urbit/' && mv ./urbit ./ships/urbit`);
Expand All @@ -25,10 +34,23 @@ rm -rf ./landscape-git`)
}
}

async function installShrubDependencies(depsPath) {
try {
await execShPromise(`git clone https://github.com/urbit/shrub.git ./shrub-git
mkdir ${depsPath}/shrub
rsync -avL ./shrub-git/pkg/shrub/* ${depsPath}/shrub
rm -rf ./shrub-git`)
} catch (err) {
console.log(err)
return err
}
}

async function bootFakeShip (shipName, { shell } = { shell: false}) {
try {
const args = !shell ? ' -dF ' : ' -F '
await execShPromise(`./ships/urbit -c ./ships/${shipName}${args}${shipName}`);
const port = getPortFromShipName(shipName)
await execShPromise(`./ships/urbit -c ./ships/${shipName}${args}${shipName} --http-port=${port}`)
} catch (err) {
console.log(err)
return err
Expand All @@ -38,7 +60,8 @@ async function bootFakeShip (shipName, { shell } = { shell: false}) {
async function restartFakeShip (shipName, { shell } = { shell: false}) {
try {
const args = !shell ? ' -d ' : ' '
await execShPromise(`./ships/urbit${args}./ships/${shipName}`);
const port = getPortFromShipName(shipName)
await execShPromise(`./ships/urbit${args}--loom 33 ./ships/${shipName} --http-port=${port}`)
} catch (err) {
console.log(err)
return err
Expand All @@ -50,4 +73,5 @@ export {
restartFakeShip,
fetchUrbitBinary,
installCoreDependencies,
installShrubDependencies,
}
Loading