Skip to content
38 changes: 36 additions & 2 deletions src/adapters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<string> {
const adapter = await getAdapter();
return (await adapter.getEnvironment()) ?? (await getRepositoryName());
}
Comment thread
angeloashmore marked this conversation as resolved.

export abstract class Adapter {
abstract readonly id: string;

abstract readonly environmentEnvVarName: string;

abstract onProjectInitialized(): Promise<void> | void;
abstract onSliceCreated(model: SharedSlice, library: URL): Promise<void> | void;
abstract onSliceUpdated(model: SharedSlice): Promise<void> | void;
Expand Down Expand Up @@ -205,4 +219,24 @@ export abstract class Adapter {
await writeFileRecursive(output, types);
return output;
}

async getEnvironment(): Promise<string | undefined> {
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;
}
Comment thread
cursor[bot] marked this conversation as resolved.

async setEnvironment(environment: string): Promise<void> {
const projectRoot = await findProjectRoot();
const envLocalPath = new URL(".env.local", projectRoot);
await setEnvFileVar(envLocalPath, this.environmentEnvVarName, environment);
}

async unsetEnvironment(): Promise<void> {
const projectRoot = await findProjectRoot();
const envLocalPath = new URL(".env.local", projectRoot);
await unsetEnvFileVar(envLocalPath, this.environmentEnvVarName);
}
}
6 changes: 4 additions & 2 deletions src/adapters/nextjs.templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}
`;
Expand All @@ -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}
`;
Expand Down
2 changes: 2 additions & 0 deletions src/adapters/nextjs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import {
export class NextJsAdapter extends Adapter {
readonly id = "next";

readonly environmentEnvVarName = "NEXT_PUBLIC_PRISMIC_ENVIRONMENT";

async setupProject(): Promise<void> {
await addDependencies({
"@prismicio/client": `^${await getNpmPackageVersion("@prismicio/client")}`,
Expand Down
2 changes: 2 additions & 0 deletions src/adapters/nuxt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ const NUXT_PRISMIC = "@nuxtjs/prismic";
export class NuxtAdapter extends Adapter {
readonly id = "nuxt";

readonly environmentEnvVarName = "NUXT_PUBLIC_PRISMIC_ENVIRONMENT";
Comment thread
angeloashmore marked this conversation as resolved.

async setupProject(): Promise<void> {
await addDependencies({
"@prismicio/client": `^${await getNpmPackageVersion("@prismicio/client")}`,
Expand Down
8 changes: 6 additions & 2 deletions src/adapters/sveltekit.templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/adapters/sveltekit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import {
export class SvelteKitAdapter extends Adapter {
readonly id = "sveltekit";

readonly environmentEnvVarName = "PUBLIC_PRISMIC_ENVIRONMENT";

async setupProject(): Promise<void> {
await addDependencies({
"@prismicio/client": `^${await getNpmPackageVersion("@prismicio/client")}`,
Expand Down
19 changes: 19 additions & 0 deletions src/commands/env-active.ts
Original file line number Diff line number Diff line change
@@ -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);
});
53 changes: 53 additions & 0 deletions src/commands/env-list.ts
Original file line number Diff line number Diff line change
@@ -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"] }));
});
39 changes: 39 additions & 0 deletions src/commands/env-set.ts
Original file line number Diff line number Diff line change
@@ -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}".`);
});
19 changes: 19 additions & 0 deletions src/commands/env-unset.ts
Original file line number Diff line number Diff line change
@@ -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}".`);
});
32 changes: 32 additions & 0 deletions src/commands/env.ts
Original file line number Diff line number Diff line change
@@ -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",
},
},
});
6 changes: 6 additions & 0 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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",
Expand Down
15 changes: 9 additions & 6 deletions src/commands/locale-add.ts
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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 });

Expand Down
Loading
Loading