diff --git a/src/adapters/index.ts b/src/adapters/index.ts index 4e3fc7d..44d487b 100644 --- a/src/adapters/index.ts +++ b/src/adapters/index.ts @@ -6,11 +6,18 @@ import { pathToFileURL } from "node:url"; import { generateTypes } from "prismic-ts-codegen"; import { glob } from "tinyglobby"; -import { readJsonFile, writeFileRecursive } from "../lib/file"; +import { + exists, + readEnvFile, + readJsonFile, + setEnvFileVar, + unsetEnvFileVar, + writeFileRecursive, +} from "../lib/file"; import { stringify } from "../lib/json"; import { readPackageJson } from "../lib/packageJson"; import { appendTrailingSlash } from "../lib/url"; -import { addRoute, removeRoute, updateRoute } from "../project"; +import { addRoute, getRepositoryName, removeRoute, updateRoute } from "../project"; import { findProjectRoot, getLibraries } from "../project"; const TYPES_FILENAME = "prismicio-types.d.ts"; @@ -42,9 +49,16 @@ export class NoSupportedFrameworkError extends Error { "No supported framework found. Run this command in a Next.js, Nuxt, or SvelteKit project."; } +export async function getActiveRepositoryName(): Promise { + const adapter = await getAdapter(); + return (await adapter.getEnvironment()) ?? (await getRepositoryName()); +} + export abstract class Adapter { abstract readonly id: string; + abstract readonly environmentEnvVarName: string; + abstract onProjectInitialized(): Promise | void; abstract onSliceCreated(model: SharedSlice, library: URL): Promise | void; abstract onSliceUpdated(model: SharedSlice): Promise | void; @@ -205,4 +219,24 @@ export abstract class Adapter { await writeFileRecursive(output, types); return output; } + + async getEnvironment(): Promise { + const projectRoot = await findProjectRoot(); + const envLocalPath = new URL(".env.local", projectRoot); + if (!(await exists(envLocalPath))) return undefined; + const envLocalVars = await readEnvFile(envLocalPath); + return envLocalVars[this.environmentEnvVarName] || undefined; + } + + async setEnvironment(environment: string): Promise { + const projectRoot = await findProjectRoot(); + const envLocalPath = new URL(".env.local", projectRoot); + await setEnvFileVar(envLocalPath, this.environmentEnvVarName, environment); + } + + async unsetEnvironment(): Promise { + const projectRoot = await findProjectRoot(); + const envLocalPath = new URL(".env.local", projectRoot); + await unsetEnvFileVar(envLocalPath, this.environmentEnvVarName); + } } diff --git a/src/adapters/nextjs.templates.ts b/src/adapters/nextjs.templates.ts index fce35bc..1dd4d08 100644 --- a/src/adapters/nextjs.templates.ts +++ b/src/adapters/nextjs.templates.ts @@ -357,7 +357,8 @@ export function prismicIOFileTemplate(args: { /** * The project's Prismic repository name. */ - export const repositoryName = prismicConfig.repositoryName; + export const repositoryName = + process.env.NEXT_PUBLIC_PRISMIC_ENVIRONMENT || prismicConfig.repositoryName; ${createClientContents} `; @@ -369,7 +370,8 @@ export function prismicIOFileTemplate(args: { /** * The project's Prismic repository name. */ - export const repositoryName = prismicConfig.repositoryName; + export const repositoryName = + process.env.NEXT_PUBLIC_PRISMIC_ENVIRONMENT || prismicConfig.repositoryName; ${createClientContents} `; diff --git a/src/adapters/nextjs.ts b/src/adapters/nextjs.ts index dc61b92..b4fab6d 100644 --- a/src/adapters/nextjs.ts +++ b/src/adapters/nextjs.ts @@ -32,6 +32,8 @@ import { export class NextJsAdapter extends Adapter { readonly id = "next"; + readonly environmentEnvVarName = "NEXT_PUBLIC_PRISMIC_ENVIRONMENT"; + async setupProject(): Promise { await addDependencies({ "@prismicio/client": `^${await getNpmPackageVersion("@prismicio/client")}`, diff --git a/src/adapters/nuxt.ts b/src/adapters/nuxt.ts index a2f5fed..abe5463 100644 --- a/src/adapters/nuxt.ts +++ b/src/adapters/nuxt.ts @@ -27,6 +27,8 @@ const NUXT_PRISMIC = "@nuxtjs/prismic"; export class NuxtAdapter extends Adapter { readonly id = "nuxt"; + readonly environmentEnvVarName = "NUXT_PUBLIC_PRISMIC_ENVIRONMENT"; + async setupProject(): Promise { await addDependencies({ "@prismicio/client": `^${await getNpmPackageVersion("@prismicio/client")}`, diff --git a/src/adapters/sveltekit.templates.ts b/src/adapters/sveltekit.templates.ts index 9a07509..28196e9 100644 --- a/src/adapters/sveltekit.templates.ts +++ b/src/adapters/sveltekit.templates.ts @@ -11,12 +11,14 @@ export function prismicIOFileTemplate(args: { typescript: boolean }): string { return dedent` import { createClient as baseCreateClient } from "@prismicio/client"; import { type CreateClientConfig, enableAutoPreviews } from '@prismicio/svelte/kit'; + import { env } from '$env/dynamic/public'; import prismicConfig from "../../prismic.config.json"; /** * The project's Prismic repository name. */ - export const repositoryName = prismicConfig.repositoryName; + export const repositoryName = + env.PUBLIC_PRISMIC_ENVIRONMENT || prismicConfig.repositoryName; /** * Creates a Prismic client for the project's repository. The client is used to @@ -40,12 +42,14 @@ export function prismicIOFileTemplate(args: { typescript: boolean }): string { return dedent` import { createClient as baseCreateClient } from "@prismicio/client"; import { enableAutoPreviews } from '@prismicio/svelte/kit'; + import { env } from '$env/dynamic/public'; import prismicConfig from "../../prismic.config.json"; /** * The project's Prismic repository name. */ - export const repositoryName = prismicConfig.repositoryName; + export const repositoryName = + env.PUBLIC_PRISMIC_ENVIRONMENT || prismicConfig.repositoryName; /** * Creates a Prismic client for the project's repository. The client is used to diff --git a/src/adapters/sveltekit.ts b/src/adapters/sveltekit.ts index 55ca168..ebc58a4 100644 --- a/src/adapters/sveltekit.ts +++ b/src/adapters/sveltekit.ts @@ -34,6 +34,8 @@ import { export class SvelteKitAdapter extends Adapter { readonly id = "sveltekit"; + readonly environmentEnvVarName = "PUBLIC_PRISMIC_ENVIRONMENT"; + async setupProject(): Promise { await addDependencies({ "@prismicio/client": `^${await getNpmPackageVersion("@prismicio/client")}`, diff --git a/src/commands/env-active.ts b/src/commands/env-active.ts new file mode 100644 index 0000000..3040fde --- /dev/null +++ b/src/commands/env-active.ts @@ -0,0 +1,19 @@ +import { getAdapter } from "../adapters"; +import { createCommand, type CommandConfig } from "../lib/command"; +import { getRepositoryName } from "../project"; + +const config = { + name: "prismic env active", + description: ` + Print the active environment. + + @experimental - This command may change or be removed in any release and does not follow semver. + `, +} satisfies CommandConfig; + +export default createCommand(config, async () => { + const repo = await getRepositoryName(); + const adapter = await getAdapter(); + const environment = await adapter.getEnvironment(); + console.info(environment ?? repo); +}); diff --git a/src/commands/env-list.ts b/src/commands/env-list.ts new file mode 100644 index 0000000..53eb73f --- /dev/null +++ b/src/commands/env-list.ts @@ -0,0 +1,53 @@ +import { getAdapter } from "../adapters"; +import { getCredentials } from "../auth"; +import { createCommand, type CommandConfig } from "../lib/command"; +import { stringify } from "../lib/json"; +import { getUserEnvironments } from "../lib/prismic/environments"; +import { formatTable } from "../lib/string"; +import { getRepositoryName } from "../project"; + +const config = { + name: "prismic env list", + description: ` + List the environments available for the project. + + @experimental - This command may change or be removed in any release and does not follow semver. + `, + options: { + json: { type: "boolean", description: "Output as JSON" }, + repo: { type: "string", short: "r", description: "Repository domain" }, + }, +} satisfies CommandConfig; + +export default createCommand(config, async ({ values }) => { + const projectRepo = await getRepositoryName(); + const { json, repo = projectRepo } = values; + + const { token, host } = await getCredentials(); + const environments = await getUserEnvironments({ repo, token, host }); + + let activeEnvironment: string | undefined; + if (repo === projectRepo) { + const adapter = await getAdapter(); + activeEnvironment = (await adapter.getEnvironment()) ?? projectRepo; + } + + if (json) { + const results = environments.map((environment) => ({ + domain: environment.domain, + kind: environment.kind, + active: environment.domain === activeEnvironment, + })); + console.info(stringify(results)); + return; + } + + const rows = environments.map((environment) => { + return [ + environment.domain, + environment.kind, + environment.domain === activeEnvironment ? "*" : "", + ]; + }); + console.info(formatTable(rows, { headers: ["DOMAIN", "KIND", "ACTIVE"] })); +}); diff --git a/src/commands/env-set.ts b/src/commands/env-set.ts new file mode 100644 index 0000000..6da8edf --- /dev/null +++ b/src/commands/env-set.ts @@ -0,0 +1,39 @@ +import { getAdapter } from "../adapters"; +import { getCredentials } from "../auth"; +import { createCommand, type CommandConfig } from "../lib/command"; +import { getUserEnvironments, InvalidEnvironmentError } from "../lib/prismic/environments"; +import { getRepositoryName } from "../project"; + +const config = { + name: "prismic env set", + description: ` + Set the active environment. + + @experimental - This command may change or be removed in any release and does not follow semver. + `, + positionals: { + environment: { description: "Environment domain", required: true }, + }, +} satisfies CommandConfig; + +export default createCommand(config, async ({ positionals }) => { + const [environment] = positionals; + + const repo = await getRepositoryName(); + const { token, host } = await getCredentials(); + const environments = await getUserEnvironments({ repo, token, host }); + + const validEnvironment = environments.some((env) => env.domain === environment); + if (!validEnvironment) throw new InvalidEnvironmentError(environment, environments, repo); + + const adapter = await getAdapter(); + + if (environment === repo) { + await adapter.unsetEnvironment(); + console.info(`Reset to the production environment "${repo}".`); + return; + } + + await adapter.setEnvironment(environment); + console.info(`Set the active environment to "${environment}".`); +}); diff --git a/src/commands/env-unset.ts b/src/commands/env-unset.ts new file mode 100644 index 0000000..92e0959 --- /dev/null +++ b/src/commands/env-unset.ts @@ -0,0 +1,19 @@ +import { getAdapter } from "../adapters"; +import { createCommand, type CommandConfig } from "../lib/command"; +import { getRepositoryName } from "../project"; + +const config = { + name: "prismic env unset", + description: ` + Reset the active environment to the production environment. + + @experimental - This command may change or be removed in any release and does not follow semver. + `, +} satisfies CommandConfig; + +export default createCommand(config, async () => { + const repo = await getRepositoryName(); + const adapter = await getAdapter(); + await adapter.unsetEnvironment(); + console.info(`Reset to the production environment "${repo}".`); +}); diff --git a/src/commands/env.ts b/src/commands/env.ts new file mode 100644 index 0000000..a79ac77 --- /dev/null +++ b/src/commands/env.ts @@ -0,0 +1,32 @@ +import { createCommandRouter } from "../lib/command"; +import envActive from "./env-active"; +import envList from "./env-list"; +import envSet from "./env-set"; +import envUnset from "./env-unset"; + +export default createCommandRouter({ + name: "prismic env", + description: ` + Manage the active environment for a Prismic project. + + @experimental - This command may change or be removed in any release and does not follow semver. + `, + commands: { + set: { + handler: envSet, + description: "Set the active environment", + }, + unset: { + handler: envUnset, + description: "Reset to the production environment", + }, + active: { + handler: envActive, + description: "Print the active environment", + }, + list: { + handler: envList, + description: "List environments", + }, + }, +}); diff --git a/src/commands/index.ts b/src/commands/index.ts index 216b8fe..19479ba 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -1,5 +1,6 @@ import { createCommandRouter } from "../lib/command"; import docs from "./docs"; +import env from "./env"; import field from "./field"; import gen from "./gen"; import init from "./init"; @@ -57,6 +58,11 @@ export default createCommandRouter({ handler: status, description: "Show local vs remote model differences", }, + env: { + handler: env, + description: "Manage the active environment", + hidden: true, + }, locale: { handler: locale, description: "Manage locales", diff --git a/src/commands/locale-add.ts b/src/commands/locale-add.ts index 476b82d..cc3ff4f 100644 --- a/src/commands/locale-add.ts +++ b/src/commands/locale-add.ts @@ -1,8 +1,7 @@ +import { getActiveRepositoryName } from "../adapters"; import { getCredentials } from "../auth"; import { createCommand, type CommandConfig } from "../lib/command"; import { upsertLocale } from "../lib/prismic/clients/locale"; -import { resolveEnvironment } from "../lib/prismic/environments"; -import { getRepositoryName } from "../project"; const config = { name: "prismic locale add", @@ -18,17 +17,21 @@ const config = { options: { master: { type: "boolean", description: "Set as the master locale" }, name: { type: "string", short: "n", description: "Custom display name (for custom locales)" }, - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, } satisfies CommandConfig; export default createCommand(config, async ({ positionals, values }) => { const [code] = positionals; - const { repo: parentRepo = await getRepositoryName(), env, master = false, name } = values; + const { env, repo = env ?? (await getActiveRepositoryName()), master = false, name } = values; const { token, host } = await getCredentials(); - const repo = env ? await resolveEnvironment(env, { repo: parentRepo, token, host }) : parentRepo; await upsertLocale({ id: code, isMaster: master, customName: name }, { repo, token, host }); diff --git a/src/commands/locale-list.ts b/src/commands/locale-list.ts index a2305f7..14bf8cb 100644 --- a/src/commands/locale-list.ts +++ b/src/commands/locale-list.ts @@ -1,10 +1,9 @@ +import { getActiveRepositoryName } from "../adapters"; import { getCredentials } from "../auth"; import { createCommand, type CommandConfig } from "../lib/command"; import { stringify } from "../lib/json"; import { getLocales } from "../lib/prismic/clients/locale"; -import { resolveEnvironment } from "../lib/prismic/environments"; import { formatTable } from "../lib/string"; -import { getRepositoryName } from "../project"; const config = { name: "prismic locale list", @@ -16,16 +15,20 @@ const config = { `, options: { json: { type: "boolean", description: "Output as JSON" }, - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, } satisfies CommandConfig; export default createCommand(config, async ({ values }) => { - const { repo: parentRepo = await getRepositoryName(), env, json } = values; + const { env, repo = env ?? (await getActiveRepositoryName()), json } = values; const { token, host } = await getCredentials(); - const repo = env ? await resolveEnvironment(env, { repo: parentRepo, token, host }) : parentRepo; const locales = await getLocales({ repo, token, host }); diff --git a/src/commands/locale-remove.ts b/src/commands/locale-remove.ts index 99b53ef..66da2df 100644 --- a/src/commands/locale-remove.ts +++ b/src/commands/locale-remove.ts @@ -1,8 +1,7 @@ +import { getActiveRepositoryName } from "../adapters"; import { getCredentials } from "../auth"; import { createCommand, type CommandConfig } from "../lib/command"; import { removeLocale } from "../lib/prismic/clients/locale"; -import { resolveEnvironment } from "../lib/prismic/environments"; -import { getRepositoryName } from "../project"; const config = { name: "prismic locale remove", @@ -16,17 +15,21 @@ const config = { code: { description: "Locale code (e.g. en-us, fr-fr)", required: true }, }, options: { - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, } satisfies CommandConfig; export default createCommand(config, async ({ positionals, values }) => { const [code] = positionals; - const { repo: parentRepo = await getRepositoryName(), env } = values; + const { env, repo = env ?? (await getActiveRepositoryName()) } = values; const { token, host } = await getCredentials(); - const repo = env ? await resolveEnvironment(env, { repo: parentRepo, token, host }) : parentRepo; await removeLocale(code, { repo, token, host }); diff --git a/src/commands/locale-set-master.ts b/src/commands/locale-set-master.ts index 9bcf607..029c199 100644 --- a/src/commands/locale-set-master.ts +++ b/src/commands/locale-set-master.ts @@ -1,8 +1,7 @@ +import { getActiveRepositoryName } from "../adapters"; import { getCredentials } from "../auth"; import { CommandError, createCommand, type CommandConfig } from "../lib/command"; import { getLocales, upsertLocale } from "../lib/prismic/clients/locale"; -import { resolveEnvironment } from "../lib/prismic/environments"; -import { getRepositoryName } from "../project"; const config = { name: "prismic locale set-master", @@ -16,17 +15,21 @@ const config = { code: { description: "Locale code (e.g. en-us, fr-fr)", required: true }, }, options: { - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, } satisfies CommandConfig; export default createCommand(config, async ({ positionals, values }) => { const [code] = positionals; - const { repo: parentRepo = await getRepositoryName(), env } = values; + const { env, repo = env ?? (await getActiveRepositoryName()) } = values; const { token, host } = await getCredentials(); - const repo = env ? await resolveEnvironment(env, { repo: parentRepo, token, host }) : parentRepo; const locales = await getLocales({ repo, token, host }); diff --git a/src/commands/preview-add.ts b/src/commands/preview-add.ts index c9dfcc1..da63997 100644 --- a/src/commands/preview-add.ts +++ b/src/commands/preview-add.ts @@ -1,8 +1,7 @@ +import { getActiveRepositoryName } from "../adapters"; import { getCredentials } from "../auth"; import { CommandError, createCommand, type CommandConfig } from "../lib/command"; import { addPreview } from "../lib/prismic/clients/core"; -import { resolveEnvironment } from "../lib/prismic/environments"; -import { getRepositoryName } from "../project"; const config = { name: "prismic preview add", @@ -17,14 +16,19 @@ const config = { }, options: { name: { type: "string", short: "n", description: "Display name (defaults to hostname)" }, - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, } satisfies CommandConfig; export default createCommand(config, async ({ positionals, values }) => { const [previewUrl] = positionals; - const { repo: parentRepo = await getRepositoryName(), env, name } = values; + const { env, repo = env ?? (await getActiveRepositoryName()), name } = values; let parsed: URL; try { @@ -38,7 +42,6 @@ export default createCommand(config, async ({ positionals, values }) => { const resolverPath = parsed.pathname === "/" ? undefined : parsed.pathname; const { token, host } = await getCredentials(); - const repo = env ? await resolveEnvironment(env, { repo: parentRepo, token, host }) : parentRepo; await addPreview({ name: displayName, websiteURL, resolverPath }, { repo, token, host }); diff --git a/src/commands/preview-list.ts b/src/commands/preview-list.ts index d6fd905..fba5117 100644 --- a/src/commands/preview-list.ts +++ b/src/commands/preview-list.ts @@ -1,10 +1,9 @@ +import { getActiveRepositoryName } from "../adapters"; import { getCredentials } from "../auth"; import { createCommand, type CommandConfig } from "../lib/command"; import { stringify } from "../lib/json"; import { getPreviews, getSimulatorUrl } from "../lib/prismic/clients/core"; -import { resolveEnvironment } from "../lib/prismic/environments"; import { formatTable } from "../lib/string"; -import { getRepositoryName } from "../project"; const config = { name: "prismic preview list", @@ -16,16 +15,20 @@ const config = { `, options: { json: { type: "boolean", description: "Output as JSON" }, - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, } satisfies CommandConfig; export default createCommand(config, async ({ values }) => { - const { repo: parentRepo = await getRepositoryName(), env, json } = values; + const { env, repo = env ?? (await getActiveRepositoryName()), json } = values; const { token, host } = await getCredentials(); - const repo = env ? await resolveEnvironment(env, { repo: parentRepo, token, host }) : parentRepo; const [previews, simulatorUrl] = await Promise.all([ getPreviews({ repo, token, host }), diff --git a/src/commands/preview-remove.ts b/src/commands/preview-remove.ts index 676eb0e..93ef5b9 100644 --- a/src/commands/preview-remove.ts +++ b/src/commands/preview-remove.ts @@ -1,8 +1,7 @@ +import { getActiveRepositoryName } from "../adapters"; import { getCredentials } from "../auth"; import { CommandError, createCommand, type CommandConfig } from "../lib/command"; import { getPreviews, removePreview } from "../lib/prismic/clients/core"; -import { resolveEnvironment } from "../lib/prismic/environments"; -import { getRepositoryName } from "../project"; const config = { name: "prismic preview remove", @@ -16,17 +15,21 @@ const config = { url: { description: "Preview URL to remove", required: true }, }, options: { - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, } satisfies CommandConfig; export default createCommand(config, async ({ positionals, values }) => { const [previewUrl] = positionals; - const { repo: parentRepo = await getRepositoryName(), env } = values; + const { env, repo = env ?? (await getActiveRepositoryName()) } = values; const { token, host } = await getCredentials(); - const repo = env ? await resolveEnvironment(env, { repo: parentRepo, token, host }) : parentRepo; const previews = await getPreviews({ repo, token, host }); diff --git a/src/commands/preview-set-simulator.ts b/src/commands/preview-set-simulator.ts index afeab58..b5f1843 100644 --- a/src/commands/preview-set-simulator.ts +++ b/src/commands/preview-set-simulator.ts @@ -1,8 +1,7 @@ +import { getActiveRepositoryName } from "../adapters"; import { getCredentials } from "../auth"; import { CommandError, createCommand, type CommandConfig } from "../lib/command"; import { setSimulatorUrl } from "../lib/prismic/clients/core"; -import { resolveEnvironment } from "../lib/prismic/environments"; -import { getRepositoryName } from "../project"; const config = { name: "prismic preview set-simulator", @@ -22,14 +21,19 @@ const config = { }, }, options: { - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, } satisfies CommandConfig; export default createCommand(config, async ({ positionals, values }) => { const [urlArg] = positionals; - const { repo: parentRepo = await getRepositoryName(), env } = values; + const { env, repo = env ?? (await getActiveRepositoryName()) } = values; let parsed: URL; try { @@ -44,7 +48,6 @@ export default createCommand(config, async ({ positionals, values }) => { const simulatorUrl = parsed.toString(); const { token, host } = await getCredentials(); - const repo = env ? await resolveEnvironment(env, { repo: parentRepo, token, host }) : parentRepo; await setSimulatorUrl(simulatorUrl, { repo, token, host }); diff --git a/src/commands/pull.ts b/src/commands/pull.ts index 59c07ef..4afad55 100644 --- a/src/commands/pull.ts +++ b/src/commands/pull.ts @@ -5,7 +5,6 @@ import { diffArrays } from "../lib/diff"; import { getDirtyPaths, getGitRoot } from "../lib/git"; import { getCustomTypes, getSlices } from "../lib/prismic/clients/custom-types"; import { completeOnboardingStepsSilently } from "../lib/prismic/clients/repository"; -import { resolveEnvironment } from "../lib/prismic/environments"; import { canonicalizeModel } from "../lib/prismic/models"; import { isDescendant, relativePathname } from "../lib/url"; import { findProjectRoot, getRepositoryName } from "../project"; @@ -20,21 +19,29 @@ const config = { `, options: { force: { type: "boolean", short: "f", description: "Overwrite local changes" }, - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, } satisfies CommandConfig; export default createCommand(config, async ({ values }) => { - const { force = false, repo: parentRepo = await getRepositoryName(), env } = values; + const adapter = await getAdapter(); + + const { + force = false, + env, + repo = env ?? (await adapter.getEnvironment()) ?? (await getRepositoryName()), + } = values; const { token, host } = await getCredentials(); - const adapter = await getAdapter(); const projectRoot = await findProjectRoot(); - const repo = env ? await resolveEnvironment(env, { repo: parentRepo, token, host }) : parentRepo; - - console.info(`Pulling from repository: ${parentRepo}${env ? ` (env: ${env})` : ""}`); + console.info(`Pulling from repository: ${repo}`); const [gitRoot, customTypeLibraries, sliceLibraries] = await Promise.all([ getGitRoot(projectRoot), @@ -139,7 +146,7 @@ export default createCommand(config, async ({ values }) => { await adapter.generateTypes(); await completeOnboardingStepsSilently({ - repo: parentRepo, + repo: await getRepositoryName(), token, host, stepIds: ["connectPrismic"], diff --git a/src/commands/push.ts b/src/commands/push.ts index 6a1a0bd..0036968 100644 --- a/src/commands/push.ts +++ b/src/commands/push.ts @@ -23,7 +23,6 @@ import { completeOnboardingStepsSilently, type OnboardingStep, } from "../lib/prismic/clients/repository"; -import { resolveEnvironment } from "../lib/prismic/environments"; import { canonicalizeModel } from "../lib/prismic/models"; import { BadRequestError } from "../lib/request"; import { appendTrailingSlash, isDescendant, relativePathname } from "../lib/url"; @@ -39,21 +38,29 @@ const config = { `, options: { force: { type: "boolean", short: "f", description: "Skip safety checks" }, - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, } satisfies CommandConfig; export default createCommand(config, async ({ values }) => { - const { force = false, repo: parentRepo = await getRepositoryName(), env } = values; + const adapter = await getAdapter(); + + const { + force = false, + env, + repo = env ?? (await adapter.getEnvironment()) ?? (await getRepositoryName()), + } = values; const { token, host } = await getCredentials(); - const adapter = await getAdapter(); const projectRoot = await findProjectRoot(); - const repo = env ? await resolveEnvironment(env, { repo: parentRepo, token, host }) : parentRepo; - - console.info(`Pushing to repository: ${parentRepo}${env ? ` (env: ${env})` : ""}`); + console.info(`Pushing to repository: ${repo}`); const [gitRoot, customTypeLibraries, sliceLibraries] = await Promise.all([ getGitRoot(projectRoot), @@ -166,7 +173,7 @@ export default createCommand(config, async ({ values }) => { } if (onboardingSteps.length > 0) { await completeOnboardingStepsSilently({ - repo: parentRepo, + repo: await getRepositoryName(), token, host, stepIds: onboardingSteps, diff --git a/src/commands/status.ts b/src/commands/status.ts index 8e23d36..9625b54 100644 --- a/src/commands/status.ts +++ b/src/commands/status.ts @@ -7,7 +7,6 @@ import { diffArrays, type ArrayDiff } from "../lib/diff"; import { getDirtyPaths, getGitRoot } from "../lib/git"; import { getCustomTypes, getSlices } from "../lib/prismic/clients/custom-types"; import { getProfile } from "../lib/prismic/clients/user"; -import { resolveEnvironment } from "../lib/prismic/environments"; import { canonicalizeModel } from "../lib/prismic/models"; import { dedent } from "../lib/string"; import { isDescendant, relativePathname } from "../lib/url"; @@ -22,16 +21,24 @@ const config = { model files with uncommitted git changes that would block pull and push. `, options: { - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, } satisfies CommandConfig; export default createCommand(config, async ({ values }) => { - const { repo: parentRepo = await getRepositoryName(), env } = values; + const adapter = await getAdapter(); + + const repositoryName = await getRepositoryName(); + const activeEnvironment = await adapter.getEnvironment(); + const { env, repo = env ?? activeEnvironment ?? repositoryName } = values; const { token, host } = await getCredentials(); - const adapter = await getAdapter(); const projectRoot = await findProjectRoot(); const [gitRoot, customTypeLibraries, sliceLibraries, localCustomTypesMeta, localSlicesMeta] = @@ -43,14 +50,10 @@ export default createCommand(config, async ({ values }) => { adapter.getSlices(), ]); - let repo = parentRepo; let userEmail: string | undefined; let customTypeOps: ArrayDiff | undefined; let sliceOps: ArrayDiff | undefined; if (token) { - if (env) { - repo = await resolveEnvironment(env, { repo: parentRepo, token, host }); - } const [profile, remoteCustomTypes, remoteSlices] = await Promise.all([ getProfile({ token, host }), getCustomTypes({ repo, token, host }), @@ -91,9 +94,9 @@ export default createCommand(config, async ({ values }) => { .map((path) => relativePathname(projectRoot, path)); } - console.info(`Repository: ${parentRepo}`); - if (env) { - console.info(`Environment: ${env}`); + console.info(`Repository: ${repositoryName}`); + if (repo !== repositoryName) { + console.info(`Environment: ${repo}`); } if (userEmail) { console.info(`Authenticated as: ${userEmail}`); diff --git a/src/commands/sync.ts b/src/commands/sync.ts index 7251b96..20c79c4 100644 --- a/src/commands/sync.ts +++ b/src/commands/sync.ts @@ -9,7 +9,6 @@ import { createCommand, type CommandConfig, CommandError } from "../lib/command" import { diffArrays } from "../lib/diff"; import { getCustomTypes, getSlices } from "../lib/prismic/clients/custom-types"; import { completeOnboardingStepsSilently } from "../lib/prismic/clients/repository"; -import { resolveEnvironment } from "../lib/prismic/environments"; import { getRepositoryName } from "../project"; import { trackCommandStart, trackCommandEnd } from "../tracking"; @@ -30,20 +29,25 @@ const config = { description: "Watch for changes and sync continuously", required: true, }, - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, } satisfies CommandConfig; export default createCommand(config, async ({ values }) => { - const { repo: parentRepo = await getRepositoryName(), env: envFlag } = values; - - const { token, host } = await getCredentials(); const adapter = await getAdapter(); - const repo = envFlag - ? await resolveEnvironment(envFlag, { repo: parentRepo, token, host }) - : parentRepo; + const { + env: envFlag, + repo = envFlag ?? (await adapter.getEnvironment()) ?? (await getRepositoryName()), + } = values; + + const { token, host } = await getCredentials(); trackCommandStart("sync", { watch: true }); process.on("SIGINT", () => { @@ -53,7 +57,7 @@ export default createCommand(config, async ({ values }) => { }); console.info( - `Watching repository: ${parentRepo}${envFlag ? ` (env: ${envFlag})` : ""} (polling every ${POLL_INTERVAL_MS / 1000}s, Ctrl+C to stop)`, + `Watching repository: ${repo} (polling every ${POLL_INTERVAL_MS / 1000}s, Ctrl+C to stop)`, ); let lastHash = ""; @@ -118,7 +122,7 @@ export default createCommand(config, async ({ values }) => { if (isInitial) { await completeOnboardingStepsSilently({ - repo: parentRepo, + repo: await getRepositoryName(), token, host, stepIds: ["connectPrismic"], diff --git a/src/commands/token-create.ts b/src/commands/token-create.ts index 35abb8b..8fdb013 100644 --- a/src/commands/token-create.ts +++ b/src/commands/token-create.ts @@ -1,3 +1,4 @@ +import { getActiveRepositoryName } from "../adapters"; import { getCredentials } from "../auth"; import { CommandError, createCommand, type CommandConfig } from "../lib/command"; import { stringify } from "../lib/json"; @@ -7,8 +8,6 @@ import { createWriteToken, getOAuthApps, } from "../lib/prismic/clients/wroom"; -import { resolveEnvironment } from "../lib/prismic/environments"; -import { getRepositoryName } from "../project"; const CLI_APP_NAME = "Prismic CLI"; @@ -32,15 +31,20 @@ const config = { description: `Name to identify the token (default: "${CLI_APP_NAME}")`, }, json: { type: "boolean", description: "Output as JSON" }, - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, } satisfies CommandConfig; export default createCommand(config, async ({ values }) => { const { - repo: parentRepo = await getRepositoryName(), env, + repo = env ?? (await getActiveRepositoryName()), write, "allow-releases": allowReleases, name = CLI_APP_NAME, @@ -52,7 +56,6 @@ export default createCommand(config, async ({ values }) => { } const { token, host } = await getCredentials(); - const repo = env ? await resolveEnvironment(env, { repo: parentRepo, token, host }) : parentRepo; let createdToken: string; let scope: string | undefined; diff --git a/src/commands/token-delete.ts b/src/commands/token-delete.ts index ac81fb9..d0afd93 100644 --- a/src/commands/token-delete.ts +++ b/src/commands/token-delete.ts @@ -1,3 +1,4 @@ +import { getActiveRepositoryName } from "../adapters"; import { getCredentials } from "../auth"; import { CommandError, createCommand, type CommandConfig } from "../lib/command"; import { @@ -6,8 +7,6 @@ import { getOAuthApps, getWriteTokens, } from "../lib/prismic/clients/wroom"; -import { resolveEnvironment } from "../lib/prismic/environments"; -import { getRepositoryName } from "../project"; const config = { name: "prismic token delete", @@ -21,17 +20,21 @@ const config = { token: { description: "Token value", required: true }, }, options: { - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, } satisfies CommandConfig; export default createCommand(config, async ({ positionals, values }) => { const [tokenValue] = positionals; - const { repo: parentRepo = await getRepositoryName(), env } = values; + const { env, repo = env ?? (await getActiveRepositoryName()) } = values; const { token, host } = await getCredentials(); - const repo = env ? await resolveEnvironment(env, { repo: parentRepo, token, host }) : parentRepo; const [apps, writeTokensInfo] = await Promise.all([ getOAuthApps({ repo, token, host }), diff --git a/src/commands/token-list.ts b/src/commands/token-list.ts index 189bf89..58208b5 100644 --- a/src/commands/token-list.ts +++ b/src/commands/token-list.ts @@ -1,10 +1,9 @@ +import { getActiveRepositoryName } from "../adapters"; import { getCredentials } from "../auth"; import { createCommand, type CommandConfig } from "../lib/command"; import { stringify } from "../lib/json"; import { getOAuthApps, getWriteTokens } from "../lib/prismic/clients/wroom"; -import { resolveEnvironment } from "../lib/prismic/environments"; import { formatTable } from "../lib/string"; -import { getRepositoryName } from "../project"; const config = { name: "prismic token list", @@ -16,16 +15,20 @@ const config = { `, options: { json: { type: "boolean", description: "Output as JSON" }, - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, } satisfies CommandConfig; export default createCommand(config, async ({ values }) => { - const { repo: parentRepo = await getRepositoryName(), env, json } = values; + const { env, repo = env ?? (await getActiveRepositoryName()), json } = values; const { token, host } = await getCredentials(); - const repo = env ? await resolveEnvironment(env, { repo: parentRepo, token, host }) : parentRepo; const [apps, writeTokensInfo] = await Promise.all([ getOAuthApps({ repo, token, host }), diff --git a/src/commands/webhook-create.ts b/src/commands/webhook-create.ts index f1c2b3d..6f1d282 100644 --- a/src/commands/webhook-create.ts +++ b/src/commands/webhook-create.ts @@ -1,8 +1,7 @@ +import { getActiveRepositoryName } from "../adapters"; import { getCredentials } from "../auth"; import { CommandError, createCommand, type CommandConfig } from "../lib/command"; import { createWebhook, WEBHOOK_TRIGGERS } from "../lib/prismic/clients/wroom"; -import { resolveEnvironment } from "../lib/prismic/environments"; -import { getRepositoryName } from "../project"; const config = { name: "prismic webhook create", @@ -24,8 +23,13 @@ const config = { short: "t", description: "Trigger events (can be repeated)", }, - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, sections: { TRIGGERS: ` @@ -53,7 +57,13 @@ const config = { export default createCommand(config, async ({ positionals, values }) => { const [webhookUrl] = positionals; - const { repo: parentRepo = await getRepositoryName(), env, name, secret, trigger = [] } = values; + const { + env, + repo = env ?? (await getActiveRepositoryName()), + name, + secret, + trigger = [], + } = values; // Validate triggers for (const t of trigger) { @@ -65,7 +75,6 @@ export default createCommand(config, async ({ positionals, values }) => { } const { token, host } = await getCredentials(); - const repo = env ? await resolveEnvironment(env, { repo: parentRepo, token, host }) : parentRepo; const defaultValue = trigger.length > 0 ? false : true; diff --git a/src/commands/webhook-disable.ts b/src/commands/webhook-disable.ts index c546df9..dafd85c 100644 --- a/src/commands/webhook-disable.ts +++ b/src/commands/webhook-disable.ts @@ -1,8 +1,7 @@ +import { getActiveRepositoryName } from "../adapters"; import { getCredentials } from "../auth"; import { CommandError, createCommand, type CommandConfig } from "../lib/command"; import { getWebhooks, updateWebhook } from "../lib/prismic/clients/wroom"; -import { resolveEnvironment } from "../lib/prismic/environments"; -import { getRepositoryName } from "../project"; const config = { name: "prismic webhook disable", @@ -16,17 +15,21 @@ const config = { url: { description: "Webhook URL", required: true }, }, options: { - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, } satisfies CommandConfig; export default createCommand(config, async ({ positionals, values }) => { const [webhookUrl] = positionals; - const { repo: parentRepo = await getRepositoryName(), env } = values; + const { env, repo = env ?? (await getActiveRepositoryName()) } = values; const { token, host } = await getCredentials(); - const repo = env ? await resolveEnvironment(env, { repo: parentRepo, token, host }) : parentRepo; const webhooks = await getWebhooks({ repo, token, host }); const webhook = webhooks.find((w) => w.config.url === webhookUrl); diff --git a/src/commands/webhook-enable.ts b/src/commands/webhook-enable.ts index 0943836..7ad3c43 100644 --- a/src/commands/webhook-enable.ts +++ b/src/commands/webhook-enable.ts @@ -1,8 +1,7 @@ +import { getActiveRepositoryName } from "../adapters"; import { getCredentials } from "../auth"; import { CommandError, createCommand, type CommandConfig } from "../lib/command"; import { getWebhooks, updateWebhook } from "../lib/prismic/clients/wroom"; -import { resolveEnvironment } from "../lib/prismic/environments"; -import { getRepositoryName } from "../project"; const config = { name: "prismic webhook enable", @@ -16,17 +15,21 @@ const config = { url: { description: "Webhook URL", required: true }, }, options: { - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, } satisfies CommandConfig; export default createCommand(config, async ({ positionals, values }) => { const [webhookUrl] = positionals; - const { repo: parentRepo = await getRepositoryName(), env } = values; + const { env, repo = env ?? (await getActiveRepositoryName()) } = values; const { token, host } = await getCredentials(); - const repo = env ? await resolveEnvironment(env, { repo: parentRepo, token, host }) : parentRepo; const webhooks = await getWebhooks({ repo, token, host }); const webhook = webhooks.find((w) => w.config.url === webhookUrl); diff --git a/src/commands/webhook-list.ts b/src/commands/webhook-list.ts index 980e1df..8db16fd 100644 --- a/src/commands/webhook-list.ts +++ b/src/commands/webhook-list.ts @@ -1,10 +1,9 @@ +import { getActiveRepositoryName } from "../adapters"; import { getCredentials } from "../auth"; import { createCommand, type CommandConfig } from "../lib/command"; import { stringify } from "../lib/json"; import { getWebhooks } from "../lib/prismic/clients/wroom"; -import { resolveEnvironment } from "../lib/prismic/environments"; import { formatTable } from "../lib/string"; -import { getRepositoryName } from "../project"; const config = { name: "prismic webhook list", @@ -16,16 +15,20 @@ const config = { `, options: { json: { type: "boolean", description: "Output as JSON" }, - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, } satisfies CommandConfig; export default createCommand(config, async ({ values }) => { - const { repo: parentRepo = await getRepositoryName(), env, json } = values; + const { env, repo = env ?? (await getActiveRepositoryName()), json } = values; const { token, host } = await getCredentials(); - const repo = env ? await resolveEnvironment(env, { repo: parentRepo, token, host }) : parentRepo; const webhooks = await getWebhooks({ repo, token, host }); if (json) { diff --git a/src/commands/webhook-remove.ts b/src/commands/webhook-remove.ts index e27b632..06de4d7 100644 --- a/src/commands/webhook-remove.ts +++ b/src/commands/webhook-remove.ts @@ -1,8 +1,7 @@ +import { getActiveRepositoryName } from "../adapters"; import { getCredentials } from "../auth"; import { CommandError, createCommand, type CommandConfig } from "../lib/command"; import { deleteWebhook, getWebhooks } from "../lib/prismic/clients/wroom"; -import { resolveEnvironment } from "../lib/prismic/environments"; -import { getRepositoryName } from "../project"; const config = { name: "prismic webhook remove", @@ -16,17 +15,21 @@ const config = { url: { description: "Webhook URL", required: true }, }, options: { - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, } satisfies CommandConfig; export default createCommand(config, async ({ positionals, values }) => { const [webhookUrl] = positionals; - const { repo: parentRepo = await getRepositoryName(), env } = values; + const { env, repo = env ?? (await getActiveRepositoryName()) } = values; const { token, host } = await getCredentials(); - const repo = env ? await resolveEnvironment(env, { repo: parentRepo, token, host }) : parentRepo; const webhooks = await getWebhooks({ repo, token, host }); const webhook = webhooks.find((w) => w.config.url === webhookUrl); diff --git a/src/commands/webhook-set-triggers.ts b/src/commands/webhook-set-triggers.ts index ea26d44..a9f3ab3 100644 --- a/src/commands/webhook-set-triggers.ts +++ b/src/commands/webhook-set-triggers.ts @@ -1,8 +1,7 @@ +import { getActiveRepositoryName } from "../adapters"; import { getCredentials } from "../auth"; import { CommandError, createCommand, type CommandConfig } from "../lib/command"; import { getWebhooks, updateWebhook, WEBHOOK_TRIGGERS } from "../lib/prismic/clients/wroom"; -import { resolveEnvironment } from "../lib/prismic/environments"; -import { getRepositoryName } from "../project"; const config = { name: "prismic webhook set-triggers", @@ -23,8 +22,13 @@ const config = { description: "Trigger events (can be repeated)", required: true, }, - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, sections: { TRIGGERS: ` @@ -40,7 +44,7 @@ const config = { export default createCommand(config, async ({ positionals, values }) => { const [webhookUrl] = positionals; - const { repo: parentRepo = await getRepositoryName(), env, trigger = [] } = values; + const { env, repo = env ?? (await getActiveRepositoryName()), trigger = [] } = values; // Validate triggers for (const t of trigger) { @@ -52,7 +56,6 @@ export default createCommand(config, async ({ positionals, values }) => { } const { token, host } = await getCredentials(); - const repo = env ? await resolveEnvironment(env, { repo: parentRepo, token, host }) : parentRepo; const webhooks = await getWebhooks({ repo, token, host }); const webhook = webhooks.find((w) => w.config.url === webhookUrl); diff --git a/src/commands/webhook-view.ts b/src/commands/webhook-view.ts index acd360d..b61431c 100644 --- a/src/commands/webhook-view.ts +++ b/src/commands/webhook-view.ts @@ -1,8 +1,7 @@ +import { getActiveRepositoryName } from "../adapters"; import { getCredentials } from "../auth"; import { CommandError, createCommand, type CommandConfig } from "../lib/command"; import { getWebhooks, WEBHOOK_TRIGGERS } from "../lib/prismic/clients/wroom"; -import { resolveEnvironment } from "../lib/prismic/environments"; -import { getRepositoryName } from "../project"; const config = { name: "prismic webhook view", @@ -16,17 +15,21 @@ const config = { url: { description: "Webhook URL", required: true }, }, options: { - repo: { type: "string", short: "r", description: "Repository domain" }, - env: { type: "string", short: "e", description: "Environment domain" }, + repo: { type: "string", short: "r", description: "Repository or environment domain" }, + env: { + type: "string", + short: "e", + description: "Alias for --repo", + deprecated: "Use `prismic env` or --repo instead.", + }, }, } satisfies CommandConfig; export default createCommand(config, async ({ positionals, values }) => { const [webhookUrl] = positionals; - const { repo: parentRepo = await getRepositoryName(), env } = values; + const { env, repo = env ?? (await getActiveRepositoryName()) } = values; const { token, host } = await getCredentials(); - const repo = env ? await resolveEnvironment(env, { repo: parentRepo, token, host }) : parentRepo; const webhooks = await getWebhooks({ repo, token, host }); const webhook = webhooks.find((webhook) => webhook.config.url === webhookUrl); diff --git a/src/lib/command.ts b/src/lib/command.ts index 3cd2d47..a6f8afd 100644 --- a/src/lib/command.ts +++ b/src/lib/command.ts @@ -15,6 +15,7 @@ export type CommandConfig = { description: string; required?: boolean; dependsOn?: string | string[]; + deprecated?: string; } >; }; @@ -84,6 +85,9 @@ export function createCommand( if (config.required && !(name in result.values)) { throw new CommandError(`Missing required option: --${name}`); } + if (config.deprecated && name in result.values) { + console.warn(`--${name} is deprecated. ${config.deprecated}`); + } if (config.dependsOn && name in result.values) { const deps = Array.isArray(config.dependsOn) ? config.dependsOn : [config.dependsOn]; for (const dep of deps) { @@ -133,6 +137,7 @@ function buildCommandHelp(config: CommandConfig): string { const optionNames = Object.keys(options); for (const optionName of optionNames) { const option = options[optionName]; + if (option.deprecated) continue; const shortPart = option.short ? `-${option.short}, ` : " "; const typeSuffix = option.type === "string" ? " string" : ""; const left = `${shortPart}--${optionName}${typeSuffix}`; @@ -169,7 +174,7 @@ type CreateCommandRouterConfig = { sections?: Record; commands: Record; }; -type RouterCommand = { handler: () => Promise; description: string }; +type RouterCommand = { handler: () => Promise; description: string; hidden?: boolean }; export function createCommandRouter(config: CreateCommandRouterConfig): () => Promise { const depth = config.name.split(" ").length; @@ -203,7 +208,7 @@ export function createCommandRouter(config: CreateCommandRouterConfig): () => Pr function buildRouterHelp(config: CreateCommandRouterConfig): string { const { name, description, sections, commands } = config; - const lines = [description]; + const lines = [dedent(description)]; lines.push(""); lines.push("USAGE"); @@ -211,7 +216,9 @@ function buildRouterHelp(config: CreateCommandRouterConfig): string { lines.push(""); lines.push("COMMANDS"); - const commandRows = Object.entries(commands).map(([name, cmd]) => [` ${name}`, cmd.description]); + const commandRows = Object.entries(commands) + .filter(([, cmd]) => !cmd.hidden) + .map(([name, cmd]) => [` ${name}`, cmd.description]); lines.push(formatTable(commandRows)); lines.push(""); diff --git a/src/lib/file.ts b/src/lib/file.ts index bf64473..009e1e8 100644 --- a/src/lib/file.ts +++ b/src/lib/file.ts @@ -1,5 +1,6 @@ import { access, mkdir, readFile, writeFile } from "node:fs/promises"; import { pathToFileURL } from "node:url"; +import { parseEnv } from "node:util"; import * as z from "zod/mini"; import { appendTrailingSlash, getExtension } from "./url"; @@ -99,3 +100,45 @@ export async function readURLFile(url: URL): Promise { throw new Error(`Unsupported file protocol: ${url.protocol}`); } + +export async function readEnvFile>>( + path: URL, + options: { schema?: z.ZodMiniType } = {}, +): Promise { + const { schema } = options; + const contents = await readFile(path, "utf8"); + const parsed = parseEnv(contents); + if (schema) return z.parse(schema, parsed); + return parsed as T; +} + +export async function setEnvFileVar(path: URL, key: string, value: string): Promise { + const hasFile = await exists(path); + let contents = ""; + if (hasFile) contents = await readFile(path, "utf8"); + + const pattern = new RegExp(`^${key}=.*$`, "mg"); + const line = `${key}=${value}`; + const hasEnvironmentVar = pattern.test(contents); + + if (hasEnvironmentVar) { + contents = contents.replace(pattern, line); + } else { + if (contents && !contents.endsWith("\n")) contents += "\n"; + contents += `${line}\n`; + } + + await writeFile(path, contents); +} + +export async function unsetEnvFileVar(path: URL, key: string): Promise { + const hasFile = await exists(path); + if (!hasFile) return; + + let contents = await readFile(path, "utf8"); + + const pattern = new RegExp(`^${key}=.*$\n?`, "mg"); + contents = contents.replace(pattern, ""); + + await writeFile(path, contents); +} diff --git a/src/lib/prismic/environments.ts b/src/lib/prismic/environments.ts index a0db3ee..d22728d 100644 --- a/src/lib/prismic/environments.ts +++ b/src/lib/prismic/environments.ts @@ -2,26 +2,22 @@ import { dedent } from "../string"; import { type Environment, getEnvironments } from "./clients/core"; import { getProfile } from "./clients/user"; -export async function resolveEnvironment( - env: string, - config: { repo: string; token: string | undefined; host: string }, -): Promise { +export async function getUserEnvironments(config: { + repo: string; + token: string | undefined; + host: string; +}): Promise { const { repo, token, host } = config; - const [profile, environments] = await Promise.all([ getProfile({ token, host }), getEnvironments({ repo, token, host }), ]); - - const availableEnvironments = environments.filter( + const userEnvironments = environments.filter( (environment) => (environment.kind === "prod" || environment.kind === "stage") && environment.users.some((user) => user.id === profile.shortId), ); - const match = availableEnvironments.find((environment) => environment.domain === env); - if (match) return match.domain; - - throw new InvalidEnvironmentError(env, availableEnvironments, repo); + return userEnvironments; } export class InvalidEnvironmentError extends Error { diff --git a/test/env-active.test.ts b/test/env-active.test.ts new file mode 100644 index 0000000..653de68 --- /dev/null +++ b/test/env-active.test.ts @@ -0,0 +1,24 @@ +import { writeFile } from "node:fs/promises"; + +import { it } from "./it"; + +it("supports --help", async ({ expect, prismic }) => { + const { stdout, exitCode } = await prismic("env", ["active", "--help"]); + expect(exitCode).toBe(0); + expect(stdout).toContain("prismic env active [options]"); +}); + +it("prints the active environment", async ({ expect, prismic, project }) => { + const path = new URL(".env.local", project); + await writeFile(path, "NEXT_PUBLIC_PRISMIC_ENVIRONMENT=my-repo-staging"); + + const { stdout, exitCode } = await prismic("env", ["active"]); + expect(exitCode).toBe(0); + expect(stdout).toContain("my-repo-staging"); +}); + +it("prints production when no environment is active", async ({ expect, prismic, repo }) => { + const { stdout, exitCode } = await prismic("env", ["active"]); + expect(exitCode).toBe(0); + expect(stdout).toContain(repo); +}); diff --git a/test/env-list.test.ts b/test/env-list.test.ts new file mode 100644 index 0000000..39d9cee --- /dev/null +++ b/test/env-list.test.ts @@ -0,0 +1,14 @@ +import { it } from "./it"; + +it("supports --help", async ({ expect, prismic }) => { + const { stdout, exitCode } = await prismic("env", ["list", "--help"]); + expect(exitCode).toBe(0); + expect(stdout).toContain("prismic env list [options]"); +}); + +it("lists environments including production", async ({ expect, prismic, repo }) => { + const { stdout, exitCode } = await prismic("env", ["list"]); + expect(exitCode).toBe(0); + expect(stdout).toContain(repo); + expect(stdout).toContain("prod"); +}); diff --git a/test/env-set.test.ts b/test/env-set.test.ts new file mode 100644 index 0000000..44bc232 --- /dev/null +++ b/test/env-set.test.ts @@ -0,0 +1,36 @@ +import { readFile, writeFile } from "node:fs/promises"; + +import { it } from "./it"; + +it("supports --help", async ({ expect, prismic }) => { + const { stdout, exitCode } = await prismic("env", ["set", "--help"]); + expect(exitCode).toBe(0); + expect(stdout).toContain("prismic env set [options]"); +}); + +// TODO: Test setting a non-production environment once the e2e setup can create +// one. It should write the environment's domain to .env.local as the active +// repository. The test repository only has production, so it can't be covered yet. + +it("clears the active environment when setting to production", async ({ + expect, + prismic, + project, + repo, +}) => { + const path = new URL(".env.local", project); + await writeFile(path, "NEXT_PUBLIC_PRISMIC_ENVIRONMENT=my-repo-staging"); + + const { stdout, exitCode } = await prismic("env", ["set", repo]); + expect(exitCode).toBe(0); + expect(stdout).toContain("production"); + + const after = await readFile(path, "utf8"); + expect(after).not.toContain("NEXT_PUBLIC_PRISMIC_ENVIRONMENT"); + expect(after).not.toContain("my-stage-env"); +}); + +it("rejects an unknown environment", async ({ expect, prismic }) => { + const { exitCode } = await prismic("env", ["set", "does-not-exist"]); + expect(exitCode).toBe(1); +}); diff --git a/test/env-unset.test.ts b/test/env-unset.test.ts new file mode 100644 index 0000000..dee9f9a --- /dev/null +++ b/test/env-unset.test.ts @@ -0,0 +1,22 @@ +import { readFile, writeFile } from "node:fs/promises"; + +import { it } from "./it"; + +it("supports --help", async ({ expect, prismic }) => { + const { stdout, exitCode } = await prismic("env", ["unset", "--help"]); + expect(exitCode).toBe(0); + expect(stdout).toContain("prismic env unset [options]"); +}); + +it("resets to the production environment", async ({ expect, prismic, repo, project }) => { + const path = new URL(".env.local", project); + await writeFile(path, "NEXT_PUBLIC_PRISMIC_ENVIRONMENT=my-repo-staging"); + + const { stdout, exitCode } = await prismic("env", ["unset"]); + expect(exitCode).toBe(0); + expect(stdout).toContain(`Reset to the production environment "${repo}".`); + + const after = await readFile(path, "utf8"); + expect(after).not.toContain("NEXT_PUBLIC_PRISMIC_ENVIRONMENT"); + expect(after).not.toContain("my-stage-env"); +}); diff --git a/test/env.test.ts b/test/env.test.ts new file mode 100644 index 0000000..1af8902 --- /dev/null +++ b/test/env.test.ts @@ -0,0 +1,13 @@ +import { it } from "./it"; + +it("prints help by default", async ({ expect, prismic }) => { + const { stdout, exitCode } = await prismic("env"); + expect(exitCode).toBe(0); + expect(stdout).toContain("prismic env [options]"); +}); + +it("supports --help", async ({ expect, prismic }) => { + const { stdout, exitCode } = await prismic("env", ["--help"]); + expect(exitCode).toBe(0); + expect(stdout).toContain("prismic env [options]"); +}); diff --git a/test/pull.test.ts b/test/pull.test.ts index a8d446b..a306739 100644 --- a/test/pull.test.ts +++ b/test/pull.test.ts @@ -20,12 +20,6 @@ it.sequential("supports --help", async ({ expect, prismic }) => { expect(stdout).toContain("prismic pull [options]"); }); -it.sequential("rejects an unknown --env", async ({ expect, prismic, repo }) => { - const { stderr, exitCode } = await prismic("pull", ["--repo", repo, "--env", "does-not-exist"]); - expect(exitCode).toBe(1); - expect(stderr).toContain(`No environments available on repository "${repo}".`); -}); - it.sequential("pulls slices and custom types from remote", async ({ expect, project, diff --git a/test/push.test.ts b/test/push.test.ts index 72893ea..44eac5c 100644 --- a/test/push.test.ts +++ b/test/push.test.ts @@ -16,12 +16,6 @@ it("supports --help", async ({ expect, prismic }) => { expect(stdout).toContain("prismic push [options]"); }); -it("rejects an unknown --env", async ({ expect, prismic, repo }) => { - const { stderr, exitCode } = await prismic("push", ["--repo", repo, "--env", "does-not-exist"]); - expect(exitCode).toBe(1); - expect(stderr).toContain(`No environments available on repository "${repo}".`); -}); - describe("with an isolated repository", () => { it.scoped({ isolateRepo: true }); diff --git a/test/status.test.ts b/test/status.test.ts index cf243f9..df551b7 100644 --- a/test/status.test.ts +++ b/test/status.test.ts @@ -9,20 +9,6 @@ it("supports --help", async ({ expect, prismic }) => { expect(stdout).toContain("prismic status [options]"); }); -it("rejects an unknown --env", async ({ expect, prismic, repo }) => { - const { stderr, exitCode } = await prismic("status", ["--repo", repo, "--env", "does-not-exist"]); - expect(exitCode).toBe(1); - expect(stderr).toContain(`No environments available on repository "${repo}".`); -}); - -it("warns and skips --env when not logged in", async ({ expect, prismic, logout, repo }) => { - await logout(); - const { stdout, stderr, exitCode } = await prismic("status", ["--repo", repo, "--env", "anything"]); - expect(exitCode, stderr).toBe(0); - expect(stdout).toContain(`Repository: ${repo}`); - expect(stdout).toContain("Environment: anything"); -}); - describe("with an isolated repository", () => { it.scoped({ isolateRepo: true });