foundryvtt-cli icon indicating copy to clipboard operation
foundryvtt-cli copied to clipboard

New command: create a document of a pack as JSON file

Open Lyokolux opened this issue 1 year ago • 0 comments

Description

For the sake of clarity, I refer to the following as a document: an object or item in a pack.

Managing a pack as a group of JSON files is proficient for developers. In our carbonsquad project, we are starting to use the following flow in our game module: JSON files -> build packs -> the Foundry server reads the packs. We get advantages: the packs can be created without having to wait for the UI to support each item property. It is a good win for prototyping, or integrating a bunch of documents in a row. It is also a good way to version the documents in git, and generating the packs from them automatically.

As I searched through modules, everyone used this flow or the interface to add new items. Both should be supported for a great experience. There is no official way to do it though, and each project has to implement its own version. It results in a waste of time that could be used for something else.

What advantages does it bring?

  • there is already a way to do it. So it can be used and called a day instead of understanding it first, then recreating a small tool for it.
  • it will be more robust than a quick implementation in 2 hours
  • all item boilerplate will be generated:
    • id
    • type
    • img base path if one exists
    • properties of the related template.json type
  • avoid some typo mistakes in the generated properties

How to use it

I don't know yet how to, but I am thinking of this CLI as actions on resources: on packs, on documents, etc... So one way could be:

fvtt document create <pack-name>

The user is then asked some questions (similar to RPG) in order to create the document. Each step can then be validated, or everything can be provided as parameters of the command.

The function that generates the JSON file could be exported and reused by other projects if one has specific needs as it is already done by the other commands.

I am also open to suggestions, as I am searching for an easy and safe flow. I am looking forward to implementing it.

Implementation

I still don't understand some pieces that makes a robust and reusable document ID in Foundry. ``!${type}!${documentSlug}` seems ok but it is somehow not a good fit.

Here is a raw implementation as a simple js script, in which we get the structure (parameters, some checking, generating the JSON object, and saving the file.

import fs from "fs"
import path from "path"

// this can be set in a config file for example
const BASE_DIR = path.join("packs", "src")
// This can be set in a config file for example
const BASE_IMG_REF = "systems/<pack-name>/media/"

const TYPES = ["actors", "adventures", "cards", "messages", "combats", "fog", "folders", "items", "journal", "macros", "playlists", "tables",
  "scenes", "settings", "users"]

/**
 * @param {number} len
 * @returns {string}
 */
const generateID = (len = 16) => {
  const p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
  return [...Array(len)].reduce(a => a + p[~~(Math.random() * p.length)], "")
}

/**
 * @param {string} s
 * @returns {string}
 */
const slugify = s => s.replaceAll(" ", "-").toLowerCase()

// read the parameters as passed to the cli
const [_, _2, type, name, dirpath] = process.argv

if (!type || !name) {
  console.error("ERR: A type and a name must be provided. Use the following")
  console.error("npm run generateDocumentTpl <type> <name> <dir>")
  process.exit(1)
}

if (!dirpath) {
  console.error("ERR: A path must be provided as last argument.")
  process.exit(1)
}

const dir = path.resolve(BASE_DIR, dirpath)
if (!fs.existsSync(dir)) {
  fs.mkdirSync(dir)
  console.info(`INFO: The directory ${dir} does not exist. One is created`)
}

if (!TYPES.includes(type)) {
  console.error(`ERR: The type should be one of ${TYPES.join(", ")}.`)
  process.exit(1)
}

const documentSlug = slugify(name)
const document = {
  _id: generateID(),
  _key: `!${type}!${documentSlug}`,
  img: `${BASE_IMG_REF}/TODO`,
  name,
  system: {}
}

const dest = path.resolve(BASE_DIR, dir, `${documentSlug}.json`)

fs.writeFileSync(
  dest,
  JSON.stringify(document, undefined, 2)
)

console.log(`Document "${name}" generated in ${dest}`)
console.log("Complete the img path, then the system properties.")

Lyokolux avatar Oct 04 '23 17:10 Lyokolux