Skip to content

Commit

Permalink
feat(adapter-next): add project:environment:read and `project:envir…
Browse files Browse the repository at this point in the history
…onment:update` hooks
  • Loading branch information
angeloashmore committed Oct 21, 2023
1 parent 642a460 commit 9886096
Show file tree
Hide file tree
Showing 9 changed files with 373 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/adapter-next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"@prismicio/types-internal": "^2.2.0",
"@slicemachine/plugin-kit": "workspace:^",
"common-tags": "^1.8.2",
"dotenv": "16.3.1",
"fp-ts": "^2.13.1",
"io-ts": "^2.2.20",
"io-ts-types": "^0.5.19",
Expand Down
12 changes: 12 additions & 0 deletions packages/adapter-next/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,15 @@
*/
export const NON_EDITABLE_FILE_BANNER =
"// Code generated by Slice Machine. DO NOT EDIT.";

/**
* The default file path at which environment variables will be stored.
*/
export const DEFAULT_ENVIRONMENT_VARIABLE_FILE_PATH = ".env.local";

/**
* The name of the environment variable that stores the active Prismic
* environment.
*/
export const PRISMIC_ENVIRONMENT_ENVIRONMENT_VARIABLE_NAME =
"NEXT_PUBLIC_PRISMIC_ENVIRONMENT";
50 changes: 50 additions & 0 deletions packages/adapter-next/src/hooks/project-environment-read.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { ProjectEnvironmentReadHook } from "@slicemachine/plugin-kit";
import {
checkHasProjectFile,
readProjectFile,
} from "@slicemachine/plugin-kit/fs";
import * as dotenv from "dotenv";

import type { PluginOptions } from "../types";
import {
DEFAULT_ENVIRONMENT_VARIABLE_FILE_PATH,
PRISMIC_ENVIRONMENT_ENVIRONMENT_VARIABLE_NAME,
} from "../constants";

export const projectEnvironmentRead: ProjectEnvironmentReadHook<
PluginOptions
> = async (_data, { options, helpers }) => {
const environmentVariableFilePath =
options.environmentVariableFilePath ||
DEFAULT_ENVIRONMENT_VARIABLE_FILE_PATH;

const hasEnvironmentVariableFile = await checkHasProjectFile({
filename: environmentVariableFilePath,
helpers,
});

if (!hasEnvironmentVariableFile) {
return {
environment: undefined,
};
}

const contents = await readProjectFile({
filename: environmentVariableFilePath,
helpers,
});

const vars = dotenv.parse(contents);

const environment = vars[PRISMIC_ENVIRONMENT_ENVIRONMENT_VARIABLE_NAME];

if (environment) {
return {
environment,
};
}

return {
environment: undefined,
};
};
124 changes: 124 additions & 0 deletions packages/adapter-next/src/hooks/project-environment-update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import type { ProjectEnvironmentUpdateHook } from "@slicemachine/plugin-kit";
import {
checkHasProjectFile,
readProjectFile,
writeProjectFile,
} from "@slicemachine/plugin-kit/fs";

import type { PluginOptions } from "../types";
import {
DEFAULT_ENVIRONMENT_VARIABLE_FILE_PATH,
PRISMIC_ENVIRONMENT_ENVIRONMENT_VARIABLE_NAME,
} from "../constants";

type BuildVariableLineArgs = {
environment: string;
};

const buildVariableLine = (args: BuildVariableLineArgs): string => {
return `${PRISMIC_ENVIRONMENT_ENVIRONMENT_VARIABLE_NAME}=${args.environment}`;
};

type AppendEnvironmentVariableArgs = {
contents: string;
environment: string;
};

const appendEnvironmentVariable = (
args: AppendEnvironmentVariableArgs,
): string => {
let res = args.contents.toString();

if (!res.endsWith("\n")) {
res += "\n";
}

res += buildVariableLine({ environment: args.environment }) + "\n";

return res;
};

type UpdateEnvironmentVariableArgs = {
contents: string;
environment: string;
};

const updateEnvironmentVariable = (
args: UpdateEnvironmentVariableArgs,
): string => {
return args.contents.replace(
new RegExp(`^${PRISMIC_ENVIRONMENT_ENVIRONMENT_VARIABLE_NAME}=.*$\n?`),
`${PRISMIC_ENVIRONMENT_ENVIRONMENT_VARIABLE_NAME}=${args.environment}`,
);
};

type RemoveEnvironmentVariableArgs = {
contents: string;
};

const removeEnvironmentVariable = (
args: RemoveEnvironmentVariableArgs,
): string => {
return args.contents.replace(
new RegExp(`^${PRISMIC_ENVIRONMENT_ENVIRONMENT_VARIABLE_NAME}=.*$\n?`),
"",
);
};

export const projectEnvironmentUpdate: ProjectEnvironmentUpdateHook<
PluginOptions
> = async ({ environment }, { options, helpers }) => {
const environmentVariableFilePath =
options.environmentVariableFilePath ||
DEFAULT_ENVIRONMENT_VARIABLE_FILE_PATH;

const variableRegExp = new RegExp(
`^${PRISMIC_ENVIRONMENT_ENVIRONMENT_VARIABLE_NAME}=.*$`,
"m",
);

const hasEnvironmentVariableFile = await checkHasProjectFile({
filename: environmentVariableFilePath,
helpers,
});

let contents;

if (hasEnvironmentVariableFile) {
const existingContents = await readProjectFile({
filename: environmentVariableFilePath,
helpers,
encoding: "utf8",
});

const hasExistingVariable = variableRegExp.test(existingContents);

if (environment === undefined) {
contents = removeEnvironmentVariable({ contents: existingContents });
} else if (hasExistingVariable) {
contents = updateEnvironmentVariable({
contents: existingContents,
environment,
});
} else {
contents = appendEnvironmentVariable({
contents: existingContents,
environment,
});
}
} else {
if (environment === undefined) {
// noop

return;
}

contents = appendEnvironmentVariable({ contents: "", environment });
}

await writeProjectFile({
filename: environmentVariableFilePath,
contents: contents,
helpers,
});
};
4 changes: 4 additions & 0 deletions packages/adapter-next/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import { PluginOptions } from "./types";

import { documentationRead } from "./hooks/documentation-read";
import { projectInit } from "./hooks/project-init";
import { projectEnvironmentRead } from "./hooks/project-environment-read";
import { projectEnvironmentUpdate } from "./hooks/project-environment-update";
import { sliceCreate } from "./hooks/slice-create";
import { sliceSimulatorSetupRead } from "./hooks/sliceSimulator-setup-read";
import { snippetRead } from "./hooks/snippet-read";
Expand All @@ -55,6 +57,8 @@ export const plugin = defineSliceMachinePlugin<PluginOptions>({
////////////////////////////////////////////////////////////////

hook("project:init", projectInit);
hook("project:environment:read", projectEnvironmentRead);
hook("project:environment:update", projectEnvironmentUpdate);

////////////////////////////////////////////////////////////////
// slice:*
Expand Down
8 changes: 8 additions & 0 deletions packages/adapter-next/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ export type PluginOptions = {
* @defaultValue `prismicio-types.d.ts`
*/
generatedTypesFilePath?: string;

/**
* The filepath at which the active Prismic environment is stored as an
* environment variable.
*
* @defaultValue `.env.local`
*/
environmentVariableFilePath?: string;
} & (
| {
/**
Expand Down
63 changes: 63 additions & 0 deletions packages/adapter-next/test/plugin-project-environment-read.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { expect, test } from "vitest";
import { createSliceMachinePluginRunner } from "@slicemachine/plugin-kit";
import * as fs from "node:fs/promises";
import * as path from "node:path";

import adapter from "../src";

test("returns the active environment", async (ctx) => {
await fs.writeFile(
path.join(ctx.project.root, ".env.local"),
"NEXT_PUBLIC_PRISMIC_ENVIRONMENT=foo",
);

const res = await ctx.pluginRunner.callHook(
"project:environment:read",
undefined,
);

expect(res.data[0].environment).toBe("foo");
});

test("reads from the configured file path", async (ctx) => {
ctx.project.config.adapter.options.environmentVariableFilePath = ".bar";
const pluginRunner = createSliceMachinePluginRunner({
project: ctx.project,
nativePlugins: {
[ctx.project.config.adapter.resolve]: adapter,
},
});
await pluginRunner.init();

await fs.writeFile(
path.join(ctx.project.root, ".bar"),
"NEXT_PUBLIC_PRISMIC_ENVIRONMENT=foo",
);

const res = await pluginRunner.callHook(
"project:environment:read",
undefined,
);

expect(res.data[0].environment).toBe("foo");
});

test("returns undefined if the env file does not exist", async (ctx) => {
const res = await ctx.pluginRunner.callHook(
"project:environment:read",
undefined,
);

expect(res.data[0].environment).toBe(undefined);
});

test("returns undefined if the env file does not contain the variable", async (ctx) => {
await fs.writeFile(path.join(ctx.project.root, ".env.local"), "FOO=bar");

const res = await ctx.pluginRunner.callHook(
"project:environment:read",
undefined,
);

expect(res.data[0].environment).toBe(undefined);
});
Loading

0 comments on commit 9886096

Please sign in to comment.