Skip to content

Commit

Permalink
fix(cli): initialize secrets file from resources
Browse files Browse the repository at this point in the history
  • Loading branch information
juanrgm committed Nov 21, 2023
1 parent 2fe73ba commit 561fddc
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 60 deletions.
5 changes: 5 additions & 0 deletions .changeset/breezy-planes-try.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rtpl/cli": patch
---

Fix secrets
20 changes: 7 additions & 13 deletions packages/cli/src/actions/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import * as lock from "../utils/self/lock.js";
import { splitGlobalOptions } from "../utils/self/options.js";
import { parseTplFile, resolveTpl } from "../utils/self/resolve.js";
import { writeSecretsFile } from "../utils/self/secrets.js";
import backup from "./backup.js";
import diff from "./diff.js";
import chalk from "chalk";
Expand Down Expand Up @@ -57,23 +58,15 @@ export default async function install(options: InstallActionOptions) {
filter: options.filter,
});

if (secrets)
await writeFile(config.secrets.path, JSON.stringify(secrets, null, 2));

//await tpl.onBeforeInstall?.(options);

const lockData = lock.parseFile(config.lock.path);

let changes = 0;
let errors = 0;

const selfLockData = lockData.templates[tpl.config.name] || {
files: {},
dirs: {},
};

const actions = await getFileActions(resources, selfLockData);

let changes = 0;
let errors = 0;
for (const path in actions) {
const action = actions[path];
if (action.type !== ActionEnum.NONE) {
Expand Down Expand Up @@ -119,9 +112,10 @@ export default async function install(options: InstallActionOptions) {

lockData.templates[tpl.config.name] = selfLockData;

if (!options.dryRun) await lock.writeFile(config.lock.path, lockData);

//await tpl.onInstall?.(options);
if (!options.dryRun) {
if (secrets) await writeSecretsFile(config.secrets.path, secrets);
await lock.writeFile(config.lock.path, lockData);
}

if (!changes) process.stderr.write(`${chalk.grey("No changes")}\n`);
return { changes, exitCode: errors ? 1 : 0 };
Expand Down
11 changes: 10 additions & 1 deletion packages/cli/src/resources/AbstractRes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ export enum ResType {

const kindType = "_tplRes";

export class ResReadyContext {
constructor(
readonly data: {
secrets: Secrets | undefined;
initialSecrets: boolean;
},
) {}
}

export class MinimalRes<T = any, D = any> {
protected [kindType] = true;
protected readonly type!: T;
Expand Down Expand Up @@ -89,7 +98,7 @@ export abstract class AbstractRes<
);
}

async onReady(path: string, secrets: Secrets | undefined) {
async onReady(path: string, ctx: ResReadyContext) {
setDelayedValue(this.path, path);
setDelayedValue(this.dirname, path);
}
Expand Down
79 changes: 40 additions & 39 deletions packages/cli/src/resources/SecretRes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ import {
upperAlphaCharset,
} from "../utils/crypto.js";
import { readIfExists } from "../utils/fs.js";
import { Secrets } from "../utils/self/secrets.js";
import { AbstractRes, ResOptions, ResType } from "./AbstractRes.js";
import {
AbstractRes,
ResOptions,
ResReadyContext,
ResType,
} from "./AbstractRes.js";
import { DelayedValue, setDelayedValue } from "./DelayedValue.js";

export type SecretData = {
Expand Down Expand Up @@ -61,42 +65,39 @@ export class SecretRes extends AbstractRes<
override toString() {
return this.value.toString();
}
override async onReady(path: string, secrets: Secrets | undefined) {
await super.onReady(path, secrets);
const value = secrets
? secrets[path]
: (await readIfExists(path))?.toString();
const hasNewValue = value ? false : true;
const generate = this.data?.generate ?? true;
let endValue = value;
if (!endValue && generate) {
const length = this.data?.length ?? 16;
const inCharset = this.data?.charset ?? "alpha-number";
const charsetMap = {
number: numberCharset,
lowerAlpha: lowerAlphaCharset,
upperAlpha: upperAlphaCharset,
alpha: alphaCharset,
"alpha-number": alphaNumberCharset,
};
const charset =
typeof inCharset === "string" ? charsetMap[inCharset] : inCharset.value;
if (typeof charset !== "string")
throw new Error(`Charset is not defined`);
if (charset.length <= 10) {
throw new Error(`Charset length is too small`);
}
endValue = randomString(length, charset);
if (secrets) secrets[path] = endValue;
}
const auxValue = await this.data?.onReady?.({
path,
prev: value,
current: endValue,
});
if (typeof auxValue === "string") endValue = auxValue;
if (typeof endValue !== "string") throw new Error(`Secret is empty`);
setDelayedValue(this.value, endValue);
setDelayedValue(this.hasNewValue, hasNewValue);
override async onReady(path: string, ctx: ResReadyContext) {
await super.onReady(path, ctx);
const prevFile = (await readIfExists(path))?.toString();
const prev = ctx.data.secrets
? ctx.data.secrets[path] ??
(ctx.data.initialSecrets ? prevFile : undefined)
: undefined;
const $generate = this.data?.generate ?? true;
let current = prev ?? ($generate ? generate(this.data) : undefined);
const custom = await this.data?.onReady?.({ path, prev, current });
if (typeof custom === "string") current = custom;
if (typeof current !== "string") throw new Error(`Secret is empty`);
if (ctx.data.secrets) ctx.data.secrets[path] = current;
setDelayedValue(this.value, current);
setDelayedValue(this.hasNewValue, current !== prevFile);
}
}

function generate(data: SecretData = {}) {
const length = data.length ?? 16;
const inCharset = data.charset ?? "alpha-number";
const charsetMap = {
number: numberCharset,
lowerAlpha: lowerAlphaCharset,
upperAlpha: upperAlphaCharset,
alpha: alphaCharset,
"alpha-number": alphaNumberCharset,
};
const charset =
typeof inCharset === "string" ? charsetMap[inCharset] : inCharset.value;
if (typeof charset !== "string") throw new Error(`Charset is not defined`);
if (charset.length <= 10) {
throw new Error(`Charset length is too small`);
}
return randomString(length, charset);
}
18 changes: 11 additions & 7 deletions packages/cli/src/utils/self/resolve.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AbstractRes } from "../../resources/AbstractRes.js";
import { AbstractRes, ResReadyContext } from "../../resources/AbstractRes.js";
import { MinimalDirRes } from "../../resources/DirRes.js";
import { findPath, readAnyFile } from "../fs.js";
import { checkPath, findPath, readAnyFile } from "../fs.js";
import { isPlainObject } from "../object.js";
import { expandPaths, isDir, isPath, stripRootBackPaths } from "../path.js";
import { makeFilter } from "../string.js";
Expand Down Expand Up @@ -156,15 +156,19 @@ export async function resolveTpl(
await item.tpl.config.onResolve?.bind(rs)(item.resources, tplOptions);
}

const secrets = config.secrets.enabled
? await parseSecretsFile(config.secrets.path)
: undefined;
const ctx = new ResReadyContext({
secrets: config.secrets.enabled
? await parseSecretsFile(config.secrets.path)
: undefined,
initialSecrets: !(await checkPath(config.secrets.path)),
});

const resources = await resolveResources({
config,
filter: options.filter,
resources: inResources,
onValue: async (path, res, actions) => {
await res.onReady(posix.join(resourcesDir, path), secrets);
await res.onReady(posix.join(resourcesDir, path), ctx);
if (MinimalDirRes.isInstance(res)) {
actions.add = false;
actions.process = !res.resolved;
Expand All @@ -184,5 +188,5 @@ export async function resolveTpl(
delete resources[path];
}

return { resources, secrets };
return { resources, secrets: ctx.data.secrets };
}
5 changes: 5 additions & 0 deletions packages/cli/src/utils/self/secrets.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { readIfExists } from "../fs.js";
import { writeFile } from "fs/promises";

export type Secrets = Record<string /* path */, string>;

export async function parseSecretsFile(path: string): Promise<Secrets> {
const buffer = await readIfExists(path);
return buffer ? JSON.parse(buffer.toString()) : {};
}

export async function writeSecretsFile(path: string, data: Secrets) {
await writeFile(path, JSON.stringify(data, null, 2));
}

0 comments on commit 561fddc

Please sign in to comment.