Skip to content
This repository has been archived by the owner on Nov 19, 2024. It is now read-only.

Commit

Permalink
Ed/render blueprint from template (#51)
Browse files Browse the repository at this point in the history
* added @eropple/find-up; ported test from Node; added test infra for CLI

* added 'render blueprint from-template'

* verbiage fix
  • Loading branch information
eropple authored Feb 7, 2023
1 parent 9caf654 commit 7dc06e4
Show file tree
Hide file tree
Showing 15 changed files with 297 additions and 77 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ jobs:
# `-$1-$kernel-$arch`.
- name: outputting version
run: ./_build/write-version.bash gha > version.ts
- name: running tests
run: make test
- name: compiling executable for x86_64-unknown-linux-gnu
run: make build-linux-x86_64
- name: compiling executable for x86_64-apple-darwin
Expand Down Expand Up @@ -109,7 +111,7 @@ jobs:
/tmp/nupkg/*
LICENSE
README.md
push-nupkg:
runs-on: ubuntu-latest
needs:
Expand All @@ -126,4 +128,4 @@ jobs:
uses: crazy-max/ghaction-chocolatey@v2
with:
args: push --key ${{ secrets.CHOCOLATEY_APIKEY }} --source https://push.chocolatey.org ./tmp/nupkg/rendercli.nupkg

3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ cache-deps:
deps:
deno cache --lock=deps-lock.json deps.ts

test:
deno test --allow-write --allow-read --allow-net --allow-env --allow-run

build-linux-x86_64: deps
$(eval OUTFILE ?= render-linux-x86_64)
deno compile \
Expand Down
42 changes: 42 additions & 0 deletions commands/blueprint/from-template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Log } from "../../deps.ts";
import { templateNewProject, writeExampleBlueprint } from "../../new/repo/index.ts";
import { standardAction, Subcommand } from "../_helpers.ts";

const desc =
`Adds a Render Blueprint (render.yaml) to this repo, based on a template.
You can fetch a Render Blueprint from a repo via any of the following identifiers:
repo-name
repo-name@gitref
user/repo-name
user/repo-name@gitref
github:user/repo-name
github:user/repo-name@gitref
If \`user\` is not provided, \`render-examples\` is assumed. If no source prefix is provided, \`github\` is assumed.
At present, \`render blueprint from-template\` does not support private repositories.
Once you've added a Render Blueprint to your repo, you can run \`render blueprint launch\` to deploy your Blueprint.
(Future TODO: enable \`gitlab:\` prefix, enable arbitrary Git repositories, enable private repositories.)`;

export const blueprintFromTemplateCommand =
new Subcommand()
.name('from-template')
.description(desc)
.arguments<[string]>("<identifier:string>")
.option("--directory <string>", "directory to write the blueprint to (default: current directory)")
.option("--skip-cleanup", "skips cleaning up tmpdir (on success or failure)")
.action((opts, identifier) =>
standardAction({
interactive: async (_logger: Log.Logger): Promise<number> => {
await writeExampleBlueprint({
identifier,
repoDir: opts.directory,
skipCleanup: opts.skipCleanup,
});
return 0;
}
}));
4 changes: 3 additions & 1 deletion commands/blueprint/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Subcommand } from "../_helpers.ts";
import { blueprintFromTemplateCommand } from "./from-template.ts";
import { blueprintLaunchCommand } from "./launch.ts";

const desc =
const desc =
`Commands for interacting with Render Blueprints (render.yaml files).`;

export const blueprintCommand =
Expand All @@ -14,5 +15,6 @@ export const blueprintCommand =
})
// .command("new", blueprintNewCommand)
.command("launch", blueprintLaunchCommand)
.command("from-template", blueprintFromTemplateCommand)
// .command("validate", blueprintValidateCommand)
;
14 changes: 0 additions & 14 deletions commands/blueprint/new.ts

This file was deleted.

53 changes: 0 additions & 53 deletions commands/blueprint/validate.ts

This file was deleted.

3 changes: 3 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions deps-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@
"https://deno.land/[email protected]/path/posix.ts": "c1f7afe274290ea0b51da07ee205653b2964bd74909a82deb07b69a6cc383aaa",
"https://deno.land/[email protected]/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9",
"https://deno.land/[email protected]/path/win32.ts": "bd7549042e37879c68ff2f8576a25950abbfca1d696d41d82c7bca0b7e6f452c",
"https://deno.land/[email protected]/testing/_diff.ts": "029a00560b0d534bc0046f1bce4bd36b3b41ada3f2a3178c85686eb2ff5f1413",
"https://deno.land/[email protected]/testing/_format.ts": "0d8dc79eab15b67cdc532826213bbe05bccfd276ca473a50a3fc7bbfb7260642",
"https://deno.land/[email protected]/testing/asserts.ts": "0ee58a557ac764e762c62bb21f00e7d897e3919e71be38b2d574fb441d721005",
"https://deno.land/[email protected]/_util/asserts.ts": "d0844e9b62510f89ce1f9878b046f6a57bf88f208a10304aab50efcb48365272",
"https://deno.land/[email protected]/_util/os.ts": "8a33345f74990e627b9dfe2de9b040004b08ea5146c7c9e8fe9a29070d193934",
"https://deno.land/[email protected]/encoding/base64.ts": "8605e018e49211efc767686f6f687827d7f5fd5217163e981d8d693105640d7a",
Expand Down
4 changes: 4 additions & 0 deletions deps.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export {
assertEquals,
assertThrows,
} from 'https://deno.land/[email protected]/testing/asserts.ts';;
export * as Path from "https://deno.land/[email protected]/path/mod.ts";
export * as FS from "https://deno.land/[email protected]/fs/mod.ts";
export * as Log from "https://deno.land/[email protected]/log/mod.ts";
Expand Down
47 changes: 47 additions & 0 deletions new/repo/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts";
import { verboseLogging } from "../../util/logging.ts";
import { pathExists } from "../../util/paths.ts";
import { templateNewProject, writeExampleBlueprint } from "./index.ts";

// TODO: tests DO pass, but currently leak resources from the `tgz` dep and we should remove it
// uncomment once fixed (n.b.: this is not a problem in the CLI itself, just the tests)
// Also be aware of GitHub API rate limiting when running tests.

// TODO: add tests for 'resolveTemplateIdentifier' when not just github
// TODO: add tests for `force` option
// Deno.test("repo from-template", async (t) => {
// const root = await Deno.makeTempDir();
// const outputDir = `${root}/output`;

// await templateNewProject({
// identifier: "sveltekit",
// outputDir,
// });

// assertEquals(true, !!(await pathExists(`${outputDir}/render.yaml`)));

// await Deno.remove(root, { recursive: true });

// });

// Deno.test("blueprint from-template", async (t) => {
// const root = Deno.makeTempDirSync();
// const repoDir = `${root}/output`;

// await Deno.mkdir(repoDir);
// const proc = Deno.run({
// cmd: [ 'git', 'init' ],
// cwd: repoDir,
// });
// const success = (await proc.status()).success;
// assertEquals(true, success);

// await writeExampleBlueprint({
// identifier: "sveltekit",
// repoDir,
// });

// assertEquals(true, !!(await pathExists(`${repoDir}/render.yaml`)));

// await Deno.remove(root, { recursive: true });
// });
69 changes: 65 additions & 4 deletions new/repo/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { identity } from "../../util/fn.ts";
import { unwrapAsyncIterator } from "../../util/iter.ts";
import { getLogger } from "../../util/logging.ts";
import { pathExists } from "../../util/paths.ts";
import { findUp } from "../../util/find-up.ts";

export type TemplateNewProjectArgs = {
identifier: string;
Expand Down Expand Up @@ -98,10 +99,8 @@ export async function templateNewProject(args: TemplateNewProjectArgs): Promise<
console.log("");
console.log("and you're good to go!");
console.log("");
console.log(Cliffy.colors.brightWhite("Now that you have a new project set up, you'll need to push to GitHub or GitLab"))
console.log(`${Cliffy.colors.brightWhite("in order to deploy it to Render")}. Once you've followed the instructions`);
console.log(`provided when you create your repo in GitHub or GitLab, you can then run`);
console.log(`${Cliffy.colors.cyan("render buildpack launch")} to easily deploy your project to Render.`);
console.log(Cliffy.colors.brightWhite("Now that you have a new blueprint set up, you'll need to push it to your repo."))
console.log(`Once done, ${Cliffy.colors.cyan("render buildpack launch")} to easily deploy your project to Render.`);
console.log("");
console.log("Thanks for using Render, and good luck with your new project!");
} finally {
Expand Down Expand Up @@ -172,3 +171,65 @@ async function downloadRepo(loc: Locator, tempDir: string, outDir: string, force
}
}
}

export type WriteExampleBlueprintArgs = {
identifier: string;
repoDir?: string;
skipCleanup?: boolean;
};

export async function writeExampleBlueprint(
args: WriteExampleBlueprintArgs,
) {
const logger = await getLogger();

const tempDir = await Deno.makeTempDir({ prefix: 'rendercli_' });
const userRepoLocation = Path.resolve(args.repoDir ?? Deno.cwd());

try {
logger.debug("Repo diretory:", userRepoLocation);
logger.debug(`Attempting to resolve template '${args.identifier}'.`);
const locator = resolveTemplateIdentifier(args.identifier);

logger.debug(`Ensuring repo is a valid Render template: ${Deno.inspect(locator)}`);
await ensureRepoIsValid(locator);


logger.debug(`Making sure we're in a git repo from '${userRepoLocation}'...`);
const gitDir = await findUp(userRepoLocation, '.git', { searchFor: 'directories' });;

if (!gitDir) {
throw new Error("You must be in a git repository to use this command.");
}

const repoDir = Path.dirname(Path.resolve(gitDir));
logger.debug(`Repo dir: ${repoDir}`);

const renderYamlPath = `${repoDir}/render.yaml`;
if (await pathExists(renderYamlPath)) {
logger.warning("This repo already has a render.yaml file; we're going to copy it to 'render.yaml.old'.");
await Deno.copyFile(renderYamlPath, `${renderYamlPath}.old`);
}

const unzipDir = `${tempDir}/blueprint-source`;
await downloadRepo(locator, tempDir, unzipDir, true);

logger.debug(`Copying '${unzipDir}/render.yaml' to '${renderYamlPath}'...`);
await Deno.copyFile(`${unzipDir}/render.yaml`, renderYamlPath);

console.log(`🎉 Done! 🎉 Your project's now ready to use! Your new ${Cliffy.colors.cyan(locator.repo)} blueprint has been saved to`);
console.log("");
console.log(Cliffy.colors.brightYellow(renderYamlPath));
console.log("");
console.log(Cliffy.colors.brightWhite("Please make sure to review the blueprint's contents to make sure they're appropriate for your app!"));
console.log("");
console.log(Cliffy.colors.brightWhite("Now that you have a new blueprint set up, you'll need to push it to your repo."))
console.log(`Once done, ${Cliffy.colors.cyan("render buildpack launch")} to easily deploy your project to Render.`);
console.log("");
console.log("Thanks for using Render, and good luck with your new project!");
} finally {
if (!args.skipCleanup) {
await Deno.remove(tempDir, { recursive: true });
}
}
}
7 changes: 7 additions & 0 deletions render.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
services:
- type: web
name: sveltekit
env: node
buildCommand: npm install && npm run build
startCommand: node build/index.js
autoDeploy: false
55 changes: 55 additions & 0 deletions util/find-up.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { assertEquals } from "../deps.ts";
import {
findUp,
} from './find-up.ts';

function f(path: string) {
Deno.writeFileSync(path, new Uint8Array());
}

Deno.test('findUp', async (t) => {
const root = Deno.makeTempDirSync({
prefix: 'find-up-test-',
});

Deno.mkdirSync(`${root}/a/b/c/d/e/f`, { recursive: true });
Deno.mkdirSync(`${root}/a/b/c/X/Y/Z`, { recursive: true });
Deno.mkdirSync(`${root}/1/2/3/4/5/6/abc`, { recursive: true });
Deno.mkdirSync(`${root}/1/abc`, { recursive: true });

f(`${root}/a/b/c/d/e/f/package.json`);
f(`${root}/a/b/c/package.json`);
f(`${root}/1/2/3/4/abc`);

await t.step('should find a file in the current directory', async () => {
const ret = await findUp(`${root}/a/b/c/d/e/f`, 'package.json');
assertEquals(ret, `${root}/a/b/c/d/e/f/package.json`);
});

await t.step('should traverse upwards to find a file', async () => {
const ret = await findUp(`${root}/a/b/c/d/e`, 'package.json');
assertEquals(ret, `${root}/a/b/c/package.json`);
});

await t.step('should iterate multiple start paths', async () => {
const ret = await findUp([`${root}/1/2/3/4/5/6`, `${root}/a/b/c/d/e`], 'package.json');
assertEquals(ret, `${root}/a/b/c/package.json`);
});

await t.step('should skip directories when opts.searchFor === "files"', async () => {
const ret = await findUp(`${root}/1/2/3/4/5/6`, 'abc', { searchFor: 'files' });
assertEquals(ret, `${root}/1/2/3/4/abc`);
});

await t.step('should skip files when opts.searchFor === "directories"', async () => {
const ret = await findUp(`${root}/1/2/3/4/5`, 'abc', { searchFor: 'directories' });
assertEquals(ret, `${root}/1/abc`);
});

await t.step('should return null when no file is found', async () => {
const ret = await findUp(`${root}/a/b/c/d/e`, 'does-not-exist');
assertEquals(ret, null);
});

Deno.remove(root, { recursive: true });
});
Loading

0 comments on commit 7dc06e4

Please sign in to comment.