diff --git a/commands/build.js b/commands/build.js index 3e5c714..78c3e0c 100644 --- a/commands/build.js +++ b/commands/build.js @@ -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) diff --git a/commands/create.js b/commands/create.js index e4c370f..952b9b9 100644 --- a/commands/create.js +++ b/commands/create.js @@ -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() @@ -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) + } } } diff --git a/commands/init.js b/commands/init.js index dbc98d8..63307eb 100644 --- a/commands/init.js +++ b/commands/init.js @@ -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 } } diff --git a/commands/shell.js b/commands/shell.js index 5db02ec..07705d0 100644 --- a/commands/shell.js +++ b/commands/shell.js @@ -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 } } diff --git a/index.js b/index.js index c84751e..cfc0516 100755 --- a/index.js +++ b/index.js @@ -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' @@ -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 @@ -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() diff --git a/lib/checks.js b/lib/checks.js index 5d1396b..243c5da 100644 --- a/lib/checks.js +++ b/lib/checks.js @@ -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`) } @@ -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}`) diff --git a/lib/files.js b/lib/files.js index f05bab5..54d0dc5 100644 --- a/lib/files.js +++ b/lib/files.js @@ -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" @@ -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 diff --git a/lib/react.js b/lib/react.js index 9767030..f020f30 100644 --- a/lib/react.js +++ b/lib/react.js @@ -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, } - diff --git a/lib/urbit.js b/lib/urbit.js index f7ed004..0e41e10 100644 --- a/lib/urbit.js +++ b/lib/urbit.js @@ -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`); @@ -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 @@ -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 @@ -50,4 +73,5 @@ export { restartFakeShip, fetchUrbitBinary, installCoreDependencies, + installShrubDependencies, } diff --git a/package-lock.json b/package-lock.json index 62ccb41..a0be46e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { "name": "@archetype-org/golem", - "version": "1.5.2", + "version": "1.6.1-next.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@archetype-org/golem", - "version": "1.5.2", + "version": "1.6.1-next.0", "license": "ISC", "dependencies": { "@archetype-org/clack": "^0.2.0", "@archetype-org/ribbit": "^0.2.0", "@near-js/accounts": "1.0.4", + "chokidar": "^3.6.0", "commander": "^11.0.0", "exec-sh": "^0.4.0" }, @@ -5052,7 +5053,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "peer": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -5240,6 +5240,17 @@ } ] }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bl": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", @@ -5487,6 +5498,29 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -6411,7 +6445,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } @@ -6855,6 +6888,17 @@ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "peer": true }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-core-module": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", @@ -8711,7 +8755,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -9593,6 +9636,17 @@ "node": ">= 6" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/readline": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", diff --git a/package.json b/package.json index 33090aa..cd01a5b 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "bin": { "golem": "./index.js" }, - "version": "1.5.2", + "version": "1.6.1-next.4", "description": "Project creation and package management CLI for Urbit", "main": "index.js", "scripts": { @@ -15,8 +15,9 @@ "dependencies": { "@archetype-org/clack": "^0.2.0", "@archetype-org/ribbit": "^0.2.0", + "@near-js/accounts": "1.0.4", + "chokidar": "^3.6.0", "commander": "^11.0.0", - "exec-sh": "^0.4.0", - "@near-js/accounts": "1.0.4" + "exec-sh": "^0.4.0" } } diff --git a/partials/cruft.js b/partials/cruft.js index f8bf8fa..cabf546 100644 --- a/partials/cruft.js +++ b/partials/cruft.js @@ -3,7 +3,7 @@ function cruft (shipName, deskName) { { path: `apps/${deskName}/desk`, name: "sys.kelvin", - content: `[%zuse 412]` + content: `[%zuse 411]` }, { path: `apps/${deskName}/desk`, name: "desk.docket-0", @@ -23,7 +23,7 @@ function cruft (shipName, deskName) { content: `:~ %${deskName} ==` }, { - path: 'ships', + path: '.', name: 'ships.json', content: JSON.stringify({ ships: [ @@ -33,6 +33,25 @@ function cruft (shipName, deskName) { } ] }, null, 2) + }, { + path: 'ships', + name: '.keep', + content: '' + }, { + path: '.', + name: '.gitignore', + content: `.node_modules +.DS_Store +dist +dist-ssr +*.local +stats.html +.eslintcache +.vercel +ships +./ships/urbit +desk-deps + ` }, ] return files diff --git a/partials/hub-agent.js b/partials/hub-agent.js new file mode 100644 index 0000000..8a85c80 --- /dev/null +++ b/partials/hub-agent.js @@ -0,0 +1,168 @@ +function hubAgent (shipName, deskName) { + const files = [ + { + path: `apps/${deskName}/desk/app`, + name: `${deskName}.hoon`, + content: ` + /- item + /+ default-agent, dbug + |% + +$ versioned-state + $% state-0 + == + +$ state-0 [%0 =items:item] + +$ card card:agent:gall + ++ tell + |= update=update:item + ^- (list card) + :~ :* %give %fact ~[/updates] %item-update + !>(update) + == + == + -- + %- agent:dbug + =| state-0 + =* state - + ^- agent:gall + |_ =bowl:gall + +* this . + def ~(. (default-agent this %.n) bowl) + :: + ++ on-init + ^- (quip card _this) + :_ + this(items ~) + ?: =(our.bowl ~zod) ~ + :~ + [%pass /items %agent [~zod %hub] %watch /updates] + == + :: + ++ on-save + ^- vase + !>(state) + :: + ++ on-load + |= old-state=vase + ^- (quip card _this) + =/ old !<(versioned-state old-state) + ?- -.old + %0 \\\`this(state old) + == + :: + ++ on-poke + |= [=mark =vase] + ^- (quip card _this) + |^ + ?> =(src.bowl our.bowl) + ?+ mark (on-poke:def mark vase) + %item-action + =^ cards state + (handle-poke !<(action:item vase)) + [cards this] + == + ++ handle-poke + |= =action:item + ^- (quip card _state) + ?- -.action + %create + =/ id (mod now.bowl (pow 10 10)) + ?: (~(has by items) id) + $(now.bowl (add id 1)) + :_ state(items (~(put by items) id [name.action our.bowl])) + (tell \\\`update:item\\\`[%create id name.action our.bowl]) + :: + %delete + :_ state(items (~(del by items) id.action)) + (tell \\\`update:item\\\`action) + :: + %update + :- (tell \\\`update:item\\\`[%update id.action name.action our.bowl]) + %= state + items %+ ~(jab by items) + id.action + |=(i=item:item i(name name.action, src our.bowl)) + == + == + -- + :: + ++ on-watch + |= =path + ^- (quip card _this) + ?+ path (on-watch:def path) + [%updates ~] + %- (slog leaf+"Subscribed to updates." ~) + :_ this + :~ [%give %fact ~[/updates] %item-update !>([%initial items])] + == + == + ++ on-leave on-leave:def + ++ on-peek + |= =path + ^- (unit (unit cage)) + ?+ path (on-peek:def path) + [%x %items ~] + \\\`\\\`items+!>(items) + :: todo: add example for 'retrive one' + :: + == + ++ on-agent + |= [=wire =sign:agent:gall] + ^- (quip card _this) + |^ + ?+ wire (on-agent:def wire sign) + [%items ~] + ?+ -.sign (on-agent:def wire sign) + %watch-ack + ?~ p.sign + ((slog '%hub: Subscribed to ~zod!' ~) \\\`this) + ((slog '%hub: Could NOT Subscribe to ~zod!' ~) \\\`this) + :: + %kick + %- (slog '%hub: Got kick, resubscribing...' ~) + :_ this + :~ [%pass /items %agent [src.bowl %hub] %watch /updates] + == + :: + %fact + ?+ p.cage.sign (on-agent:def wire sign) + %item-update + (handle-update !<(update:item q.cage.sign)) + == + == + == + ++ handle-update + |= =update:item + ~& "Got update" + ~& update + ?- -.update + %initial + (hear (~(uni by items) items.update)) + %create + (hear (~(put by items) id.update [name=name.update src=src.update])) + %update + (hear (~(put by items) id.update [name=name.update src=src.update])) + %delete + (hear (~(del by items) id.update)) + == + ++ hear + |= items=$_(+.state) + :- ~ + %= this state + %= state items + items + == + == + -- + ++ on-arvo on-arvo:def + ++ on-fail on-fail:def + -- + ` + }, + ] + return files + } + + export { + hubAgent, + } + \ No newline at end of file diff --git a/partials/item-source-files.js b/partials/item-source-files.js new file mode 100644 index 0000000..0ae03a0 --- /dev/null +++ b/partials/item-source-files.js @@ -0,0 +1,124 @@ +function itemFiles (shipName, deskName) { + const files = [ + { + path: `apps/${deskName}/desk/mar/item`, + name: `action.hoon`, + content: ` + /- item + /+ *item + |_ =action:item + ++ grab + |% + ++ noun action:item + ++ json decode-item-action + -- + ++ grow + |% + ++ noun action + -- + ++ grad %noun + --` + }, { + path: `apps/${deskName}/desk/mar/item`, + name: `update.hoon`, + content: ` + /- item + |_ =update:item + ++ grab + |% + ++ noun update:item + -- + ++ grow + |% + ++ noun update + -- + ++ grad %noun + --` + }, { + path: `apps/${deskName}/desk/sur`, + name: `item.hoon`, + content: ` +|% ++$ id @ ++$ name @t ++$ src @p ++$ item [=name =src] ++$ items (map id item) ++$ action + $% [%create =name] + [%delete =id] + [%update =id =name] + == ++$ update + $% [%create =id =name =src] + [%delete =id] + [%update =id =name =src] + [%initial =items] + == +-- +` + }, { + path: `apps/${deskName}/desk/lib`, + name: `item.hoon`, + content: ` +/- *item +|% +++ decode-item-action + =, dejs:format + |= jon=json + ^- action + %. jon + %- of + :~ [%create (ot ~[name+so])] + [%delete (ot ~[id+ni])] + [%update (ot ~[id+ni name+so])] + == +++ enc-items + |= =items + ^- json + a+(turn ~(tap by items) enc-item-tuple) +++ enc-item-tuple + =, enjs:format + |= t=[=id =item] + ^= json + %- pairs + :~ ['id' (numb id.t)] + ['item' (enc-item item.t)] + == +++ enc-item + =, enjs:format + |= i=item + %- pairs + :~ + ['name' (tape (trip name.i))] + == +-- +` + }, { + path: `apps/${deskName}/desk/mar`, + name: `items.hoon`, + content: ` + /- *item + /+ *item + |_ =items + ++ grab + |% + ++ noun items + -- + ++ grow + |% + ++ noun items + ++ json (enc-items items) + -- + ++ grad %noun + -- + ` + }, + ] + return files + } + + export { + itemFiles, + } + \ No newline at end of file diff --git a/partials/react-files.js b/partials/react-files.js index 15d685e..d64282d 100644 --- a/partials/react-files.js +++ b/partials/react-files.js @@ -84,7 +84,7 @@ module.exports = { - ${deskName}} + ${deskName} diff --git a/partials/shrub-files.js b/partials/shrub-files.js new file mode 100644 index 0000000..3734f4f --- /dev/null +++ b/partials/shrub-files.js @@ -0,0 +1,110 @@ + +function shrubFiles (shipName, deskName) { + const neoPath = `apps/${deskName}/desk/neo/cod/std/src` + const files = [ + { + path: `apps/${deskName}/desk`, + name: "desk.docket-0", + content: `:~ + title+'${deskName}' + info+'Shrubbery namespace browser.' + color+0xdd.dddd + image+'https://storage.googleapis.com/tlon-prod-memex-assets/simsur-ronbet/2024.6.19..16.56.27..5ced.9168.72b0.20c4-sky-icon.png' + site+/neo/sky + version+[0 0 1] + website+'https://github.com/urbit/shrub' + license+'MIT' + ==` + }, { + path: `${neoPath}/imp`, + name: "counter.hoon", + content: `/@ number +/@ counter-diff +^- kook:neo +|% +++ state + ^- curb:neo + [%pro %number] +++ poke + ^- (set stud:neo) + (sy %counter-diff ~) +++ kids + ^- kids:neo + *kids:neo +++ deps + ^- deps:neo + *deps:neo +++ form + ^- form:neo + |_ [=bowl:neo =aeon:neo =stud:neo state-vase=vase] + +* state !<(number state-vase) + ++ init + |= old=(unit pail:neo) + ^- (quip card:neo pail:neo) + [~ (need old)] + ++ poke + |= [=stud:neo vaz=vase] + ^- (quip card:neo pail:neo) + ?> =(%counter-diff stud) + =/ act + !<(counter-diff vaz) + ?> =(-.act %inc) + [~ [%number !>(+(state))]] + -- +--` + }, { + path: `${neoPath}/pro`, + name: "counter-diff.hoon", + content: `,[%inc ~]` + }, { + path: `${neoPath}/pro`, + name: "number.hoon", + content: `,@ud` + }, { + path: `${neoPath}/con`, + name: "number-htmx.hoon", + content: `/@ number :: @ud +/- feather-icons +:- [%number %$ %htmx] +|= =number +|= =bowl:neo +^- manx +;div.p3.fc.g2.ac.br2 + ;h1: Counter + ;p: {} + ;form + =hx-post "/neo/hawk{(en-tape:pith:neo here.bowl)}?stud=counter-diff" + =hx-target "find .loading" + =hx-swap "outerHTML" + =head "inc" + ;button.bd1.br1.p2.b1.hover.loader + ;span.loaded: Increment + ;span.loading + ;+ loading.feather-icons + == + == + == +== +` + }, { + path: `${neoPath}/con`, + name: "node-counter-diff.hoon", + content: `/@ node :: manx +/@ counter-diff :: [%inc ~] +/- manx-utils +:- [%node %$ %counter-diff] +|= =node +^- counter-diff +=/ mu ~(. manx-utils node) +=/ head (?(%inc) (got:mu %head)) +[head ~] +` + } + ] + return files + } + + export { + shrubFiles, + } + \ No newline at end of file diff --git a/templates/aframe-desk.js b/templates/aframe-desk.js new file mode 100644 index 0000000..15b48cd --- /dev/null +++ b/templates/aframe-desk.js @@ -0,0 +1,34 @@ +import { cruft } from '../partials/cruft.js' +import { crudAgent } from '../partials/crud-agent.js' +import { itemFiles } from '../partials/item-files.js' + +function crudDesk (shipName, deskName) { + const files = [ + ...cruft(shipName, deskName), + ...crudAgent(shipName, deskName), + ...itemFiles(shipName, deskName), + { + path: `apps/${deskName}/ui`, + name: `index.html`, + content: ` + + + + + + + + + + + + + + + ` + }, + ] + return { files } +} + +export default crudDesk diff --git a/templates/sail-desk.js b/templates/cms-desk.js similarity index 100% rename from templates/sail-desk.js rename to templates/cms-desk.js diff --git a/templates/hub-desk.js b/templates/hub-desk.js new file mode 100644 index 0000000..ce77166 --- /dev/null +++ b/templates/hub-desk.js @@ -0,0 +1,83 @@ +import { cruft } from '../partials/cruft.js' +import { hubAgent } from '../partials/hub-agent.js' +import { itemFiles } from '../partials/item-source-files.js' +import { reactFiles } from '../partials/react-files.js' + +function crudDesk (shipName, deskName) { + const files = [ + ...cruft(shipName, deskName), + ...hubAgent(shipName, deskName), + ...itemFiles(shipName, deskName), + ...reactFiles(shipName, deskName), + { + path: `apps/${deskName}/ui/src`, + name: `app.jsx`, + content: ` +import React, { useEffect, useState } from 'react' +import Urbit from '@urbit/http-api' + +const api = new Urbit('', '', window.desk) +api.ship = window.ship + +const sendPoke = async (path, payload) => { + return api.poke({ + app: "hub", + mark: "item-action", + json: { [path]: { ...payload } }, + onError: err => { + alert(\\\`Error: \\\${path} failed. You may need to refresh the page and try again\\\`) + } + }) +} + +const scryFor = async (path) => { + return api.scry({ app: 'hub', path }) +} + + +export function App() { + const [items, setItems] = useState([]) + const [name, setName] = useState("") + + useEffect(() => { + async function init() { + let items = await scryFor('/items') + setItems(items) + } + init() + }, []) + + return ( +
+
+
+
+

Welcome to hub

+

Here are ~zod's items:

+
+ {items && ( +
    + {items.map(({id, item}) => ( +
  • +
    +

    + {item.name || 'untitled'} +   +

    +
    +
  • + ))} +
+ )} +
+
+
+ ) +} +` + } + ] + return { files } +} + +export default crudDesk diff --git a/templates/phaser-desk.js b/templates/phaser-desk.js new file mode 100644 index 0000000..af96199 --- /dev/null +++ b/templates/phaser-desk.js @@ -0,0 +1,77 @@ +import { cruft } from '../partials/cruft.js' +import { crudAgent } from '../partials/crud-agent.js' +import { itemFiles } from '../partials/item-files.js' + +function crudDesk (shipName, deskName) { + const files = [ + ...cruft(shipName, deskName), + ...crudAgent(shipName, deskName), + ...itemFiles(shipName, deskName), + { + path: `apps/${deskName}/ui`, + name: `index.html`, + content: ` + + + + + + + + + + + + ` + }, + ] + return { files } +} + +export default crudDesk diff --git a/templates/shrub-desk.js b/templates/shrub-desk.js new file mode 100644 index 0000000..5edb8c8 --- /dev/null +++ b/templates/shrub-desk.js @@ -0,0 +1,13 @@ +import { cruft } from '../partials/cruft.js' +import { shrubFiles } from '../partials/shrub-files.js' + +function shrubDesk (shipName, deskName) { + const files = [ + ...cruft(shipName, deskName), + ...shrubFiles(shipName, deskName), + ] + return { files } +} + +export default shrubDesk +