Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ac5ff6e375 | |||
| ff0c257e57 | |||
| aad5f78aba | |||
| ebdaf110d0 |
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,5 +1,21 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v0.0.10
|
||||||
|
|
||||||
|
[compare changes](https://gitea.websimple.com/pascalmartineau/wpop/compare/v0.0.9...v0.0.10)
|
||||||
|
|
||||||
|
### 🚀 Enhancements
|
||||||
|
|
||||||
|
- Ignore missing gitea repo in provision (ff0c257)
|
||||||
|
|
||||||
|
## v0.0.9
|
||||||
|
|
||||||
|
[compare changes](https://gitea.websimple.com/pascalmartineau/wpop/compare/v0.0.8...v0.0.9)
|
||||||
|
|
||||||
|
### 🚀 Enhancements
|
||||||
|
|
||||||
|
- Wpop destroy (ebdaf11)
|
||||||
|
|
||||||
## v0.0.8
|
## v0.0.8
|
||||||
|
|
||||||
[compare changes](https://gitea.websimple.com/pascalmartineau/wpop/compare/v0.0.7...v0.0.8)
|
[compare changes](https://gitea.websimple.com/pascalmartineau/wpop/compare/v0.0.7...v0.0.8)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Package manager is pnpm@10 (declared via `packageManager`); husky + lint-staged
|
|||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
Commands today: `deploy` (local → remote), `sync` (remote → local), and `provision` (bootstrap a local site). The layout assumes more will be added.
|
Commands today: `deploy` (local → remote), `sync` (remote → local), `provision` (bootstrap a local site), and `destroy` (tear down a local site). The layout assumes more will be added.
|
||||||
|
|
||||||
- `src/cli.ts` — commander entry. Defines global flags (`--cwd`, `--dry-run`, `--json`, `--yes`, `--verbose`) on the root program and registers subcommands. Each subcommand action calls `createContext(program.opts())` and passes the context as the first argument to its handler. New commands should follow this pattern: register in `cli.ts`, implement in `src/commands/<name>.ts`, take `(context, options)`.
|
- `src/cli.ts` — commander entry. Defines global flags (`--cwd`, `--dry-run`, `--json`, `--yes`, `--verbose`) on the root program and registers subcommands. Each subcommand action calls `createContext(program.opts())` and passes the context as the first argument to its handler. New commands should follow this pattern: register in `cli.ts`, implement in `src/commands/<name>.ts`, take `(context, options)`.
|
||||||
- `src/lib/context.ts` — `WPopContext` carries the resolved cwd and the global flags. Every side-effecting helper takes a context; nothing reads `process.cwd()` or the flags directly.
|
- `src/lib/context.ts` — `WPopContext` carries the resolved cwd and the global flags. Every side-effecting helper takes a context; nothing reads `process.cwd()` or the flags directly.
|
||||||
@@ -37,9 +37,10 @@ Commands today: `deploy` (local → remote), `sync` (remote → local), and `pro
|
|||||||
4. Rsync, in this order: core (excludes `wp-config.php`, `wp-content/`, `.htaccess`, `.user.ini`, `php.ini`, `robots.txt`, `.well-known/`), `vendor/` + `composer.json/lock`, then each content component. After vendor sync, asserts `vendor/autoload.php` is referenced from remote `wp-config.php`.
|
4. Rsync, in this order: core (excludes `wp-config.php`, `wp-content/`, `.htaccess`, `.user.ini`, `php.ini`, `robots.txt`, `.well-known/`), `vendor/` + `composer.json/lock`, then each content component. After vendor sync, asserts `vendor/autoload.php` is referenced from remote `wp-config.php`.
|
||||||
5. Remote DB updates: `wp core update-db`, `wp wc update` if WooCommerce is present, `wp acf json sync` if ACF ≥ 6.8 is installed.
|
5. Remote DB updates: `wp core update-db`, `wp wc update` if WooCommerce is present, `wp acf json sync` if ACF ≥ 6.8 is installed.
|
||||||
- `src/commands/provision.ts` — bootstraps a minimal local site from a positional `<slug>`. Resolves env via `readProvisionEnv` (`WP_LOCAL_ROOT_PATH`, `WEBSIMPLE_STACK_DOMAIN`, `WEBSIMPLE_STACK_PROTOCOL`, `WP_VERSION`, `WP_LOCALE`, `WPOP_GITEA_OWNER`, `WPOP_GITEA_TEMPLATE`, plus a Gitea token) and derives path (`${WP_LOCAL_ROOT_PATH}/<slug>`), DB (`wp_<slug>`), and URL. Each step is idempotent ("check, then act"): ensure the Gitea `wp-sites/<slug>` repo (create from `templates/wp-boilerplate` if missing), clone it, report git freshness, `CREATE DATABASE IF NOT EXISTS`, `wp core download --skip-content`, `wp config create`, and inject a `vendor/autoload.php` guard into `wp-config.php`. It does **not** run `wp core install` or `composer install`.
|
- `src/commands/provision.ts` — bootstraps a minimal local site from a positional `<slug>`. Resolves env via `readProvisionEnv` (`WP_LOCAL_ROOT_PATH`, `WEBSIMPLE_STACK_DOMAIN`, `WEBSIMPLE_STACK_PROTOCOL`, `WP_VERSION`, `WP_LOCALE`, `WPOP_GITEA_OWNER`, `WPOP_GITEA_TEMPLATE`, plus a Gitea token) and derives path (`${WP_LOCAL_ROOT_PATH}/<slug>`), DB (`wp_<slug>`), and URL. Each step is idempotent ("check, then act"): ensure the Gitea `wp-sites/<slug>` repo (create from `templates/wp-boilerplate` if missing), clone it, report git freshness, `CREATE DATABASE IF NOT EXISTS`, `wp core download --skip-content`, `wp config create`, and inject a `vendor/autoload.php` guard into `wp-config.php`. It does **not** run `wp core install` or `composer install`.
|
||||||
|
- `src/commands/destroy.ts` — the local inverse of `provision`. Resolves env via `readDestroyEnv` (only `WP_LOCAL_ROOT_PATH`), derives path (`${WP_LOCAL_ROOT_PATH}/<slug>`) and DB (`wp_<slug>`), confirms (yes/no, default No; `--yes` skips), guards that the path is a direct child of the root, then `rm -rf` the folder and `DROP DATABASE IF EXISTS`. Idempotent and **local only** — it never touches the Gitea repo or the remote.
|
||||||
- Caches (`composer/`, `npm/`, `pnpm/`, `yarn/`) live under `WPOP_CACHE_DIR` (default `/tmp/wpop`) and are wired into the child env so repeated runs reuse downloads.
|
- Caches (`composer/`, `npm/`, `pnpm/`, `yarn/`) live under `WPOP_CACHE_DIR` (default `/tmp/wpop`) and are wired into the child env so repeated runs reuse downloads.
|
||||||
|
|
||||||
Logging uses `consola`; structured output is opt-in via `--json` (`deploy` and `provision` emit a one-shot JSON report).
|
Logging uses `consola`; structured output is opt-in via `--json` (`deploy`, `provision`, and `destroy` emit a one-shot JSON report).
|
||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@lewebsimple/wpop",
|
"name": "@lewebsimple/wpop",
|
||||||
"version": "0.0.8",
|
"version": "0.0.10",
|
||||||
"description": "WordPress operations CLI for Websimple projects.",
|
"description": "WordPress operations CLI for Websimple projects.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "Pascal Martineau <pascal@lewebsimple.ca>",
|
"author": "Pascal Martineau <pascal@lewebsimple.ca>",
|
||||||
|
|||||||
15
src/cli.ts
15
src/cli.ts
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
import { deploy } from "./commands/deploy";
|
import { deploy } from "./commands/deploy";
|
||||||
|
import { destroy } from "./commands/destroy";
|
||||||
import { provision } from "./commands/provision";
|
import { provision } from "./commands/provision";
|
||||||
import { sync } from "./commands/sync";
|
import { sync } from "./commands/sync";
|
||||||
import { createContext } from "./lib/context";
|
import { createContext } from "./lib/context";
|
||||||
@@ -74,6 +75,20 @@ program
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
program
|
||||||
|
.command("destroy <slug>")
|
||||||
|
.description("Destroy a local WordPress site (remove folder, drop database)")
|
||||||
|
.action(async (slug: string, options: Record<string, never>) => {
|
||||||
|
const context = createContext(program.opts());
|
||||||
|
configureLogger(context);
|
||||||
|
try {
|
||||||
|
await destroy(context, slug, options);
|
||||||
|
} catch (error) {
|
||||||
|
handleError(context, error);
|
||||||
|
process.exitCode = 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function handleError(context: { json: boolean }, error: unknown): void {
|
function handleError(context: { json: boolean }, error: unknown): void {
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
if (context.json) {
|
if (context.json) {
|
||||||
|
|||||||
163
src/commands/destroy.ts
Normal file
163
src/commands/destroy.ts
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import { existsSync } from "node:fs";
|
||||||
|
import { dirname, join, resolve } from "node:path";
|
||||||
|
import { consola } from "consola";
|
||||||
|
import prompts from "prompts";
|
||||||
|
import type { WPopContext } from "../lib/context";
|
||||||
|
import { readDestroyEnv, type DestroyEnv } from "../lib/env";
|
||||||
|
import { emitJson } from "../lib/output";
|
||||||
|
import { run } from "../lib/run";
|
||||||
|
|
||||||
|
export type DestroyOptions = Record<string, never>;
|
||||||
|
|
||||||
|
type Derived = {
|
||||||
|
sitePath: string;
|
||||||
|
dbName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DestroyReport = {
|
||||||
|
command: "destroy";
|
||||||
|
slug: string;
|
||||||
|
sitePath: string;
|
||||||
|
dbName: string;
|
||||||
|
dryRun: boolean;
|
||||||
|
steps: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function destroy(
|
||||||
|
context: WPopContext,
|
||||||
|
slug: string,
|
||||||
|
_options: DestroyOptions,
|
||||||
|
): Promise<void> {
|
||||||
|
if (!/^[a-zA-Z0-9._-]+$/.test(slug)) {
|
||||||
|
throw new Error(`Invalid site slug "${slug}": use letters, numbers, ".", "_" or "-" only`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const env = readDestroyEnv(process.env);
|
||||||
|
const derived = resolveDerived(env, slug);
|
||||||
|
const report = buildReport(context, slug, derived);
|
||||||
|
|
||||||
|
if (!context.dryRun) {
|
||||||
|
const dirExists = existsSync(derived.sitePath);
|
||||||
|
const dbExists = await databaseExists(context, derived.dbName);
|
||||||
|
if (!dirExists && !dbExists) {
|
||||||
|
if (context.json) {
|
||||||
|
emitJson(report);
|
||||||
|
} else {
|
||||||
|
consola.warn(
|
||||||
|
`Nothing to destroy for ${slug}: no directory at ${derived.sitePath} and no database ${derived.dbName}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.json) {
|
||||||
|
if (!context.yes && !context.dryRun) {
|
||||||
|
throw new Error("--json requires --yes (cannot prompt for confirmation in JSON mode)");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logPlan(context, slug, derived);
|
||||||
|
if (!(await confirm(context, slug, derived))) {
|
||||||
|
consola.warn("Aborted.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertWithinRoot(env, derived.sitePath);
|
||||||
|
|
||||||
|
await removeSiteDirectory(context, derived.sitePath, report);
|
||||||
|
await dropDatabase(context, derived.dbName, report);
|
||||||
|
|
||||||
|
if (context.json) {
|
||||||
|
emitJson(report);
|
||||||
|
} else {
|
||||||
|
consola.success(`Destroyed ${slug}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveDerived(env: DestroyEnv, slug: string): Derived {
|
||||||
|
return {
|
||||||
|
sitePath: join(env.WP_LOCAL_ROOT_PATH, slug),
|
||||||
|
dbName: `wp_${slug}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildReport(context: WPopContext, slug: string, derived: Derived): DestroyReport {
|
||||||
|
return {
|
||||||
|
command: "destroy",
|
||||||
|
slug,
|
||||||
|
sitePath: derived.sitePath,
|
||||||
|
dbName: derived.dbName,
|
||||||
|
dryRun: context.dryRun,
|
||||||
|
steps: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function logPlan(context: WPopContext, slug: string, derived: Derived): void {
|
||||||
|
consola.info(`Planning destroy of ${slug}`);
|
||||||
|
consola.info(` path: ${derived.sitePath}`);
|
||||||
|
consola.info(` database: ${derived.dbName}`);
|
||||||
|
if (context.dryRun) {
|
||||||
|
consola.info("Dry-run: no changes will be made");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirm(context: WPopContext, slug: string, derived: Derived): Promise<boolean> {
|
||||||
|
if (context.yes || context.dryRun) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await prompts({
|
||||||
|
type: "confirm",
|
||||||
|
name: "confirmed",
|
||||||
|
message: `Destroy ${slug}? This deletes ${derived.sitePath} and drops ${derived.dbName}.`,
|
||||||
|
initial: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return Boolean(response.confirmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function databaseExists(context: WPopContext, dbName: string): Promise<boolean> {
|
||||||
|
const result = await run(context, "mysql", ["-N", "-e", `SHOW DATABASES LIKE '${dbName}'`], {
|
||||||
|
capture: true,
|
||||||
|
reject: false,
|
||||||
|
});
|
||||||
|
if (result.exitCode !== 0) {
|
||||||
|
// Could not query (e.g. mysql unreachable); assume it may exist so we don't wrongly skip teardown.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return result.stdout.trim().length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertWithinRoot(env: DestroyEnv, sitePath: string): void {
|
||||||
|
if (dirname(resolve(sitePath)) !== resolve(env.WP_LOCAL_ROOT_PATH)) {
|
||||||
|
throw new Error(
|
||||||
|
`Refusing to delete ${sitePath}: not a direct child of ${env.WP_LOCAL_ROOT_PATH}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeSiteDirectory(
|
||||||
|
context: WPopContext,
|
||||||
|
sitePath: string,
|
||||||
|
report: DestroyReport,
|
||||||
|
): Promise<void> {
|
||||||
|
if (!context.dryRun && !existsSync(sitePath)) {
|
||||||
|
consola.info(`Site directory already absent at ${sitePath}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
consola.info(`Removing site directory ${sitePath}`);
|
||||||
|
await run(context, "rm", ["-rf", sitePath]);
|
||||||
|
report.steps.push("fs:remove");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function dropDatabase(
|
||||||
|
context: WPopContext,
|
||||||
|
dbName: string,
|
||||||
|
report: DestroyReport,
|
||||||
|
): Promise<void> {
|
||||||
|
consola.info(`Dropping database ${dbName}`);
|
||||||
|
await run(context, "mysql", ["-e", `DROP DATABASE IF EXISTS \`${dbName}\`;`]);
|
||||||
|
report.steps.push("db:drop");
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ type Derived = {
|
|||||||
templateOwner: string;
|
templateOwner: string;
|
||||||
templateRepo: string;
|
templateRepo: string;
|
||||||
cloneUrl: string;
|
cloneUrl: string;
|
||||||
|
templateCloneUrl: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ProvisionReport = {
|
type ProvisionReport = {
|
||||||
@@ -70,8 +71,8 @@ export async function provision(
|
|||||||
throw new Error(`Local root path does not exist: ${env.WP_LOCAL_ROOT_PATH}`);
|
throw new Error(`Local root path does not exist: ${env.WP_LOCAL_ROOT_PATH}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await ensureGiteaRepo(context, env, slug, derived, report);
|
const repoAvailable = await ensureGiteaRepo(context, env, slug, derived, report);
|
||||||
await ensureSiteDirectory(context, env, slug, derived, report);
|
await ensureSiteDirectory(context, env, derived, repoAvailable, report);
|
||||||
await checkGitFreshness(context, derived.sitePath);
|
await checkGitFreshness(context, derived.sitePath);
|
||||||
await ensureDatabase(context, derived.dbName, report);
|
await ensureDatabase(context, derived.dbName, report);
|
||||||
await ensureCore(context, env, derived.sitePath, report);
|
await ensureCore(context, env, derived.sitePath, report);
|
||||||
@@ -102,6 +103,7 @@ function resolveDerived(env: ProvisionEnv, slug: string): Derived {
|
|||||||
templateOwner,
|
templateOwner,
|
||||||
templateRepo,
|
templateRepo,
|
||||||
cloneUrl: `ssh://${env.WPOP_GITEA_SSH_USER}@${host}:${env.WPOP_GITEA_SSH_PORT}/${env.WPOP_GITEA_OWNER}/${slug}.git`,
|
cloneUrl: `ssh://${env.WPOP_GITEA_SSH_USER}@${host}:${env.WPOP_GITEA_SSH_PORT}/${env.WPOP_GITEA_OWNER}/${slug}.git`,
|
||||||
|
templateCloneUrl: `ssh://${env.WPOP_GITEA_SSH_USER}@${host}:${env.WPOP_GITEA_SSH_PORT}/${templateOwner}/${templateRepo}.git`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,24 +146,30 @@ async function confirm(context: WPopContext, slug: string, derived: Derived): Pr
|
|||||||
return Boolean(response.confirmed);
|
return Boolean(response.confirmed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures the per-site Gitea repo exists. Returns `true` when the repo is
|
||||||
|
* available to clone from (it already existed or was just created), `false`
|
||||||
|
* when the user declined creation — in which case the caller falls back to
|
||||||
|
* cloning the template directly so the local site is still provisioned.
|
||||||
|
*/
|
||||||
async function ensureGiteaRepo(
|
async function ensureGiteaRepo(
|
||||||
context: WPopContext,
|
context: WPopContext,
|
||||||
env: ProvisionEnv,
|
env: ProvisionEnv,
|
||||||
slug: string,
|
slug: string,
|
||||||
derived: Derived,
|
derived: Derived,
|
||||||
report: ProvisionReport,
|
report: ProvisionReport,
|
||||||
): Promise<void> {
|
): Promise<boolean> {
|
||||||
if (context.dryRun) {
|
if (context.dryRun) {
|
||||||
consola.info(
|
consola.info(
|
||||||
`[dry-run] Would ensure Gitea repo ${derived.owner}/${slug} exists (create from ${env.WPOP_GITEA_TEMPLATE} if missing)`,
|
`[dry-run] Would ensure Gitea repo ${derived.owner}/${slug} exists (create from ${env.WPOP_GITEA_TEMPLATE} if missing)`,
|
||||||
);
|
);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const exists = await giteaRepoExists(env.giteaBaseUrl, env.giteaToken, derived.owner, slug);
|
const exists = await giteaRepoExists(env.giteaBaseUrl, env.giteaToken, derived.owner, slug);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
consola.info(`Gitea repo ${derived.owner}/${slug} already exists`);
|
consola.info(`Gitea repo ${derived.owner}/${slug} already exists`);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!context.yes) {
|
if (!context.yes) {
|
||||||
@@ -172,7 +180,11 @@ async function ensureGiteaRepo(
|
|||||||
initial: true,
|
initial: true,
|
||||||
});
|
});
|
||||||
if (!response.confirmed) {
|
if (!response.confirmed) {
|
||||||
throw new Error(`Gitea repo ${derived.owner}/${slug} is required but was not created`);
|
consola.warn(
|
||||||
|
`Skipping Gitea repo ${derived.owner}/${slug}; provisioning local site from template ${env.WPOP_GITEA_TEMPLATE}`,
|
||||||
|
);
|
||||||
|
report.steps.push("gitea:skip");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,13 +196,14 @@ async function ensureGiteaRepo(
|
|||||||
name: slug,
|
name: slug,
|
||||||
});
|
});
|
||||||
report.steps.push("gitea:create");
|
report.steps.push("gitea:create");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureSiteDirectory(
|
async function ensureSiteDirectory(
|
||||||
context: WPopContext,
|
context: WPopContext,
|
||||||
env: ProvisionEnv,
|
env: ProvisionEnv,
|
||||||
slug: string,
|
|
||||||
derived: Derived,
|
derived: Derived,
|
||||||
|
repoAvailable: boolean,
|
||||||
report: ProvisionReport,
|
report: ProvisionReport,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!context.dryRun && existsSync(derived.sitePath)) {
|
if (!context.dryRun && existsSync(derived.sitePath)) {
|
||||||
@@ -202,11 +215,22 @@ async function ensureSiteDirectory(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
consola.info(`Cloning ${derived.cloneUrl} into ${derived.sitePath}`);
|
const cloneUrl = repoAvailable ? derived.cloneUrl : derived.templateCloneUrl;
|
||||||
await run(context, "git", ["clone", derived.cloneUrl, derived.sitePath], {
|
consola.info(`Cloning ${cloneUrl} into ${derived.sitePath}`);
|
||||||
|
await run(context, "git", ["clone", cloneUrl, derived.sitePath], {
|
||||||
cwd: env.WP_LOCAL_ROOT_PATH,
|
cwd: env.WP_LOCAL_ROOT_PATH,
|
||||||
});
|
});
|
||||||
report.steps.push("git:clone");
|
report.steps.push("git:clone");
|
||||||
|
|
||||||
|
// When provisioning from the template (the per-site repo was not created),
|
||||||
|
// drop the origin remote so the clone is not tied to the template repo.
|
||||||
|
if (!repoAvailable) {
|
||||||
|
await run(context, "git", ["remote", "remove", "origin"], {
|
||||||
|
cwd: derived.sitePath,
|
||||||
|
reject: false,
|
||||||
|
});
|
||||||
|
report.steps.push("git:detach-template");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkGitFreshness(context: WPopContext, sitePath: string): Promise<void> {
|
async function checkGitFreshness(context: WPopContext, sitePath: string): Promise<void> {
|
||||||
|
|||||||
@@ -68,6 +68,20 @@ export function readProvisionEnv(env: NodeJS.ProcessEnv = process.env): Provisio
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const destroyEnvSchema = z.object({
|
||||||
|
WP_LOCAL_ROOT_PATH: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type DestroyEnv = z.infer<typeof destroyEnvSchema>;
|
||||||
|
|
||||||
|
export function readDestroyEnv(env: NodeJS.ProcessEnv = process.env): DestroyEnv {
|
||||||
|
if (!hasValue(env.WP_LOCAL_ROOT_PATH)) {
|
||||||
|
throw new Error("Missing destroy environment variable(s): WP_LOCAL_ROOT_PATH");
|
||||||
|
}
|
||||||
|
|
||||||
|
return destroyEnvSchema.parse(env);
|
||||||
|
}
|
||||||
|
|
||||||
type GiteaVariableKey = (typeof giteaVariableKeys)[number];
|
type GiteaVariableKey = (typeof giteaVariableKeys)[number];
|
||||||
|
|
||||||
type GiteaRepo = {
|
type GiteaRepo = {
|
||||||
|
|||||||
Reference in New Issue
Block a user