Client-side TypeScript support (#824)

* Add typescript support to generator

* Change jsconfig to tsconfig for client

* Change Main.js to Main.tsx

* Implement TypeScript support on client

* Fix formatting

* Fixes in actions/index.ts

* Remove extra types from comments

* Update waspc/src/Wasp/Generator/ServerGenerator.hs

Co-authored-by: Martin Šošić <Martinsos@users.noreply.github.com>

* Remove server-specific typescript stuff

* Fix useQuery type

* Add typeroots to client tsconfig

* Improve TypeScript support on client

* Remove tsx extension from template wasp file

* Format code

Co-authored-by: Martin Šošić <Martinsos@users.noreply.github.com>
This commit is contained in:
Filip Sodić 2022-11-24 14:28:37 +01:00 committed by GitHub
parent ccf3c7199f
commit 31df276a25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 252 additions and 116 deletions

View File

@ -99,7 +99,7 @@ writeMainWaspFile waspProjectDir (ProjectInfo projectName appName) = writeFile a
"",
"route RootRoute { path: \"/\", to: MainPage }",
"page MainPage {",
" component: import Main from \"@client/MainPage.js\"",
" component: import Main from \"@client/MainPage\"",
"}"
]

View File

@ -0,0 +1,60 @@
declare module '*.avif' {
const src: string;
export default src;
}
declare module '*.bmp' {
const src: string;
export default src;
}
declare module '*.gif' {
const src: string;
export default src;
}
declare module '*.jpg' {
const src: string;
export default src;
}
declare module '*.jpeg' {
const src: string;
export default src;
}
declare module '*.png' {
const src: string;
export default src;
}
declare module '*.webp' {
const src: string;
export default src;
}
declare module '*.svg' {
import * as React from 'react';
export const ReactComponent: React.FunctionComponent<React.SVGProps<
SVGSVGElement
> & { title?: string }>;
const src: string;
export default src;
}
declare module '*.module.css' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.module.scss' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.module.sass' {
const classes: { readonly [key: string]: string };
export default classes;
}

View File

@ -1,5 +1,15 @@
{
"compilerOptions": {
// JSX support
"jsx": "preserve",
// Enable default imports in TypeScript.
"esModuleInterop": true,
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
// The following settings enable IDE support in user-provided source files.
// Editing them might break features like import autocompletion and
// definition lookup. Don't change them unless you know what you're doing.
@ -22,6 +32,8 @@
// definitions.
"node_modules/@types/*"
]
}
},
// Correctly resolve types: https://www.typescriptlang.org/tsconfig#typeRoots
"typeRoots": ["../../.wasp/out/web-app/node_modules/@types"]
}
}

View File

@ -0,0 +1,3 @@
export type Action<Input, Output> = (args?: Input) => Promise<Output>;
export function createAction<Input, Output>(actionRoute: string, entitiesUsed: unknown[]): Action<Input, Output>

View File

@ -1,93 +1,57 @@
import {
QueryClient,
QueryKey,
useMutation,
UseMutationOptions,
useQueryClient,
} from '@tanstack/react-query'
export { configureQueryClient } from '../queryClient'
import { Action } from './core';
/**
* An options object passed into the `useAction` hook and used to enhance the
* action with extra options.
*
* @typedef {Object} ActionOptions
* @property {PublicOptimisticUpdateDefinition[]} optimisticUpdates
*/
export type ActionOptions<ActionInput, CacheItem> = {
optimisticUpdates: OptimisticUpdateDefinition<ActionInput, CacheItem>[];
}
/**
* A documented (public) way to define optimistic updates.
*
* @typedef {Object} PublicOptimisticUpdateDefinition
* @property {GetQuerySpecifier} querySpecifier
* @property {UpdateQuery} updateQuery
*/
export type OptimisticUpdateDefinition<ActionInput, CacheItem> = {
getQuerySpecifier: GetQuerySpecifier<ActionInput>;
updateQuery: UpdateQuery<ActionInput, CacheItem>;
}
/**
* A function that takes an item and returns a Wasp Query specifier.
*
* @callback GetQuerySpecifier
* @param {T} item
* @returns {QuerySpecifier}
*/
export type GetQuerySpecifier<Item> = (item: Item) => QuerySpecifier
/**
* A function that takes an item and the previous state of the cache, and returns
* the desired (new) state of the cache.
*
* @callback UpdateQuery
* @param {T} item
* @param {T[]} oldData
* @returns {T[]}
*/
export type UpdateQuery<ActionInput, CacheItem> = (item: ActionInput, oldData: CacheItem[]) => CacheItem[]
/**
* A public query specifier used for addressing Wasp queries. See our docs for details:
* https://wasp-lang.dev/docs/language/features#the-useaction-hook.
*
* @typedef {any[]} QuerySpecifier
*/
/**
* An internal (undocumented, private, desugared) way of defining optimistic updates.
*
* @typedef {Object} InternalOptimisticUpdateDefinition
* @property {GetQuerySpecifier} querySpecifier
* @property {UpdateQuery} updateQuery
*/
/**
* An UpdateQuery function "instantiated" with a specific item. It only takes
* the current state of the cache and returns the desired (new) state of the
* cache.
*
* @callback SpecificUpdateQuery
* @param {any[]} oldData
*/
/**
* A specific, "instantiated" optimistic update definition which contains a
* fully-constructed query key and a specific update function.
*
* @typedef {Object} SpecificOptimisticUpdateDefinition
* @property {QueryKey} queryKey
* @property {SpecificUpdateQuery} updateQuery
*/
/**
* An array React Query uses to address queries. See their docs for details:
* https://react-query-v3.tanstack.com/guides/query-keys#array-keys.
*
* @typedef {any[]} QueryKey
*/
export type QuerySpecifier = any[]
/**
* A hook for adding extra behavior to a Wasp Action (e.g., optimistic updates).
*
* @param actionFn The Wasp Action you wish to enhance/decorate.
* @param {ActionOptions} actionOptions An options object for enhancing/decorating the given Action.
* @param actionOptions An options object for enhancing/decorating the given Action.
* @returns A decorated Action with added behavior but an unchanged API.
*/
export function useAction(actionFn, actionOptions) {
export function useAction<ActionInput, CacheItem = any>(
actionFn: (item: ActionInput) => Promise<void>,
actionOptions: ActionOptions<ActionInput, CacheItem>
): typeof actionFn {
const queryClient = useQueryClient();
let mutationFn = actionFn
@ -110,14 +74,49 @@ export function useAction(actionFn, actionOptions) {
}
/**
* Translates/Desugars a public optimistic update definition object into a definition object our
* system uses internally.
*
* @param {PublicOptimisticUpdateDefinition} publicOptimisticUpdateDefinition An optimistic update definition
* object that's a part of the public API: https://wasp-lang.dev/docs/language/features#the-useaction-hook.
* @returns {InternalOptimisticUpdateDefinition} An internally-used optimistic update definition object.
* An internal (undocumented, private, desugared) way of defining optimistic updates.
*/
function translateToInternalDefinition(publicOptimisticUpdateDefinition) {
type InternalOptimisticUpdateDefinition<ActionInput, CacheItem> = {
getQueryKey: (item: ActionInput) => QueryKey,
updateQuery: UpdateQuery<ActionInput, CacheItem>;
}
/**
* An UpdateQuery function "instantiated" with a specific item. It only takes
* the current state of the cache and returns the desired (new) state of the
* cache.
*/
type SpecificUpdateQuery<Item> = (oldData: Item[]) => Item[]
/**
* A specific, "instantiated" optimistic update definition which contains a
* fully-constructed query key and a specific update function.
*/
type SpecificOptimisticUpdateDefinition<Item> = {
queryKey: QueryKey;
updateQuery: SpecificUpdateQuery<Item>;
}
type InternalAction<Input, Output> = Action<Input, Output> & {
internal<CacheItem extends unknown>(
item: Input,
optimisticUpdateDefinitions: SpecificOptimisticUpdateDefinition<CacheItem>[]
): Promise<Output>
}
/**
* Translates/Desugars a public optimistic update definition object into a
* definition object our system uses internally.
*
* @param publicOptimisticUpdateDefinition An optimistic update definition
* object that's a part of the public API:
* https://wasp-lang.dev/docs/language/features#the-useaction-hook.
* @returns An internally-used optimistic update definition object.
*/
function translateToInternalDefinition<Item, CacheItem>(
publicOptimisticUpdateDefinition: OptimisticUpdateDefinition<Item, CacheItem>
): InternalOptimisticUpdateDefinition<Item, CacheItem> {
const { getQuerySpecifier, updateQuery } = publicOptimisticUpdateDefinition
const definitionErrors = []
@ -141,17 +140,19 @@ function translateToInternalDefinition(publicOptimisticUpdateDefinition) {
* Creates a function that performs an action while telling it about the
* optimistic updates it caused.
*
* @param actionFn - The Wasp Action.
* @param {InternalOptimisticUpdateDefinition} optimisticUpdateDefinitions - The optimisitc updates the
* action causes.
* @returns A
* @param actionFn The Wasp Action.
* @param optimisticUpdateDefinitions The optimisitc updates the action causes.
* @returns An decorated action which performs optimistic updates.
*/
function makeOptimisticUpdateMutationFn(actionFn, optimisticUpdateDefinitions) {
function makeOptimisticUpdateMutationFn<Input, Output, CacheItem>(
actionFn: Action<Input, Output>,
optimisticUpdateDefinitions: InternalOptimisticUpdateDefinition<Input, CacheItem>[]
): typeof actionFn {
return function performActionWithOptimisticUpdates(item) {
const specificOptimisticUpdateDefinitions = optimisticUpdateDefinitions.map(
generalDefinition => getOptimisticUpdateDefinitionForSpecificItem(generalDefinition, item)
)
return actionFn.internal(item, specificOptimisticUpdateDefinitions)
return (actionFn as InternalAction<Input, Output>).internal(item, specificOptimisticUpdateDefinitions)
}
}
@ -163,13 +164,18 @@ function makeOptimisticUpdateMutationFn(actionFn, optimisticUpdateDefinitions) {
* optimistic updates definition. Check their docs for details:
* https://tanstack.com/query/v4/docs/guides/optimistic-updates?from=reactQueryV3&original=https://react-query-v3.tanstack.com/guides/optimistic-updates
*
* @param {Object} queryClient The QueryClient instance used by React Query.
* @param {InternalOptimisticUpdateDefinition} optimisticUpdateDefinitions A list containing internal optimistic updates definition objects
* (i.e., a list where each object carries the instructions for performing particular optimistic update).
* @returns {Object} An object containing 'onMutate' and 'onError' functions corresponding to the given optimistic update
* definitions (check the docs linked above for details).
* @param queryClient The QueryClient instance used by React Query.
* @param optimisticUpdateDefinitions A list containing internal optimistic
* updates definition objects (i.e., a list where each object carries the
* instructions for performing particular optimistic update).
* @returns An object containing 'onMutate' and 'onError' functions
* corresponding to the given optimistic update definitions (check the docs
* linked above for details).
*/
function makeRqOptimisticUpdateOptions(queryClient, optimisticUpdateDefinitions) {
function makeRqOptimisticUpdateOptions<ActionInput, CacheItem>(
queryClient: QueryClient,
optimisticUpdateDefinitions: InternalOptimisticUpdateDefinition<ActionInput, CacheItem>[]
): Pick<UseMutationOptions, "onMutate" | "onError"> {
async function onMutate(item) {
const specificOptimisticUpdateDefinitions = optimisticUpdateDefinitions.map(
generalDefinition => getOptimisticUpdateDefinitionForSpecificItem(generalDefinition, item)
@ -187,7 +193,7 @@ function makeRqOptimisticUpdateOptions(queryClient, optimisticUpdateDefinitions)
const previousData = new Map()
specificOptimisticUpdateDefinitions.forEach(({ queryKey, updateQuery }) => {
// Snapshot the currently cached value.
const previousDataForQuery = queryClient.getQueryData(queryKey)
const previousDataForQuery: CacheItem[] = queryClient.getQueryData(queryKey)
// Attempt to optimistically update the cache using the new value.
try {
@ -227,13 +233,16 @@ function makeRqOptimisticUpdateOptions(queryClient, optimisticUpdateDefinitions)
* uses a closure over the updated item to construct an item-specific query key
* (e.g., useful when the query key depends on an ID).
*
* @param {InternalOptimisticUpdateDefinition} optimisticUpdateDefinition The general, "uninstantiated" optimistic
* @param optimisticUpdateDefinition The general, "uninstantiated" optimistic
* update definition with a function for constructing the query key.
* @param item The item triggering the Action/optimistic update (i.e., the argument passed to the Action).
* @returns {SpecificOptimisticUpdateDefinition} A specific optimistic update definition
* which corresponds to the provided definition and closes over the provided item.
* @param item The item triggering the Action/optimistic update (i.e., the
* argument passed to the Action).
* @returns A specific optimistic update definition which corresponds to the
* provided definition and closes over the provided item.
*/
function getOptimisticUpdateDefinitionForSpecificItem(optimisticUpdateDefinition, item) {
function getOptimisticUpdateDefinitionForSpecificItem<ActionInput, CacheItem>(
optimisticUpdateDefinition: InternalOptimisticUpdateDefinition<ActionInput, CacheItem>, item: ActionInput
): SpecificOptimisticUpdateDefinition<CacheItem> {
const { getQueryKey, updateQuery } = optimisticUpdateDefinition
return {
queryKey: getQueryKey(item),
@ -244,11 +253,11 @@ function getOptimisticUpdateDefinitionForSpecificItem(optimisticUpdateDefinition
/**
* Translates a Wasp query specifier to a query cache key used by React Query.
*
* @param {QuerySpecifier} querySpecifier A query specifier that's a part of the public API:
* @param querySpecifier A query specifier that's a part of the public API:
* https://wasp-lang.dev/docs/language/features#the-useaction-hook.
* @returns {QueryKey} A cache key React Query internally uses for addressing queries.
* @returns A cache key React Query internally uses for addressing queries.
*/
function getRqQueryKeyFromSpecifier(querySpecifier) {
function getRqQueryKeyFromSpecifier(querySpecifier: QuerySpecifier): QueryKey {
const [queryFn, ...otherKeys] = querySpecifier
return [...queryFn.queryCacheKey, ...otherKeys]
}

View File

@ -0,0 +1,3 @@
export type Query<Input, Output> = (args: Input) => Promise<Output>
export function createQuery<Input, Output>(queryRoute: string, entitiesUsed: any[]): Query<Input, Output>

View File

@ -0,0 +1,8 @@
import { UseQueryResult } from "@tanstack/react-query";
import { OutputHTMLAttributes } from "react";
import { Query } from "./core";
export function useQuery<Input, Output>(
queryFn: Query<Input, Output>,
queryFnArgs?: Input, options?: any
): UseQueryResult<Output, any>

View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "es2018",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}

View File

@ -3,12 +3,14 @@ module Wasp.Generator.ExternalCodeGenerator
)
where
import Data.Maybe (mapMaybe)
import StrongPath (File', Path', Rel, (</>))
import qualified StrongPath as SP
import qualified System.FilePath as FP
import qualified Wasp.AppSpec.ExternalCode as EC
import qualified Wasp.Generator.ExternalCodeGenerator.Common as C
import Wasp.Generator.ExternalCodeGenerator.Js (generateJsFile)
import Wasp.Generator.ExternalCodeGenerator.Js (genSourceFile)
import Wasp.Generator.FileDraft (FileDraft)
import qualified Wasp.Generator.FileDraft as FD
import Wasp.Generator.Monad (Generator)
@ -18,17 +20,22 @@ genExternalCodeDir ::
C.ExternalCodeGeneratorStrategy ->
[EC.File] ->
Generator [FD.FileDraft]
genExternalCodeDir strategy = mapM (genFile strategy)
genExternalCodeDir strategy = sequence . mapMaybe (genFile strategy)
genFile :: C.ExternalCodeGeneratorStrategy -> EC.File -> Generator FD.FileDraft
genFile :: C.ExternalCodeGeneratorStrategy -> EC.File -> Maybe (Generator FD.FileDraft)
genFile strategy file
| extension `elem` [".js", ".jsx"] = generateJsFile strategy file
| otherwise =
let relDstPath = C._extCodeDirInProjectRootDir strategy </> dstPathInGenExtCodeDir
absSrcPath = EC.fileAbsPath file
in return $ FD.createCopyFileDraft relDstPath absSrcPath
| fileName == "tsconfig.json" = Nothing
| extension `elem` [".js", ".jsx", ".ts", ".tsx"] = Just $ genSourceFile strategy file
| otherwise = Just $ genResourceFile strategy file
where
extension = FP.takeExtension filePath
fileName = FP.takeFileName filePath
filePath = SP.toFilePath $ EC.filePathInExtCodeDir file
genResourceFile :: C.ExternalCodeGeneratorStrategy -> EC.File -> Generator FileDraft
genResourceFile strategy file = return $ FD.createCopyFileDraft relDstPath absSrcPath
where
relDstPath = C._extCodeDirInProjectRootDir strategy </> dstPathInGenExtCodeDir
absSrcPath = EC.fileAbsPath file
dstPathInGenExtCodeDir :: Path' (Rel C.GeneratedExternalCodeDir) File'
dstPathInGenExtCodeDir = C.castRelPathFromSrcToGenExtCodeDir $ EC.filePathInExtCodeDir file
extension = FP.takeExtension $ SP.toFilePath $ EC.filePathInExtCodeDir file

View File

@ -1,5 +1,5 @@
module Wasp.Generator.ExternalCodeGenerator.Js
( generateJsFile,
( genSourceFile,
resolveJsFileWaspImportsForExtCodeDir,
)
where
@ -17,8 +17,8 @@ import qualified Wasp.Generator.ExternalCodeGenerator.Common as C
import qualified Wasp.Generator.FileDraft as FD
import Wasp.Generator.Monad (Generator)
generateJsFile :: C.ExternalCodeGeneratorStrategy -> EC.File -> Generator FD.FileDraft
generateJsFile strategy file = return $ FD.createTextFileDraft dstPath text'
genSourceFile :: C.ExternalCodeGeneratorStrategy -> EC.File -> Generator FD.FileDraft
genSourceFile strategy file = return $ FD.createTextFileDraft dstPath text'
where
filePathInSrcExtCodeDir = EC.filePathInExtCodeDir file

View File

@ -56,7 +56,7 @@ import Wasp.Util ((<++>))
genServer :: AppSpec -> Generator [FileDraft]
genServer spec =
sequence
[ genReadme,
[ genFileCopy [relfile|README.md|],
genPackageJson spec (npmDepsForWasp spec),
genNpmrc,
genGitignore
@ -68,6 +68,7 @@ genServer spec =
<++> genJobs spec
<++> genJobExecutors
<++> genPatches spec
where genFileCopy = return . C.mkTmplFd
genDotEnv :: AppSpec -> Generator [FileDraft]
genDotEnv spec = return $
@ -83,9 +84,6 @@ genDotEnv spec = return $
dotEnvInServerRootDir :: Path' (Rel C.ServerRootDir) File'
dotEnvInServerRootDir = [relfile|.env|]
genReadme :: Generator FileDraft
genReadme = return $ C.mkTmplFd (C.asTmplFile [relfile|README.md|])
genPackageJson :: AppSpec -> N.NpmDepsForWasp -> Generator FileDraft
genPackageJson spec waspDependencies = do
combinedDependencies <- N.genNpmDepsForPackage spec waspDependencies
@ -157,10 +155,10 @@ genGitignore =
genSrcDir :: AppSpec -> Generator [FileDraft]
genSrcDir spec =
sequence
[ copyTmplFile [relfile|app.js|],
copyTmplFile [relfile|utils.js|],
copyTmplFile [relfile|core/AuthError.js|],
copyTmplFile [relfile|core/HttpError.js|],
[ genFileCopy [relfile|app.js|],
genFileCopy [relfile|utils.js|],
genFileCopy [relfile|core/AuthError.js|],
genFileCopy [relfile|core/HttpError.js|],
genDbClient spec,
genConfigFile spec,
genServerJs spec
@ -170,7 +168,7 @@ genSrcDir spec =
<++> genOperations spec
<++> genAuth spec
where
copyTmplFile = return . C.mkSrcTmplFd
genFileCopy = return . C.mkSrcTmplFd
genDbClient :: AppSpec -> Generator FileDraft
genDbClient spec = return $ C.mkTmplFdWithDstAndData tmplFile dstFile (Just tmplData)

View File

@ -43,7 +43,8 @@ import Wasp.Util ((<++>))
genWebApp :: AppSpec -> Generator [FileDraft]
genWebApp spec = do
sequence
[ genReadme,
[ genFileCopy [relfile|README.md|],
genFileCopy [relfile|tsconfig.json|],
genPackageJson spec (npmDepsForWasp spec),
genNpmrc,
genGitignore,
@ -54,6 +55,8 @@ genWebApp spec = do
<++> genExternalCodeDir extClientCodeGeneratorStrategy (AS.externalClientFiles spec)
<++> genExternalCodeDir extSharedCodeGeneratorStrategy (AS.externalSharedFiles spec)
<++> genDotEnv spec
where
genFileCopy = return . C.mkTmplFd
genDotEnv :: AppSpec -> Generator [FileDraft]
genDotEnv spec = return $
@ -69,9 +72,6 @@ genDotEnv spec = return $
dotEnvInWebAppRootDir :: Path' (Rel C.WebAppRootDir) File'
dotEnvInWebAppRootDir = [relfile|.env|]
genReadme :: Generator FileDraft
genReadme = return $ C.mkTmplFd $ C.asTmplFile [relfile|README.md|]
genPackageJson :: AppSpec -> N.NpmDepsForWasp -> Generator FileDraft
genPackageJson spec waspDependencies = do
combinedDependencies <- N.genNpmDepsForPackage spec waspDependencies
@ -114,7 +114,13 @@ npmDepsForWasp spec =
-- See discussion here for more: https://github.com/wasp-lang/wasp/pull/621
N.waspDevDependencies =
AS.Dependency.fromList
[]
[ -- TODO: Allow users to choose whether they want to use TypeScript
-- in their projects and install these dependencies accordingly.
("typescript", "^4.8.4"),
("@types/react", "^18.0.25"),
("@types/react-dom", "^18.0.8"),
("@types/react-router-dom", "^5.3.3")
]
}
depsRequiredByTailwind :: AppSpec -> [AS.Dependency.Dependency]

View File

@ -40,15 +40,18 @@ genQueries spec =
mapM (genQuery spec) (AS.getQueries spec)
<++> return
[ C.mkSrcTmplFd [relfile|queries/index.js|],
C.mkSrcTmplFd [relfile|queries/core.js|]
C.mkSrcTmplFd [relfile|queries/index.d.ts|],
C.mkSrcTmplFd [relfile|queries/core.js|],
C.mkSrcTmplFd [relfile|queries/core.d.ts|]
]
genActions :: AppSpec -> Generator [FileDraft]
genActions spec =
mapM (genAction spec) (AS.getActions spec)
<++> return
[ C.mkSrcTmplFd [relfile|actions/index.js|],
C.mkSrcTmplFd [relfile|actions/core.js|]
[ C.mkSrcTmplFd [relfile|actions/index.ts|],
C.mkSrcTmplFd [relfile|actions/core.js|],
C.mkSrcTmplFd [relfile|actions/core.d.ts|]
]
genQuery :: AppSpec -> (String, AS.Query.Query) -> Generator FileDraft

View File

@ -33,6 +33,7 @@ data-files:
Generator/templates/server/npmrc
Generator/templates/**/*.prisma
Generator/templates/**/*.toml
Generator/templates/**/*.ts
Generator/templates/**/*.json
Generator/templates/**/*.ico
Generator/templates/**/*.html
@ -41,9 +42,10 @@ data-files:
Generator/templates/**/*.png
Cli/bash-completion
Cli/templates/**/*.css
Cli/templates/**/*.js
Cli/templates/**/*.json
Cli/templates/**/*.png
Cli/templates/**/*.ts
Cli/templates/**/*.tsx
Cli/templates/new/.gitignore
Cli/templates/new/.wasproot
Cli/templates/new/src/.waspignore