#[derive(Debug, Clone, Copy)]
pub struct ResourceTemplate {
pub name: &'static str,
pub description: &'static str,
pub contents: &'static str,
pub companions: &'static [CompanionFile],
pub companion_trackers: &'static [CompanionTracker],
}
#[derive(Debug, Clone, Copy)]
pub struct CompanionFile {
pub name: &'static str,
pub contents: &'static str,
}
#[derive(Debug, Clone, Copy)]
pub struct CompanionTracker {
pub name: &'static str,
pub audience: &'static str,
pub merge_with_source: bool,
}
pub const RESOURCE_TEMPLATES: &[ResourceTemplate] = &[
ResourceTemplate {
name: "process",
description: "a branch-local long-running process with its own port",
companions: &[],
companion_trackers: &[],
contents: r#"kind = "process"
ownership = "branch"
# Trackers or resources that must be ready first, e.g. ["deps", "runtime-env"].
depends_on = []
[ports]
app = { start = 3100, env = "PORT" }
[actions.start]
# Edit to your dev command, e.g. "pnpm dev" or "bin/rails server".
command = "npm run dev"
long_running = true
[actions.stop]
signal = "term"
[exports]
APP_URL = "http://127.0.0.1:{{ports.app}}"
# Most tools read their port from a committed config file rather than argv.
# Uncomment and point this at yours. There is no template file: `find` names
# the project's working default, so a clone without newgit still starts on it.
#
# `find` is literal, never a regex, and must match exactly once — which is
# also the drift detector. When the default changes upstream, the bind fails
# naming the file and the string instead of quietly doing nothing.
#
# [[render]]
# path = "vite.config.ts"
# replace = [
# { find = "port: 3000", with = "port: {{ports.app}}" },
# ]
"#,
},
ResourceTemplate {
name: "pnpm",
description: "dependency install via pnpm; recomputed per instance, hardlinked from one store",
companions: &[CompanionFile {
name: "pnpm-store",
contents: r#"kind = "external-store"
ownership = "user"
[checkpoint]
mode = "hash"
paths = ["pnpm-lock.yaml"]
[restore]
mode = "none"
"#,
}],
companion_trackers: &[],
contents: r#"# Every instance installs its own dependencies: two branches with
# different lockfiles must not share a tree, or one branch's install
# rewrites the other's. What that costs is your package manager's call.
# pnpm hardlinks from one shared store, so instance ten adds directory
# entries, not gigabytes; `npm ci` expands a full copy every time.
kind = "command"
ownership = "workspace"
depends_on = ["pnpm-store"]
[identity]
paths = ["package.json", "pnpm-lock.yaml"]
[actions.prepare]
command = "pnpm install --frozen-lockfile"
[checkpoint]
mode = "hash"
paths = ["package.json", "pnpm-lock.yaml"]
[restore]
mode = "recompute"
action = "prepare"
"#,
},
ResourceTemplate {
name: "command-snapshot",
description: "a daemon-owned database captured through the daemon into a tracker",
companions: &[],
companion_trackers: &[CompanionTracker {
name: "db-snapshots",
audience: "project-devs",
merge_with_source: false,
}],
contents: r#"kind = "command-snapshot"
ownership = "branch"
# A branch-local database, named after the instance so instances never share
# one. Edit the commands for your database; the shape is what matters:
# checkpoint emits a dump into the lane, restore reads it back.
[actions.prepare]
command = "createdb {{branch.slug}} || true"
# A convenience command, not a lifecycle hook: no stage runs `migrate`, and
# nothing but you ever will — `newgit action <this resource>.migrate`. Only
# `prepare` is run for you (by `spawn`, and by a `recompute` restore).
[actions.migrate]
command = "npm run db:migrate"
[checkpoint]
mode = "command"
command = "pg_dump {{branch.slug}} > {{snapshot.path}}/db.sql && echo {{snapshot.path}}/db.sql"
into_tracker = "db-snapshots"
[restore]
mode = "command"
command = "dropdb {{branch.slug}} --if-exists && createdb {{branch.slug}} && psql --quiet {{branch.slug}} < {{state_ref}}"
[cleanup]
command = "dropdb {{branch.slug}} --if-exists"
[exports]
DATABASE_URL = "postgres://localhost/{{branch.slug}}"
"#,
},
ResourceTemplate {
name: "external",
description: "a resource another system owns; newgit holds only a handle",
companions: &[],
companion_trackers: &[],
contents: r#"kind = "external"
ownership = "external"
# newgit does not own this resource, so it never assumes deletion semantics
# it did not author: `ownership = "external"` means cleanup runs exactly the
# command below and nothing else.
#
# `captures` reads names out of the prepare command's stdout — either a flat
# JSON object or KEY=VALUE lines — and publishes them as this resource's
# exports, so `newgit run` and the hooks below can use them.
#
# When `captures` is set, stdout belongs to newgit: send anything else the
# command prints to stderr, or a chatty CLI's progress output will be
# interleaved with the values and they will not parse. A declared name that
# never turns up is reported as a warning, not an error.
[actions.prepare]
# Edit to your provisioning command.
command = "cloudctl preview create --branch {{branch.name}} --json"
captures = ["PREVIEW_ID", "PREVIEW_URL"]
[checkpoint]
mode = "external"
state_ref = "{{exports.PREVIEW_ID}}"
[restore]
# The handle is recorded, not re-created: an undo does not rewind another
# system. Change to `mode = "command"` if yours can be rewound.
mode = "external"
[cleanup]
command = "cloudctl preview delete {{state_ref}}"
"#,
},
];
pub fn resource_template(name: &str) -> Option<&'static ResourceTemplate> {
RESOURCE_TEMPLATES
.iter()
.find(|template| template.name == name)
}