Skip to content

Commit

Permalink
introduce includeInDmnoConfig option
Browse files Browse the repository at this point in the history
  • Loading branch information
theoephraim committed Dec 13, 2024
1 parent 30ef304 commit fc294c7
Show file tree
Hide file tree
Showing 15 changed files with 109 additions and 42 deletions.
11 changes: 11 additions & 0 deletions .changeset/few-rings-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@dmno/encrypted-vault-plugin": patch
"@dmno/remix-integration": patch
"@dmno/vite-integration": patch
"@dmno/1password-plugin": patch
"@dmno/bitwarden-plugin": patch
"@dmno/infisical-plugin": patch
"dmno": patch
---

add `includeInDmnoConfig` option so items can be exluded from DMNO_CONFIG
12 changes: 12 additions & 0 deletions example-repo/packages/astro-web/.dmno/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ export default defineDmnoService({
'SOME_API_KEY',
],
schema: {

INTERNAL_ITEM: {
description: 'will not be included in DMNO_CONFIG',
value: 'dont-include-me',
includeInDmnoConfig: false,
},

FN_INTERNAL_CHECK: {
value: () => DMNO_CONFIG.INTERNAL_ITEM,
},


OP_TOKEN: { extends: OnePasswordTypes.serviceAccountToken },

FOO: {
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/cli/commands/printenv.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@ program.action(async (itemPath: string, opts: {}, thisCommand) => {
checkForSchemaErrors(workspace);
checkForConfigErrors(service);


const injectedEnv = await ctx.dmnoServer.makeRequest('getInjectedJson', service.serviceName);
// TODO: process env version or dmno env? what if the key is renamed as an env var? flag to select?
// TODO: could be smarter about not caring about errors unless they affect the item(s) being printed
// TODO: support nested paths
// TODO: do we want to support multiple items?

if (!injectedEnv[itemPath]) {
const { injectedProcessEnv } = await ctx.dmnoServer.makeRequest('getServiceResolvedConfig', service.serviceName);

if (!(itemPath in injectedProcessEnv)) {
throw new CliExitError(`Config item ${itemPath} not found in config schema`, {
details: [
'Perhaps you meant one of:',
Expand All @@ -49,10 +50,9 @@ program.action(async (itemPath: string, opts: {}, thisCommand) => {
});
}


// TODO: what to do about formatting of arrays/objects/etc
// now just print the resolved value
ctx.logOutput(injectedEnv[itemPath].value);
ctx.logOutput(injectedProcessEnv[itemPath]);
});

export const PrintEnvCommand = program;
9 changes: 7 additions & 2 deletions packages/core/src/cli/commands/resolve.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ program.action(async (opts: {
checkForSchemaErrors(workspace);
checkForConfigErrors(service, { showAll: opts?.showAll });

// this logic could probably move to the service itself?
function getExposedConfigValues() {
const values = {} as Record<string, any>;
for (const itemKey in service.configNodes) {
Expand All @@ -71,14 +72,18 @@ program.action(async (opts: {
return values;
}

// when we are using the non default format (which includes everything) we have the same questions
// here about what values to include, and how to handle env var keys that may be renamed
// probably need a flag to select which we are talking about

// console.log(service.config);
if (opts.format === 'json') {
console.log(JSON.stringify(getExposedConfigValues()));
} else if (opts.format === 'json-full') {
console.dir(service, { depth: null });
} else if (opts.format === 'json-injected') {
const injectedJson = await ctx.dmnoServer.makeRequest('getInjectedJson', ctx.selectedService.serviceName);
console.log(JSON.stringify(injectedJson));
const { injectedDmnoEnv } = await ctx.dmnoServer.makeRequest('getServiceResolvedConfig', ctx.selectedService.serviceName);
console.log(JSON.stringify(injectedDmnoEnv));
} else if (opts.format === 'env') {
console.log(stringifyObjectAsEnvFile(getExposedConfigValues()));
} else {
Expand Down
21 changes: 7 additions & 14 deletions packages/core/src/cli/commands/run.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,25 +62,18 @@ program.action(async (_command, opts: {
//! await workspace.resolveConfig();
checkForConfigErrors(service);

const injectedJson = await ctx.dmnoServer.makeRequest('getInjectedJson', ctx.selectedService.serviceName);
const {
injectedProcessEnv,
injectedDmnoEnv,
} = await ctx.dmnoServer.makeRequest('getServiceResolvedConfig', ctx.selectedService.serviceName);

const fullInjectedEnv = {
// TODO: not sure if we want to overwrite or not
...process.env,
...injectedProcessEnv,
};
// we need to add any config items that are defined in dmno config, but we dont want to modify existing items
for (const key in injectedJson) {
// must skip $SETTINGS
if (key.startsWith('$')) continue;

// TODO: need to think about how we deal with nested items
// TODO: let config nodes expose themselves in inject env vars with aliases
if (!Object.hasOwn(process.env, key)) {
const strVal = injectedJson[key]?.value?.toString();
if (strVal !== undefined) fullInjectedEnv[key] = strVal;
}
}

fullInjectedEnv.DMNO_INJECTED_ENV = JSON.stringify(injectedJson);
fullInjectedEnv.DMNO_INJECTED_ENV = JSON.stringify(injectedDmnoEnv);
// this is what signals to the child process that is has a parent dmno server to use
fullInjectedEnv.DMNO_PARENT_SERVER = ctx.dmnoServer.parentServerInfo;

Expand Down
22 changes: 18 additions & 4 deletions packages/core/src/config-engine/config-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,10 +409,24 @@ export class DmnoService {
return this.configraphEntity.isValid;
}

getEnv() {
const env: Record<string, any> = _.mapValues(this.configraphEntity.configNodes, (node) => {
return node.resolvedValue;
});
/**
* key/value object of resolved config ready to be injected as actual environment variables (process.env)
*/
getInjectedProcessEnv() {
const env: Record<string, string | undefined> = {};
for (const node of _.values(this.configraphEntity.configNodes)) {
// TODO: handle key renaming
// TODO: better handling of nested keys / object
const val = node.resolvedValue;
// not sure about this handling of _empty_ items
if (val === null || val === undefined || val === '') {
env[node.key] = undefined;
} else if (_.isPlainObject(val)) {
env[node.key] = JSON.stringify(val);
} else {
env[node.key] = val.toString();
}
}
return env;
}

Expand Down
13 changes: 12 additions & 1 deletion packages/core/src/config-engine/configraph-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ export type DmnoDataTypeMetadata = {

/** opt in/out of build-type code replacements - default is false unless changed at the service level */
dynamic?: boolean;

/** set to false to keep this item out of DMNO_CONFIG */
includeInDmnoConfig?: boolean,
};

// way more legible, but super weird that we are involving a class like this
Expand Down Expand Up @@ -142,7 +145,11 @@ DmnoEntityMetadata, DmnoDataTypeMetadata, DmnoConfigraphNode
getInjectedEnvJSON(): InjectedDmnoEnv {
// some funky ts stuff going on here... doesn't like how I set the values,
// but otherwise the type seems to work ok?
const env: any = _.mapValues(this.configNodes, (item) => item.toInjectedJSON());
const env: any = {};
_.each(this.configNodes, (item, itemKey) => {
if (!item.includeInDmnoConfig) return;
env[itemKey] = item.toInjectedJSON();
});
// simple way to get settings passed through to injected stuff - we may want
env.$SETTINGS = this.settings;
return env as any;
Expand All @@ -159,6 +166,10 @@ export class DmnoConfigraphNode extends ConfigraphNode<DmnoDataTypeMetadata> {
return !!this.type.getMetadata('sensitive');
}

get includeInDmnoConfig() {
return this.type.getMetadata('includeInDmnoConfig') !== false;
}

get isDynamic() {
// this resolves whether the item should actually be treated as static or dynamic
// which takes into account the specific item's `dynamic` override
Expand Down
26 changes: 17 additions & 9 deletions packages/core/src/config-engine/type-generation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,22 @@ export async function generateServiceTypes(service: DmnoService, writeToFile = f
// write global file which defines a DMNO_CONFIG global
// this used in our config.mts files and in front-end apps where we inject rollup rewrites
await fs.promises.writeFile(`${typeGenFolderPath}/global.d.ts`, `${AUTOGENERATED_FILE_BANNER}
import type { DmnoGeneratedConfigSchema } from './schema.d.ts';
import type { DmnoConfigSchema } from './schema.d.ts';
declare global {
/** ${service.serviceName} config global obj */
const DMNO_CONFIG: DmnoGeneratedConfigSchema;
const DMNO_CONFIG: DmnoConfigSchema;
}
`, 'utf-8');

// write global file which defines a DMNO_CONFIG global
// this used in our config.mts files and in front-end apps where we inject rollup rewrites
await fs.promises.writeFile(`${typeGenFolderPath}/global-public.d.ts`, `${AUTOGENERATED_FILE_BANNER}
import type { DmnoGeneratedPublicConfigSchema } from './schema.d.ts';
import type { DmnoPublicConfigSchema } from './schema.d.ts';
declare global {
/** ${service.serviceName} config global obj - public (non-sensitive) items only */
const DMNO_PUBLIC_CONFIG: DmnoGeneratedPublicConfigSchema;
const DMNO_PUBLIC_CONFIG: DmnoPublicConfigSchema;
}
`, 'utf-8');
}
Expand All @@ -53,20 +53,28 @@ declare global {
export async function generateTypescriptTypes(service: DmnoService) {
const tsSrc = [
AUTOGENERATED_FILE_BANNER,
'export type DmnoGeneratedConfigSchema = {',
'export type FullDmnoConfigSchema = {',
];
const publicKeys: Array<string> = [];
const dmnoConfigKeys: Array<string> = [];
const dmnoPublicConfigKeys: Array<string> = [];
for (const itemKey in service.config) {
const configItem = service.config[itemKey];
if (!configItem.isSensitive) publicKeys.push(itemKey);
// generate the TS type for the item in the full schema
tsSrc.push(...await getTsDefinitionForNode(configItem, 1));
// then include in DMNO_CONFIG and DMNO_PUBLIC_CONFIG based on settings
if (configItem.includeInDmnoConfig) {
dmnoConfigKeys.push(itemKey);
if (!configItem.isSensitive) dmnoPublicConfigKeys.push(itemKey);
}
}
tsSrc.push('}');
tsSrc.push('\n');

const publicKeysForPick = _.map(publicKeys, JSON.stringify).join(' | ');
tsSrc.push(`export type DmnoGeneratedPublicConfigSchema = Pick<DmnoGeneratedConfigSchema, ${publicKeysForPick || 'never'}>`);

const keysForPick = _.map(dmnoConfigKeys, JSON.stringify).join(' | ');
tsSrc.push(`export type DmnoConfigSchema = Pick<FullDmnoConfigSchema, ${keysForPick || 'never'}>`);
const publicKeysForPick = _.map(dmnoPublicConfigKeys, JSON.stringify).join(' | ');
tsSrc.push(`export type DmnoPublicConfigSchema = Pick<FullDmnoConfigSchema, ${publicKeysForPick || 'never'}>`);
return tsSrc.join('\n');
}
export async function getPublicConfigKeys(service: DmnoService) {
Expand Down
12 changes: 8 additions & 4 deletions packages/core/src/config-loader/dmno-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,10 +426,13 @@ export class DmnoServer {
// TODO: ideally resolution would be a second step that we could trigger as needed
return workspace.toJSON();
},
getInjectedJson: async (serviceId: string) => {
getServiceResolvedConfig: async (serviceId: string) => {
const workspace = await this.configLoader?.getWorkspace()!;
const service = workspace.getService(serviceId);
return service.getInjectedEnvJSON();
return {
injectedProcessEnv: service.getInjectedProcessEnv(),
injectedDmnoEnv: service.getInjectedEnvJSON(),
};
},
clearCache: async () => {
const workspace = await this.configLoader?.getWorkspace()!;
Expand Down Expand Up @@ -485,11 +488,12 @@ export class DmnoServer {
throw new Error(`Unable to select service by package name - ${packageName}`);
}

const injectedEnv = await this.makeRequest('getInjectedJson', service.serviceName);
const { injectedProcessEnv, injectedDmnoEnv } = await this.makeRequest('getServiceResolvedConfig', service.serviceName);

return {
serviceDetails: service,
injectedEnv,
injectedProcessEnv,
injectedDmnoEnv,
};
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/integrations/remix/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ async function reloadDmnoConfig() {
(process as any).dmnoServer ||= new DmnoServer({ watch: true });
dmnoServer = (process as any).dmnoServer;
const resolvedService = await dmnoServer.getCurrentPackageConfig();
const injectedConfig = resolvedService.injectedEnv;
const injectedConfig = resolvedService.injectedDmnoEnv;
dmnoConfigValid = resolvedService.serviceDetails.isValid;
configItemKeysAccessed = {};

Expand Down
2 changes: 1 addition & 1 deletion packages/integrations/vite/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ async function reloadDmnoConfig() {
(process as any).dmnoServer ||= new DmnoServer({ watch: true });
dmnoServer = (process as any).dmnoServer;
const resolvedService = await dmnoServer.getCurrentPackageConfig();
const injectedConfig = resolvedService.injectedEnv;
const injectedConfig = resolvedService.injectedDmnoEnv;
dmnoConfigValid = resolvedService.serviceDetails.isValid;
configItemKeysAccessed = {};

Expand Down
1 change: 1 addition & 0 deletions packages/plugins/1password/src/data-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const OnePasswordServiceAccountToken = createDmnoDataType({
sensitive: {
redactMode: 'show_last_2',
},
includeInDmnoConfig: false,
});

const OnePasswordUUID = createDmnoDataType({
Expand Down
3 changes: 3 additions & 0 deletions packages/plugins/bitwarden/src/data-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const BitwardenMachineIdentityAccessToken = createDmnoDataType({
icon: BITWARDEN_ICON,
},
sensitive: true,
includeInDmnoConfig: false,
});

const BitwardenSecretId = createDmnoDataType({
Expand All @@ -29,6 +30,7 @@ const BitwardenSecretId = createDmnoDataType({
ui: {
icon: BITWARDEN_ICON,
},
includeInDmnoConfig: false,
});

const BitwardenProjectId = createDmnoDataType({
Expand All @@ -42,6 +44,7 @@ const BitwardenProjectId = createDmnoDataType({
ui: {
icon: BITWARDEN_ICON,
},
includeInDmnoConfig: false,
});


Expand Down
1 change: 1 addition & 0 deletions packages/plugins/encrypted-vault/src/data-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const DmnoEncryptionKey = createDmnoDataType({
icon: 'material-symbols:key',
},
sensitive: true,
includeInDmnoConfig: false,
});

export const EncryptedVaultTypes = {
Expand Down
6 changes: 5 additions & 1 deletion packages/plugins/infisical/src/data-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const InfisicalClientId = createDmnoDataType({
ui: {
icon: INFISICAL_ICON,
},
sensitive: true,
includeInDmnoConfig: false,
});

const InfisicalClientSecret = createDmnoDataType({
Expand All @@ -29,6 +29,8 @@ const InfisicalClientSecret = createDmnoDataType({
ui: {
icon: INFISICAL_ICON,
},
sensitive: true,
includeInDmnoConfig: false,
});

const InfisicalEnvironment = createDmnoDataType({
Expand All @@ -42,6 +44,7 @@ const InfisicalEnvironment = createDmnoDataType({
ui: {
icon: INFISICAL_ICON,
},
includeInDmnoConfig: false,
});

const InfisicalProjectId = createDmnoDataType({
Expand All @@ -55,6 +58,7 @@ const InfisicalProjectId = createDmnoDataType({
ui: {
icon: INFISICAL_ICON,
},
includeInDmnoConfig: false,
});


Expand Down

0 comments on commit fc294c7

Please sign in to comment.