diff --git a/CHANGELOG.md b/CHANGELOG.md index b5ae280..d03c9db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -139,4 +139,14 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how - deploy cli config.json placed next to tenant.yml will be merged into deploy configuration. ### Changed -- reverted to auth0 v2.42.0 \ No newline at end of file +- reverted to auth0 v2.42.0 + +## [1.4.2] - 2023-04-19 +### Added +- Support for Codespaces added so that when the registered command `openEndpointByName` is used, it resolves the correct URL for apps launched in the Codespaces environment. + +## [1.4.3] - 2023-05-01 +### Added +- Implemented `auth0.lab.postConfigureCommand` command +- Support for writing more than one app config to a single `.env` without overwriting values +- Added runtime-specific replacement values for tenant configuration from yaml: `CODESPACE_NAME` (same as defined [here]( https://docs.github.com/en/codespaces/developing-in-codespaces/default-environment-variables-for-your-codespace)) and `AUTH0_DOMAIN` (your tenant domain). diff --git a/README.md b/README.md index 2e1f2d4..49b166d 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,9 @@ In addition to the visual features listed below, the Labs extension also contrib - **Auth0: Configure Tenant Resources** - `auth0.lab.tenantConfigure` Configures tenant for the current lab materials. Only available when lab materials are present in workspace. - **Auth0: Export Tenant** - `auth0.exportTenant` Exports tenant configuration yml to the root directory. This exports everything and needs to be edited down for a lab. - **Auth0: Open Endpoint Url** - `auth0.lab.openEndpointByName?["Endpoint 1, Endpoint 2"]` Opens the URL associated with a specific named endpoint in the default browser. Multiple endpoints can be opened by supplying a comma seperated list. Only available when lab materials are present in workspace. +- **Auth0: Run Post Configure Command** `auth0.lab.postConfigureCommand` Allows you to specify the path of a shell script in `environment.json` (i.e., `"postConfigureCommand": ""`), that will execute upon configuration completion. Enables you to run a script to perform actions like storing values client id and client secret as secrets in the environment. This command currently has access to the following environment variables: + - `AUTH0_DOMAIN`: Your Auth0 domain URI + - `AUTH0_TOKEN`: The access token issued to your client from the authorization server. ### Authenticating diff --git a/package.json b/package.json index 1f7410e..a6c36a4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-labs", "preview": true, "displayName": "Auth0 Labs", - "version": "1.4.2", + "version": "1.4.3", "description": "A Visual Studio Code extension for training lab automation and quick access to tenant information.", "main": "./dist/extension.js", "publisher": "auth0", @@ -224,6 +224,13 @@ "icon": "$(beaker)", "enablement": "auth0:authenticated && auth0:isLabWorkspace" }, + { + "category": "Auth0", + "command": "auth0.lab.postConfigureCommand", + "title": "Run Post Configure Command", + "icon": "$(beaker)", + "enablement": "auth0:authenticated && auth0:isLabWorkspace" + }, { "category": "Auth0", "command": "auth0.lab.openLocalEndpoint", diff --git a/src/features/deploy/commands.ts b/src/features/deploy/commands.ts index ed68bdc..17b002d 100644 --- a/src/features/deploy/commands.ts +++ b/src/features/deploy/commands.ts @@ -71,6 +71,10 @@ export class DeployCommands { AUTH0_DOMAIN: getDomainFromToken(accessToken), AUTH0_CLIENT_ID: 'NEEDED FOR v7.17.0 Deploy CLI', AUTH0_ALLOW_DELETE: false, + AUTH0_KEYWORD_REPLACE_MAPPINGS: { + AUTH0_DOMAIN: getDomainFromToken(accessToken), + CODESPACE_NAME: process.env.CODESPACE_NAME + } }), }; await deploy(opts); diff --git a/src/features/labs/commands.ts b/src/features/labs/commands.ts index 5fe33d1..7170259 100644 --- a/src/features/labs/commands.ts +++ b/src/features/labs/commands.ts @@ -3,7 +3,13 @@ import * as path from 'path'; import { getLabEnvironment, getLabWorkspace } from './workspace'; import { LabResourceResolverBuilder } from './resolver'; import { LabEnvWriter } from './writer'; -import { getUrlForPort, getFileUri, startTour } from '../../utils'; +import { getClient } from '../../client'; +import { + getDomainFromToken, + getUrlForPort, + getFileUri, + startTour, +} from '../../utils'; const registerCommand = commands.registerCommand; const executeCommand = commands.executeCommand; @@ -25,6 +31,10 @@ export class LabCommands { registerCommand('auth0.lab.configure', this.configureLab), registerCommand('auth0.lab.localConfigure', this.localConfigure), registerCommand('auth0.lab.tenantConfigure', this.tenantConfigure), + registerCommand( + 'auth0.lab.postConfigureCommand', + this.postConfigureCommand + ), registerCommand('auth0.lab.openLocalEndpoint', this.openLocalEndpoint), registerCommand( 'auth0.lab.openEndpointByName', @@ -144,11 +154,20 @@ export class LabCommands { await executeCommand('auth0.lab.localConfigure'); } + if (labEnv.postConfigureCommand && !token.isCancellationRequested) { + progress.report({ + message: 'running post configure command', + increment: 4, + }); + + await executeCommand('auth0.lab.postConfigureCommand'); + } + //issue post command to kick off next process if (labEnv.postConfigureTour && !token.isCancellationRequested) { progress.report({ message: 'Starting Lab', - increment: 4, + increment: 5, }); const uri = getFileUri( @@ -190,4 +209,31 @@ export class LabCommands { new LabEnvWriter(workspace.uri).writeAll(resolvers); } }; + + postConfigureCommand = async (): Promise => { + console.log('auth0.labs.postConfigure'); + const workspace = getLabWorkspace(); + const labEnv = await getLabEnvironment(); + const client = await getClient(); + const accessToken = await client.getAccessToken(); + + if (workspace && labEnv && labEnv.postConfigureCommand) { + const uri = getFileUri( + `/.auth0/lab/${labEnv.postConfigureCommand}`, + workspace.uri + ); + + const terminal = window.createTerminal({ + name: 'Post Configure Script', + env: { + // eslint-disable-next-line @typescript-eslint/naming-convention + AUTH0_DOMAIN: getDomainFromToken(accessToken), + // eslint-disable-next-line @typescript-eslint/naming-convention + AUTH0_TOKEN: accessToken, + }, + }); + terminal.show(); + terminal.sendText(uri.fsPath); + } + }; } diff --git a/src/features/labs/models.ts b/src/features/labs/models.ts index af56b4a..b42c86f 100644 --- a/src/features/labs/models.ts +++ b/src/features/labs/models.ts @@ -4,6 +4,7 @@ export interface LocalEnvironment { resources: string | null; unauthenticatedTour: string | null; postConfigureTour: string | null; + postConfigureCommand: string | null; clients: Array; resourceServers: Array; } diff --git a/src/features/labs/writer.ts b/src/features/labs/writer.ts index ad23a71..27a1fe6 100644 --- a/src/features/labs/writer.ts +++ b/src/features/labs/writer.ts @@ -1,24 +1,50 @@ import * as vscode from 'vscode'; +import { readUriContents } from '../../utils'; import { Resolver } from './resolver'; const openTextDocument = vscode.workspace.openTextDocument; const fs = vscode.workspace.fs; export class LabEnvWriter { - constructor(private worspace: vscode.Uri) {} + constructor(private workspace: vscode.Uri) {} + + getExisting = async (uri: vscode.Uri) => { + try { + // will error if file does not exist + const stat = await fs.stat(uri); + + // read and parse file into name/value pairs + return await readUriContents(uri) + .then((file) => file.split('\n')) + .then((lines) => + lines.map((line) => { + const pair = line.split('='); + return { name: pair[0], value: pair[1] }; + }) + ); + } catch { + // return an empty array if file does not exist + return []; + } + }; writeAll = async (resolvers: Resolver[]) => { - resolvers.forEach(async (resolver) => { + for (const resolver of resolvers) { const uri = vscode.Uri.joinPath( - this.worspace, + this.workspace, resolver.getDirectory(), '.env' ); - const env = resolver - .resolveEnv(resolvers) - .map((i) => `${i.name}=${i.value}`) - .join('\n'); + + const existingEnv = await this.getExisting(uri); + const newEnv = resolver.resolveEnv(resolvers); + const unique = [ + ...new Map(existingEnv.concat(newEnv).map((m) => [m.name, m])).values(), + ]; + + const env = unique.map((i) => `${i.name}=${i.value}`).join('\n'); + await fs.writeFile(uri, Buffer.from(env, 'utf8')); - }); + } }; } diff --git a/tests/suite/extension.test.ts b/tests/suite/extension.test.ts index 2bdaaca..145695e 100644 --- a/tests/suite/extension.test.ts +++ b/tests/suite/extension.test.ts @@ -15,5 +15,5 @@ suite('Extension', () => { await started.activate(); assert.strictEqual(started && started.isActive, true); } - }); + }).timeout(1000 * 10);//10 seconds });