Skip to content

Commit

Permalink
App Management V1 (#2494)
Browse files Browse the repository at this point in the history
* Feat/cli/appmanager/publish (#2178)

* Chore/deps/update/fusion wc person (#2493)

* chore(person-resolver): updating fusion-wc-person to fix issue with controlled component usage

* docs: changeset

* feat(cli): command for publish app

* fix(http): reverting changes to http module

* feat(cli): command 'app publish' to publish app with app api

* feat(cli): new app commands for upload, tag and publish

* feat(cookbook-app-react): new config type for cli app publishing

* chore(cookbooks): updating app config to latest type

* docs: changeset

* fix(cli): removing commented console.log

* fix(cli): fixing lint warning

* chore(cli): console message when uploading new app version

* chore(cli): app publishing support environments

* fix(cli): renaming versin parameter for main cli

* fix(cli): renaming variable

* fix(cli): lint warning in upload-application

* fix(cli): comment in upload-application

* fix(cli): getEndpointUrl resolves api from service discovery

* feat(apps-service): cookbook app-react adjustd for new apps service

* feat(module-app): module-apps using the new apps-proxy, cli using the updated apps module

* fix(cli): options for config --env is no longer required

* feat(module-app): appmanifest is now instance of ApplicationManifest

* fix(legacy): mapping legacy apploader to new api

* fix: lockfile

* feat(app-react): new hook for useMyApps for getting a users apps

* docs: chanegset

* chore(cors): adding cors module to cli dev-serve

* chore(module-app): handle setting app key

* chore(config): adapting cookbooks config til new app api scheme

* fix(cli): appConfigurators setClient in type

* fix(cli): improves commands help text

* docs(cli): ads command and authentication documentation to README

* docs(cli): updated README

* docs(cli): add examples in README

* fix(cli): use correct path to resources

* docs(cli): improving documentation structure in vue-press

* fix(app): setting app api version and exposing baseUri in app configurator

* fix(cli): app config now supports using tags

* docs(cli): example for github-workflow to publish with app service

* chore(cookbook-app-react): removing publish script from package.json

* fix(cli): adding dev comments

* fix(cli: using ApplicationManifest in app-package command

* docs: comments and changeset

* chore(app): update app configurator to support filtering app manifests by current user

* docs: changeset cookbooks

* fix(cookbooks): adjusting app config and manifest to breaking changes in cookbooks

* docs(cli): vuepress cli commands options in markdown table

* docs(cli): changing config to new syntax in vuepress

* chore(vuepress): simplifying github workflow for publishing app

* chore(cli): renaming variable tagd to tagged

* fix(cli): creating export manifest defaults increateManifestFromPackage

* fix(cli): createManifestFromPackage proper check for type module

* fix(react-app): removing hook useMyApps since removed from provider

* fix(module-app): remove early return in logic

* fix(modules-app): set api version in client query

* fix(module-app): enableAppModule async callback

* feat(cli): create plugin for handling application api calls (#2448)

- generate manifest, config and source code when local app
- proxy app-service calls when app miss-match

* feat(cli): create plugin for serving index.html from external public directory (#2449)

Create a plugin `externalPublicPlugin` to fix the issue with serving the `index.html` file from the specified external public directory. Vite mode `spa` will not serve the `index.html` file from the specified external public directory.

- Enhanced the middleware to intercept requests and serve the `index.html` file from the specified external public directory.
- Transformed the HTML using Vite's `transformIndexHtml` method.
- Applied appropriate content headers and additional configured headers before sending the response.

* feat(cli): Rewrite dev-portal (#2462)

* feat(cli): add proxyRequestLogger to log proxy requests in the CLI

* fix(cli): set entryPoint in loadAppManifest based on environment mode

* feat(cli): rewrite CLI to use standard vite config.

- removed custom proxy servers
- added plugin for external portal
- added plugin for app-proxy
- added httpClient for app-proxy
- fixed app manifest

TODO: rework app manifest template

* fix(app): update AppConfigurator and ApplicationManifest for improved type handling and async support

* refactor(cli): remove unused imports and clean up Vite config

* build: update lock file

* feat(cookbooks): add a portal poc app

* Feat/cli/appmanager/refactoring-commands (#2450)

* fix(cli): improving docs and types

* feat(cli): refactoring and error handling in commands using apps-service

* fix: lint fixes

* fix(cli): todo in getEndpointUrl, and not using default import in dev-proxy

* fix(cookbook-poc-portal): package as private to not releae to npm

* Renaming getCommisSha to resolveCommitSha

* docs: temporary changeset

* fix(cli): re-adding is-mergable-object as dependency

* fix(cli): make sure export manifest uses js or mjs based on package type

* Feat(module-app): Adjusted module to the new app service API. (#2470)

* feat(module-app): Align manifest with new app service

- Updated zod to version 3.23.8
- Added zod to package.json dependencies
- Removed unused import from index.ts
- Removed unused import from types.ts
- Updated flows.ts to use payload.appKey instead of payload.key
- Updated actions.ts to use AppManifest instead of ApplicationManifest
- Added application.schema.ts to validate application manifest
- Added changeset slimy-buses-give.md

* refactor: Update modules to reflect new manifest

* style: fix lint

* Refactor(app): Update AppBuildManifest and AppManifest types (#2472)

* Refactor(cli): Improve build process and manifest generation

- Refactored build-application.ts to improve the build process.
- Updated create-export-manifest.js to generate the build manifest.
- Renamed getCommisSha to resolveCommitSha in app-package.ts.
- Updated load-app-config.ts to provide more informative console output.
- Updated config.ts to handle unsupported file types.
- Updated vite-config.ts to handle undefined config.
- Updated spinner.ts to attach and detach console output.
- Updated app-config.ts to handle void return type in AppConfigFn.
- Updated bundle-application.ts to use createBuildManifest instead of createExportManifest.
- Updated load-manifest.ts to generate the manifest with command and mode information.

* Refactor(cookbook-app-react): Update app.config and app.manifest.config

- Update app.config.ts to remove unused imports and adjust the endpoints URL.
- Update app.manifest.config.ts to set the appKey to 'app-react' when serving.

* Refactor(app): Update AppBuildManifest and AppManifest types

* refactor(cookbook): Remove unused app configuration files

* doc: update changesets

* refactor(cli): Update publish option default value to 'current'

* docs: update changeset

* Refactor(types): Update imports and exports in types.ts

* doc: updated docs

* docs(cli): documenting major cli changes and vue-press docs

* fix(AppManifest): adding missing types for AppManifest in module-app

* fix(app-module): adding allowedExtensions in build manifest

* fix(module-app): correcting name of categories in AppManifest

* fix(app-module): removed allowed extensions in zod ApiApplicationBuildSchema

* fix(cli): merge of manifest

* fix(app-proxy): simplify app proxy

* fix(module-app): update interfaces and schemas for api calls

* fix(cli): use correct app key when running dev-server

* fix(app-module): merging fixes

* fix(cli): remove doubble import of deepmerge

* fix(module-app): adding category to ApiApplicationSchema

* style: fix lint

* refactor(cli): emit error on failed bundle upload

* refactor(cli): Update import paths for app-proxy and external-public plugins

* doc(cli): Add README for the app proxy plugin

* refactor(cli): Add middleware to serve static assets from specified path

- remove altering of `publicDir`
- add middleware for handling static assets from provided path
- ensure that middleware does not hijack source assets
- ensure static asset middleware does not process `index.html`

* refactor(cli): Update external-public plugin

- check public dir for asset
- allow filtering

* doc(cli): Add External Public Plugin documentation

* build: update lock

* feat(cli): Add App Asset Export Plugin (#2479)

* feat(cli): Add app assets export plugin

create a plugin which will extract application assets and inject loader for import the correct asset when imported threw proxy

* feat(cli): Include StandardIncludeAssetExtensions in generated base manifest

* feat(cli): Include AppAssetExportPlugin in ViteConfig

When building an application, the `AppAssetExportPlugin` is now added to the `ViteConfig` and configured to include `manifest.build.allowedExtensions`.

* refactor(cli): Improve asset resolution and error handling in AppAssetExportPlugin

- resolve should check if the asset exists before filtering

* feat(cookbook-app-react-assets): Add Asset cookbook

Create a cookbook for testing the asset plugin

* refactor(app): rename `baseUri` to `assetUri` to make more sense

* refactor(configurator): Update ServiceDiscoveryConfigurator

If the discoveryClient is already configured, skip the fallback configuration

* refactor(cookbook-poc-portal): Update configuration of service discovery

* docs(changeset): setting minor for fusion-framewoerk-react

* fix(cli): adding correct entryPoint when building and serving

* docs: changeset for externalPlugin for vite

* docs(cli): vue-press adding command app manifest

* docs(vue-press): cli corrects typo in README

* fix(cli): env as required option to publish config, display error response in console

* fix(cli): using node fetch in cli

* fix(cli): remove dead code

* fix(cli): setting node-fetch agent to allow certificate error

* fix(cli): defineAppManifest returns Partial AppManifest

* fix(cli): removing https agent in node-fetch

* docs(cli): defineAppManifest example

* refactor(cli): export plugins

* fix(cli): resolving code review issues

* docs: changeset

---------

Co-authored-by: Odin Thomas Rochmann <[email protected]>

* fix(cli): app-proxy-plugin changin order of execution for paths (#2498)

* Fix/cli/appmanifest/app key (#2502)

* fix(cli): log response on isAppRegistered

* fix(cli): better spinner mesage

* fix(cli): using node fetch in is AppRegistered (#2505)

* fix(cli): better error handling of isAppRegistered (#2507)

* feat(app): Convert config to instance so that implementation of behavior is abstracted (#2508)

* feat(app): Convert config to instance so that implementation of behavior is abstracted

- create manifest for config validation
- create class for transpiling of config
- update cli to use the VO type of config

NOTE: direct access of endpoints if now deprecated!

BREAKING CHANGES: `endpoints` will be deprecate in future, applications should use `getEndpoint`

* refactor(app): Refactor AppConfig class to improve endpoint handling

* Feat/cli/upload config (#2506)

* feat(cli): added new command for upload-config

* docs: changeset for the new command upload-config

* fix(cli): eslint issues

* fix(cli): better error handling in isAppRegistered

* fix(cli): to many input params for isAppREgistered in  upload-export-config

* fix(cli): throwing error if app is not registrered

* fix(cli): error message when app is deleted

* refactor(cli): rework cli to support import without type module in package.json (#2509)

* refactor(cli): Remove "type" module from package.json

* fix/packages/modules (#2510)

* fix/packages/modules

* docs: changeset

* empty commit

* fix(cli): adding ApiConfigSchema zod type to zli (#2511)

* Revert "fix/packages/modules (#2510)"

This reverts commit 663bed8.

* Revert "refactor(cli): Remove "type" module from package.json"

This reverts commit 43b4cac.

* fix(cli): adding ApiConfigSchema zod type

* Revert "refactor(cli): rework cli to support import without type module in package.json (#2509)"

This reverts commit e2f0f5f.

* Delete packages/cli/TODO.md

---------

Co-authored-by: Øyvind Eikeland <[email protected]>
  • Loading branch information
odinr and eikeland authored Oct 15, 2024
1 parent f247332 commit e11ad64
Show file tree
Hide file tree
Showing 146 changed files with 5,029 additions and 1,841 deletions.
8 changes: 8 additions & 0 deletions .changeset/cuddly-spoons-itch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@equinor/fusion-framework-cli': minor
---

Introduced `proxyRequestLogger` to log proxy requests in the CLI.

- Show the request URL and method in the console when a proxy request is made.
- Show proxy response status code
10 changes: 10 additions & 0 deletions .changeset/curvy-lies-laugh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@equinor/fusion-framework-react-components-people-provider': patch
'@equinor/fusion-framework-cli': patch
---

Updating fusion-wc-person to fix issues when using selectedPerson = null in PersonSelect component.

Updated the following dependencies

- `@equinor/fusion-wc-person` from `^3.0.1` to `^3.0.3` in `packages/cli/package.json` and `packages/react/components/people-resolver/package.json`.
5 changes: 5 additions & 0 deletions .changeset/giant-points-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@equinor/fusion-framework-cli': patch
---

Generated base manifest from package will now include `StandardIncludeAssetExtensions` as `allowedExtensions`
5 changes: 5 additions & 0 deletions .changeset/good-wasps-exercise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@equinor/fusion-framework-docs': minor
---

Documenting the new CLI commands in vue-press.
20 changes: 20 additions & 0 deletions .changeset/hot-ears-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
'@equinor/fusion-framework-cli': minor
---

Create a plugin `externalPublicPlugin` to fix the issue with serving the `index.html` file from the specified external public directory. Vite mode `spa` will not serve the `index.html` file from the specified external public directory.

- Enhanced the middleware to intercept requests and serve the `index.html` file from the specified external public directory.
- Transformed the HTML using Vite's `transformIndexHtml` method.
- Applied appropriate content headers and additional configured headers before sending the response.

```typescript
const viteConfig = defineConfig({
// vite configuration
root: './src', // this where vite will look for the index.html file
plugins: [
// path which contains the index.html file
externalPublicPlugin('./my-portal'),
],
});
```
19 changes: 19 additions & 0 deletions .changeset/nervous-cougars-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
'@equinor/fusion-framework-cookbook-app-react-environment-variables': patch
'@equinor/fusion-framework-cookbook-app-react-bookmark-advanced': patch
'@equinor/fusion-framework-cookbook-app-react-feature-flag': patch
'@equinor/fusion-framework-cookbook-app-react-bookmark': patch
'@equinor/fusion-framework-cookbook-app-react-ag-grid': patch
'@equinor/fusion-framework-cookbook-app-react-context': patch
'@equinor/fusion-framework-cookbook-app-react-module': patch
'@equinor/fusion-framework-cookbook-app-react-people': patch
'@equinor/fusion-framework-cookbook-app-react-msal': patch
'@equinor/fusion-framework-cookbook-app-vanilla': patch
'@equinor/fusion-framework-cookbook-app-react': patch
'@equinor/fusion-framework-cookbook-app-react-context-custom-error': patch
'@equinor/fusion-framework-cookbook-app-react-router': patch
---

Cleaned up app config

Removed `app.config.*` from the cookbook apps to prevent confusion when using the cookbook apps as a template for new apps.
15 changes: 15 additions & 0 deletions .changeset/pink-laws-cough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'@equinor/fusion-framework-cli': minor
---

Updated commands in CLI to reflect purpose of the command:
- renamed `config` to `build-config` to generate build config of an application.
- renamed `pack`to `build-pack` to bundle an application.
- added `build-manifest` command to generate build manifest of an application.

> [!WARNING]
> Config callback for `manifest` and `config` now allows `void` return type.
> Return value from callback is now merged with default config instead of replacing it, this might be a breaking change for some applications.
> [!NOTE]
> This mean that `mergeAppConfig` and `mergeManifestConfig` functions are no longer needed and can be removed from the application.
64 changes: 64 additions & 0 deletions .changeset/quiet-scissors-breathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
'@equinor/fusion-framework-cli': minor
---

The `appProxyPlugin` is a Vite plugin designed to proxy requests to a Fusion app backend.
It sets up proxy rules for API and bundle requests and serves the app configuration and manifest based on the app key and version.

Key Features:

1. Proxy Configuration:

- Proxies API calls to the Fusion apps backend.
- Proxies bundle requests to the Fusion apps backend.
- Uses a base path `proxyPath` for proxying.
- Captures and reuses authorization tokens for asset requests.

2. **App Configuration and Manifest**:

- Serves the app configuration if the request matches the current app and version.
- Serves the app manifest if the request matches the current app.

3. **Middleware Setup**:
- Sets up middleware to handle requests for app configuration, manifest, and local bundles.

This plugin is used by the CLI for local development, but design as exportable for custom CLI to consume applications from other API`s

example configuration:
```typescript
const viteConfig = defineConfig({
// vite configuration
plugins: [
appProxyPlugin({
proxy: {
path: '/app-proxy',
target: 'https://fusion-s-apps-ci.azurewebsites.net/',
// optional callback when matched request is proxied
onProxyReq: (proxyReq, req, res) => {
proxyReq.on('response', (res) => { console.log(res.statusCode) });
},
},
// optional, but required for serving local app configuration, manifest and resources
app: {
key: 'my-app',
version: '1.0.0',
generateConfig: async () => ({}),
generateManifest: async () => ({}),
},
}),
],
});
```

example usage:
```typescript
// Example API calls
fetch('/app-proxy/apps/my-app/builds/1.0.0/config'); // local
fetch('/app-proxy/apps/my-app/builds/0.0.9/config'); // proxy
fetch('/app-proxy/apps/other-app/builds/1.0.0/config'); // proxy

// Example asset calls
fetch('/app-proxy/bundles/my-app/builds/1.0.0/index.js'); // local
fetch('/app-proxy/bundles/my-app/builds/0.0.9/index.js'); // proxy
```

84 changes: 84 additions & 0 deletions .changeset/rare-carrots-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
"@equinor/fusion-framework-cli": major
---

Adding new commands for app management, `build-publish`, `build-pack`, `build-upload`, `build-config`, `build-manifest` and `build-tag`.

Introduces new parameters to the `build-config` command for publishing the app config to a build version.

Commands:

- `build-pack` - Bundle the app for distribution
- `-o, --output <output>` - Output directory for the packed app
- `-a, --archive` - Archive name for the packed app
- `build-upload` - Upload the packed app to the Fusion App Store
- `-b, --bundle <bundle>` - Path to the packed app bundle
- `-e, --env <ci | fqa | tr | fprd>` - Environment to upload the app to
- `-s, --service <service>` - Custom app service
- `build-tag` - Tag the uploaded app with a version
- `-t, --tag <tag>` - Tag to apply to the uploaded app
- `-v, --version <version>` - Version to attach to the tag
- `-e, --env <ci | fqa | tr | fprd>` - Environment to tag the app in
- `-s, --service <service>` - Custom app service
- `build-publish` - Publish the app config to a build version
- `-t, --tag <tag>` - Tag to apply to the uploaded app
- `-e, --env <ci | fqa | tr | fprd>` - Environment to tag the app in
- `-s, --service <service>` - Custom app service
- `build-config` - Generate app config for an environment
- `-o, --output <output>` - Output file for the app config
- `-c, --config <config>` - Path to the app config file (for config generation)
- `-p, --publish` - Flag for upload the generated config
- `-v, --version<semver | current | latest | preview>` - Publish the app config to version
- `-e, --env <ci | fqa | tr | fprd>` - Environment to publish the app config to
- `-s, --service <service>` - Custom app service
- `upload-config` - Upload the app config to a build version
- `-c, --config <config>` - Path to the app config json file to upload
- `-p, --publish<semver | current | latest | preview>` - Publish the app config to the build version
- `-e, --env <ci | fqa | tr | fprd>` - Environment to publish the app config to
- `-s, --service <service>` - Custom app service
- `build-manifest` - Creates the build manifest to publish with app
- `-o, --output <output>` - Output file for manifest
- `-c, --config <config>` - Manifest config file

simple usage:
```sh
fusion-framework-cli app build-publish -e ci
```

complex usage:
```sh
fusion-framework-cli app build-pack -o ./dist -a my-app.zip
fusion-framework-cli app build-upload -b ./dist/my-app.zip -e ci
fusion-framework-cli app build-tag -t my-tag -v 1.0.0 -e ci
```

After publishing a build of an app, the app config should be uploaded to the build version. This is done by running the `build-config` command.

```sh
# Publish the app config to the build version
fusion-framework-cli app build-config -p -e ci

# Publish the app config to a specific build tag
fusion-framework-cli app build-config -p preview -e ci

# Publish the app config to a specific build version
fusion-framework-cli app build-config -p 1.0.0 -e ci
```

__breaking changes:__

- renaming all commands accociated with build.
- The app-config endpoints is now an object containing url and scopes, where name is the object key:

```ts
environment: {
myProp: 'foobar',
},
endpoints: {
api: {
url: 'https://foo.bars'
scopes: ['foobar./default']
},
},
```
- The `config` command has been removed, use `build-config` instead
5 changes: 5 additions & 0 deletions .changeset/real-kiwis-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@equinor/fusion-framework-cli': minor
---

when building an application the `AppAssetExportPlugin` is now added to the `ViteConfig` and configure to include `manifest.build.allowedExtensions`
65 changes: 65 additions & 0 deletions .changeset/slimy-buses-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
'@equinor/fusion-framework-module-app': major
'@equinor/fusion-framework-react-app': patch
'@equinor/fusion-framework-react-components-bookmark': patch
'@equinor/fusion-framework-react': minor
'@equinor/fusion-framework-legacy-interopt': patch
---

Adjusted module to the new app service API.

> [!WARNING]
> This will introduce breaking changes to the configuration of `AppConfigurator.client`.
**Added**

- Introduced `AppClient` class to handle application manifest and configuration queries.
- Added `zod` to validate the application manifest.

**Changed**

- Updated `AppModuleProvider` to use `AppClient` for fetching application manifests and configurations.
- Modified `AppConfigurator` to utilize `AppClient` for client configuration.
- Updated `useApps` hook with new input parameter for `filterByCurrentUser` in `fusion-framework-react`.

**Migration**

before:

```ts
configurator.setClient({
getAppManifest: {
client: {
fn: ({ appKey }) => httpClient.json$<ApiApp>(`/apps/${appKey}`),
},
key: ({ appKey }) => appKey,
},
getAppManifests: {
client: {
fn: () => httpClient.json$<ApiApp[]>(`/apps`),
},
key: () => `all-apps`,
},
getAppConfig: {
client: {
fn: ({ appKey }) => httpClient.json$<ApiApp>(`/apps/${appKey}/config`),
},
key: ({ appKey }) => appKey,
},
});
```

after:

```ts
import { AppClient } from `@equinor/fusion-framework-module-app`;
configurator.setClient(new AppClient());
```

custom client implementation:

```ts
import { AppClient } from `@equinor/fusion-framework-module-app`;
class CustomAppClient implements IAppClient { ... }
configurator.setClient(new CustomAppClient());
```
22 changes: 22 additions & 0 deletions .changeset/slow-apes-remember.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
'@equinor/fusion-framework-cli': minor
---

**App Assets Export Plugin**

Create a plugin that exports assets from the app's source code.
This plugin resolves the issue where assets are not extracted from the app's source code since the app is in `lib` mode.

```typescript
export default {
plugins: [
AppAssetExportPlugin(
include: createExtensionFilterPattern(
manifest.build.allowedExtensions
),
),
]
}
```

see readme for more information.
13 changes: 0 additions & 13 deletions cookbooks/app-react-ag-grid/app.config.js

This file was deleted.

7 changes: 7 additions & 0 deletions cookbooks/app-react-assets/app.manifest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineAppManifest } from '@equinor/fusion-framework-cli';

export default defineAppManifest(async (env) => {
return {
appKey: 'test-assets',
};
});
24 changes: 24 additions & 0 deletions cookbooks/app-react-assets/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "@equinor/fusion-framework-cookbook-app-react-assets",
"version": "0.0.1",
"description": "",
"private": true,
"type": "module",
"main": "src/index.ts",
"scripts": {
"build": "fusion-framework-cli app build",
"dev": "fusion-framework-cli app dev",
"docker": "cd .. && sh docker-script.sh app-react"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@equinor/fusion-framework-cli": "workspace:^",
"@equinor/fusion-framework-react-app": "workspace:^",
"@types/react": "^18.2.50",
"@types/react-dom": "^18.2.7",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "^5.5.4"
}
}
9 changes: 9 additions & 0 deletions cookbooks/app-react-assets/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import memeUrl from './mount_batur.jpg';

export const App = () => (
<div>
<img src={memeUrl} />
</div>
);

export default App;
Loading

0 comments on commit e11ad64

Please sign in to comment.