diff --git a/.changeset/fresh-hairs-join.md b/.changeset/fresh-hairs-join.md new file mode 100644 index 00000000..e42be70f --- /dev/null +++ b/.changeset/fresh-hairs-join.md @@ -0,0 +1,9 @@ +--- +"@dmno/astro-integration": patch +"@dmno/remix-integration": patch +"@dmno/vite-integration": patch +"@dmno/configraph": patch +"dmno": patch +--- + +refactor dmno server, perf improvements diff --git a/example-repo/.dmno/config.mts b/example-repo/.dmno/config.mts index 613c5bcc..5df257a1 100644 --- a/example-repo/.dmno/config.mts +++ b/example-repo/.dmno/config.mts @@ -18,9 +18,9 @@ const OnePassSecretsDev = new OnePasswordDmnoPlugin('1pass', { const BitwardenPlugin = new BitwardenSecretsManagerDmnoPlugin('bitwarden'); -const InfisicalPlugin = new InfisicalDmnoPlugin('infisical', { - environment: 'dev', -}); +// const InfisicalPlugin = new InfisicalDmnoPlugin('infisical', { +// environment: 'dev', +// }); const EncryptedVaultSecrets = new EncryptedVaultDmnoPlugin('vault/prod', { name: 'prod', key: inject() }); // const NonProdVault = new EncryptedVaultDmnoPlugin('vault/dev', { @@ -46,21 +46,21 @@ export default defineDmnoService({ typeDescription: 'standardized environment flag set by DMNO', value: (ctx) => ctx.get('NODE_ENV'), }, - INFISICAL_CLIENT_ID: { - extends: InfisicalTypes.clientId, - }, - INFISICAL_CLIENT_SECRET: { - extends: InfisicalTypes.clientSecret, - }, - INFISICAL_PROJECT_ID: { - extends: InfisicalTypes.projectId, - }, - TEST_KEY_ALL_ENVS: { - value: InfisicalPlugin.secret(), - }, - DEV_ONLY: { - value: InfisicalPlugin.secret(), - }, + // INFISICAL_CLIENT_ID: { + // extends: InfisicalTypes.clientId, + // }, + // INFISICAL_CLIENT_SECRET: { + // extends: InfisicalTypes.clientSecret, + // }, + // INFISICAL_PROJECT_ID: { + // extends: InfisicalTypes.projectId, + // }, + // TEST_KEY_ALL_ENVS: { + // value: InfisicalPlugin.secret(), + // }, + // DEV_ONLY: { + // value: InfisicalPlugin.secret(), + // }, REDACT_TEST: { sensitive: true, value: 'a a a a b b b b c c c c d d d', diff --git a/example-repo/packages/api/.dmno/config-example.ts b/example-repo/packages/api/.dmno/config-example.ts deleted file mode 100644 index 74af7372..00000000 --- a/example-repo/packages/api/.dmno/config-example.ts +++ /dev/null @@ -1,257 +0,0 @@ -import { - defineDmnoService, DmnoBaseTypes, configPath, dmnoFormula, switchBy, - createDmnoDataType, ValidationError, - EncryptedFileStorePlugin -} from 'dmno'; -import { OnePasswordDmnoPlugin } from '@dmno/1password-plugin'; - -// plugins can be used to create reusable functionality and can reference config items in their initialization -const encryptedSecrets = new EncryptedFileStorePlugin('vault', { name: 'local-secrets', key: configPath('LOCAL_SECRETS_KEY') }); - -// pre-configured plugins can be auto-injected from those that were initialized in the workspace root -const onePassSync = OnePasswordDmnoPlugin.injectInstance('1pass'); - -export default defineDmnoService({ - // each service can be explicitly named or will default to the name from its package.json - name: 'api', - - // explicitly set a parent service to nest them, otherwise everything is a child of the "root" workspace - // this affects the dependency graph of services and it affects "picking" logic (see below) - parent: 'group1', - - // config items can be "picked" from other services (in every service except the root) - // while picking from an ancestor, you can pick from _all_ config items in that service - // otherwise you can only pick items that have been marked as `expose: true` - pick: [ - // you can specify the source service name and key(s) to pick - { - source: 'root', - key: 'SINGLE_KEY', - }, - - // if source is omitted, it will fallback to the workspace root - { key: 'OTHER_KEY_FROM_ROOT' }, - - // shorthand to pick single key from root - 'SHORTHAND_PICK_FROM_ROOT', - - // you can pick multiple keys at once - { - source: 'other-service', - key: ['MULTIPLE', 'KEYS'], - }, - - // you can pick by filtering keys with a function - // (filters from all items or just exposed items depending if the source is an ancestor) - { - source: 'root', - key: (key) => key.startsWith('DB_'), - }, - - // keys can be transformed, and you can use a static value if picking a single key - { - key: 'ORIGINAL_KEY', - renameKey: 'NEW_KEY_NAME', - }, - - // or use a function if picking multiple - { - key: ['KEY1', 'KEY2'], - renameKey: (k) => `PREFIX_${k}`, - }, - - // values can also be transformed with functions - { - key: 'GROUP1_THINGY', - transformValue: (v) => v + 1, - }, - ], - - // services also define more config items relevant to only themselves and to be picked by others - schema: { - - // SETTING ITEM TYPE ----------------------------------------------------------------- - // the default method, where a datatype is called as a function with some settings - EXTENDS_TYPE_INITIALIZED: { - extends: DmnoBaseTypes.number({ min: 0, max: 100 }) - }, - // you can use a type that has not been initialized if no settings are needed - EXTENDS_TYPE_UNINITIALIZED: { - extends: DmnoBaseTypes.number - }, - // string/named format works for some basic types (string, number, boolean, etc) with no settings - EXTENDS_STRING: { - extends: 'number' - }, - // passing nothing will try to infer the type from a static value or fallback to a string otherwise - DEFAULTS_TO_NUMBER: { value: 42 }, // infers number - DEFAULTS_TO_STRING: { value: 'cool' }, // infers string - FALLBACK_TO_STRING_NO_INFO: { }, // assumes string - FALLBACK_TO_STRING_UNABLE_TO_INFER: { // assumes string - value: onePassSync.item('https://start.1password.com/open/i?a=I3GUA2KU6BD3FBHA47QNBIVEV4&v=ut2dftalm3ugmxc6klavms6tfq&i=bphvvrqjegfmd5yoz4buw2aequ&h=dmnoinc.1password.com'), - }, - - // an additional shorthand is provided for config items with no settings other than extends/type - // (although not recommended because attaching additional metadata/info is helpful) - SHORTHAND_TYPE_NAME: 'number', - SHORTHAND_TYPE_UNINITIALIZED: DmnoBaseTypes.number, - SHORTHAND_TYPE_INITIALIZED: DmnoBaseTypes.number({ min: 100 }), - - // and of course you can use custom types (see below), which can in turn extend other types - USE_CUSTOM_TYPE: { - extends: MyCustomPostgresConnectionUrlType, - // additional settings can be added/overridden as normal - required: true, - }, - - - // SETTING VALUES ----------------------------------------------------------------- - // config items can specify how to set their value within their schema - // so you can set sensible defaults, or even set all possible values and sync secrets securely with various backends - // overrides from .env file(s) and actual environment variables will also be applied - // and then coercion/validation logic will be run on the resolved value - - // values can be set to a static value - useful for constants and settings that will be overridden by env vars - STATIC_VAL: { - value: 'static' - }, - // or use a function that takes a ctx object that has other config item values available - FN_VAL: { - value: (ctx) => `prefix_${ctx.get('OTHER_ITEM')}` - }, - // a simple formula DSL is provided which handles common cases without needing to write a function at all - SET_BY_FORMULA2: { - value: dmnoFormula('prefix_{{ OTHER_ITEM }}'), - }, - // or synced with a secure backend using a plugin - SECRET_EXAMPLE: { - value: onePassSync.itemByReference("op://dev test/example/username"), - }, - - // or switched based on another value (often an env flag, but not always) - // and a "value resolver" can always return another resolver, which lets you easily compose functionality - // NOTE - it's easy to author your own reusable resolvers to create whatever functionality you need - SWITCHED_ENV_EXAMPLE: { - value: switchBy('NODE_ENV', { - _default: 'default-value', - staging: (ctx) => `${ctx.get('NODE_ENV')}-value`, - production: onePassSync.item("https://start.1password.com/open/i?a=I3GUA2KU6BD3FBHA47QNBIVEV4&v=ut2dftalm3ugmxc6klavms6tfq&i=bphvvrqjegfmd5yoz4buw2aequ&h=dmnoinc.1password.com"), - }), - }, - - // COMPLEX TYPES (object, arrays, maps) ////////////////////////// - OBJECT_EXAMPLE: { - extends: DmnoBaseTypes.object({ - CHILD1: { }, - CHILD2: { }, - }), - }, - - ARRAY_EXAMPLE: { - extends: DmnoBaseTypes.array({ - itemSchema: { - extends: 'number' - }, - minLength: 2, - }), - }, - - DICTIONARY_EXAMPLE: { - extends: DmnoBaseTypes.dictionary({ - itemSchema: { - extends: 'number' - }, - validateKeys: (key) => key.length === 2, - }), - }, - - // VALIDATIONS + COERCION ///////////////////////////////////////////////// - - // most validation logic will likely be handled by helpers on reusable types - // but sometimes you may need something more custom - // it will run _after_ the type (extends) defined validation(s) - VALIDATE_EXAMPLE: { - extends: DmnoBaseTypes.string({ isLength: 128, startsWith: 'pk_' }), - validate(val, ctx) { - // validations can use the ctx to access other values - if (ctx.get('NODE_ENV') === 'production') { - if (!val.startsWith('pk_live_')) { - // throw a ValidationError with a meaningful message - throw new ValidationError('production key must start with "pk_live_"'); - } - } - return true; - } - }, - - // async validations can be used if a validation needs to make async calls - // NOTE - these will be triggered on-demand rather than run constantly like regular validations - ASYNC_VALIDATE_EXAMPLE: { - asyncValidate: async (val, ctx) => { - // if the request succeeds, we know the value was ok - await fetch(`https://example.com/api/items/${val}`); - return true; - } - }, - - - // MORE SETTINGS ////////////////////////////////////////////// - KITCHEN_SINK: { - // some basic info will help within the UI and be included in generated ts types - // as well as help other devs understand what this env var is for :) - summary: 'short label', - description: 'longer description can go here', - // mark an item as required so it will fail validation if empty - required: true, - // mark an item as secret so we know it must be handled sensitively! - // for example, it will not be logged or injected into front-end builds - sensitive: true, - // understand when this value is used, which lets us parallelize run/deploy - // and know when a missing item should be considered a critical problem or be ignored - useAt: ['build', 'boot', 'deploy'], - - // opt-in out of build-time code replacements - dynamic: true, - // mark an item as being "exposed" for picking by other services - expose: true, - // override name when importing/exporting into process.env - importEnvKey: 'IMPORT_FROM_THIS_VAR', - exportEnvKey: 'EXPORT_AS_THIS_VAR', - }, - }, -}); - -// our custom type system allows you to build your own reusable types -// or to take other plugin/community defined types and tweak them as necessary -// internally a chain of "extends" types is stored and settings are resolved by walking up the chain -const MyCustomPostgresConnectionUrlType = createDmnoDataType({ - // you can extend one of our base types or another custom type... - extends: DmnoBaseTypes.url, - - // all normal config item settings are supported - sensitive: true, - - // a few docs related settings made for reusable types (although they can still be set directly on items) - // these will show up within the UI and generated types in various ways - typeDescription: 'Postgres connection url', - externalDocs: { - description: 'explanation from prisma docs', - url: 'https://www.prisma.io/dataguide/postgresql/short-guides/connection-uris#a-quick-overview' - }, - ui: { - // uses iconify names, see https://icones.js.org for options - icon: 'akar-icons:postgresql-fill', - color: '336791', // postgres brand color :) - }, - - // for validation/coercion, we walk up the chain and apply the functions from top to bottom - // for example, given the following type chain: - // - DmnoBaseTypes.string - makes sure the value is a string - // - DmnoBaseTypes.url - makes sure that string looks like a URL - // - PostgresConnectionUrlType - checks that url against some custom logic - validate(val, ctx) { - // TODO: check this url looks like a postgres connection url - }, - // but you can alter the exection order, or disable the parent altogether - runParentValidate: 'after', // set to `false` to disable running the parent's validate -}); diff --git a/example-repo/packages/api/package.json b/example-repo/packages/api/package.json index 95c41fdc..232798ab 100644 --- a/example-repo/packages/api/package.json +++ b/example-repo/packages/api/package.json @@ -25,7 +25,6 @@ "chalk": "^5.2.0", "debug": "^4.3.4", "dmno": "link:../../../packages/core", - "dotenv": "^16.0.3", "exit-hook": "^3.2.0", "glob": "^10.2.2", "ioredis": "^5.3.1", diff --git a/example-repo/packages/astro-web/astro.config.ts b/example-repo/packages/astro-web/astro.config.ts index ad1131e7..4f639adf 100644 --- a/example-repo/packages/astro-web/astro.config.ts +++ b/example-repo/packages/astro-web/astro.config.ts @@ -1,5 +1,4 @@ import dmnoAstroIntegration from '@dmno/astro-integration'; -import { unredact } from 'dmno'; import { defineConfig } from 'astro/config'; import vue from "@astrojs/vue"; import node from "@astrojs/node"; diff --git a/example-repo/packages/vite-dynamic/.dmno/config.mts b/example-repo/packages/vite-dynamic/.dmno/config.mts new file mode 100644 index 00000000..78184b8f --- /dev/null +++ b/example-repo/packages/vite-dynamic/.dmno/config.mts @@ -0,0 +1,13 @@ +import { DmnoBaseTypes, defineDmnoService } from 'dmno'; + +export default defineDmnoService({ + settings: { + dynamicConfig: 'default_static' + }, + schema: { + PUBLIC_STATIC: { value: 'public-static' }, + SECRET_STATIC: { value: 'secret-static', sensitive: true }, + PUBLIC_DYNAMIC: { value: 'public-dynamic', dynamic: true }, + SECRET_DYNAMIC: { value: 'secret-dynamic', dynamic: true, sensitive: true }, + }, +}); diff --git a/example-repo/packages/vite-dynamic/.dmno/tsconfig.json b/example-repo/packages/vite-dynamic/.dmno/tsconfig.json new file mode 100644 index 00000000..db661d7a --- /dev/null +++ b/example-repo/packages/vite-dynamic/.dmno/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "dmno/tsconfigs/dmno-folder", + "include": [ + "./**/*.mts", + "./.typegen/global.d.ts" + ] +} \ No newline at end of file diff --git a/example-repo/packages/vite-dynamic/index.html b/example-repo/packages/vite-dynamic/index.html new file mode 100644 index 00000000..0580bc81 --- /dev/null +++ b/example-repo/packages/vite-dynamic/index.html @@ -0,0 +1,16 @@ + + + + + + + + Vite App + + + +
+ + + + diff --git a/example-repo/packages/vite-dynamic/package.json b/example-repo/packages/vite-dynamic/package.json new file mode 100644 index 00000000..da3f36f8 --- /dev/null +++ b/example-repo/packages/vite-dynamic/package.json @@ -0,0 +1,24 @@ +{ + "name": "vite-dynamic", + "version": "1.0.0", + "description": "", + "main": "index.js", + "type": "module", + "scripts": { + "build": "vite build", + "boot": "dmno run -- node dist/main.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ ], + "author": "", + "license": "ISC", + "dependencies": { + "@dmno/vite-integration": "link:../../../packages/integrations/vite", + "dmno": "link:../../../packages/core", + "vite": "^5.4.10", + "vite-node": "^2.1.4" + }, + "devDependencies": { + "@types/node": "^20.12.7" + } +} diff --git a/example-repo/packages/vite-dynamic/src/dmno-env.d.ts b/example-repo/packages/vite-dynamic/src/dmno-env.d.ts new file mode 100644 index 00000000..5ec80415 --- /dev/null +++ b/example-repo/packages/vite-dynamic/src/dmno-env.d.ts @@ -0,0 +1,4 @@ +// inject DMNO_CONFIG global +/// +// inject DMNO_PUBLIC_CONFIG global +/// diff --git a/example-repo/packages/vite-dynamic/src/main.ts b/example-repo/packages/vite-dynamic/src/main.ts new file mode 100644 index 00000000..3f3d055d --- /dev/null +++ b/example-repo/packages/vite-dynamic/src/main.ts @@ -0,0 +1,18 @@ +import { injectDmnoGlobals } from "dmno/inject-globals" + +injectDmnoGlobals(); + +console.log({ + 'DMNO_CONFIG.PUBLIC_STATIC': DMNO_CONFIG.PUBLIC_STATIC, + 'DMNO_CONFIG.SECRET_STATIC': DMNO_CONFIG.SECRET_STATIC, + 'DMNO_CONFIG.PUBLIC_DYNAMIC': DMNO_CONFIG.PUBLIC_DYNAMIC, + 'DMNO_CONFIG.SECRET_DYNAMIC': DMNO_CONFIG.SECRET_DYNAMIC, +}); + +// no TS error - silently resolves to `undefined` +console.log(process.env.DOES_NOT_EXIST); +// TS error - throws helpful error +console.log(DMNO_PUBLIC_CONFIG.SECRET_STATIC); + + +process.exit(1); diff --git a/example-repo/packages/vite-dynamic/tsconfig.json b/example-repo/packages/vite-dynamic/tsconfig.json new file mode 100644 index 00000000..e89e1dda --- /dev/null +++ b/example-repo/packages/vite-dynamic/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "module": "Preserve", + "target": "ESNext", + "lib": [ + "ESNext" + ], + "strict": true, + "moduleResolution": "Bundler", + "allowSyntheticDefaultImports": true, + "outDir": "dist", + "rootDir": "./", + }, + "include": [ + "src/**/*.ts", + ] +} diff --git a/example-repo/packages/vite-dynamic/vite.config.ts b/example-repo/packages/vite-dynamic/vite.config.ts new file mode 100644 index 00000000..d66c7bf4 --- /dev/null +++ b/example-repo/packages/vite-dynamic/vite.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from 'vite' +import { injectDmnoConfigVitePlugin } from '@dmno/vite-integration'; + +export default defineConfig({ + plugins: [ + injectDmnoConfigVitePlugin({ injectSensitiveConfig: true }) + ], + build: { + emptyOutDir: false, + ssr: "./src/main.ts", + outDir: "dist", + // rollupOptions: { + // preserveSymlinks: true, + // }, + }, + // optimizeDeps: { + // include: ["auth0"], + // exclude: ["@rowm/lib-utils"] + // }, + // ssr: { + // noExternal: ["auth0", "uuid", "express-openid-connect", "jose"], + // }, +}) diff --git a/example-repo/pnpm-lock.yaml b/example-repo/pnpm-lock.yaml index be8559ef..1dad12d8 100644 --- a/example-repo/pnpm-lock.yaml +++ b/example-repo/pnpm-lock.yaml @@ -47,9 +47,6 @@ importers: dmno: specifier: link:../../../packages/core version: link:../../../packages/core - dotenv: - specifier: ^16.0.3 - version: 16.4.5 exit-hook: specifier: ^3.2.0 version: 3.2.0 @@ -152,7 +149,7 @@ importers: version: 10.9.2(@swc/core@1.7.3(@swc/helpers@0.5.5))(@types/node@20.14.13)(typescript@5.5.4) tsup: specifier: ^8.0.2 - version: 8.2.3(@swc/core@1.7.3(@swc/helpers@0.5.5))(jiti@1.21.6)(postcss@8.4.40)(typescript@5.5.4)(yaml@2.5.0) + version: 8.2.3(@swc/core@1.7.3(@swc/helpers@0.5.5))(jiti@1.21.6)(postcss@8.4.49)(typescript@5.5.4)(yaml@2.5.0) typescript: specifier: ^5.4.5 version: 5.5.4 @@ -170,7 +167,7 @@ importers: version: 8.3.2(astro@4.12.2(@types/node@20.14.13)(terser@5.31.3)(typescript@5.5.4)) '@astrojs/vue': specifier: ^4.1.0 - version: 4.5.0(astro@4.12.2(@types/node@20.14.13)(terser@5.31.3)(typescript@5.5.4))(rollup@4.19.1)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4)) + version: 4.5.0(astro@4.12.2(@types/node@20.14.13)(terser@5.31.3)(typescript@5.5.4))(rollup@4.26.0)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4)) '@dmno/astro-integration': specifier: link:../../../packages/integrations/astro version: link:../../../packages/integrations/astro @@ -252,7 +249,7 @@ importers: version: 8.4.40 tailwindcss: specifier: ^3.4.3 - version: 3.4.7(ts-node@10.9.2(@swc/core@1.7.3(@swc/helpers@0.5.5))(@types/node@20.14.13)(typescript@5.5.4)) + version: 3.4.7(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)) typescript: specifier: ^5.4.5 version: 5.5.4 @@ -261,7 +258,7 @@ importers: dependencies: nuxt: specifier: ^3.11.2 - version: 3.12.4(@parcel/watcher@2.4.1)(@types/node@20.14.13)(eslint@8.57.0)(ioredis@5.4.1)(magicast@0.3.4)(optionator@0.9.4)(rollup@4.19.1)(terser@5.31.3)(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))(vue-tsc@2.0.29(typescript@5.5.4)) + version: 3.12.4(@parcel/watcher@2.4.1)(@types/node@20.14.13)(eslint@8.57.0)(ioredis@5.4.1)(magicast@0.3.4)(optionator@0.9.4)(rollup@4.19.1)(terser@5.31.3)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.13)(terser@5.31.3))(vue-tsc@2.0.29(typescript@5.5.4)) vue: specifier: ^3.4.27 version: 3.4.34(typescript@5.5.4) @@ -301,7 +298,7 @@ importers: version: link:../../../packages/integrations/remix '@remix-run/dev': specifier: ^2.10.3 - version: 2.10.3(@remix-run/react@2.10.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4))(@remix-run/serve@2.10.3(typescript@5.5.4))(@types/node@20.14.13)(terser@5.31.3)(ts-node@10.9.2(@swc/core@1.7.3(@swc/helpers@0.5.5))(@types/node@20.14.13)(typescript@5.5.4))(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3)) + version: 2.10.3(@remix-run/react@2.10.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4))(@remix-run/serve@2.10.3(typescript@5.5.4))(@types/node@20.14.13)(terser@5.31.3)(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4))(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3)) '@types/react': specifier: ^18.2.20 version: 18.3.3 @@ -343,7 +340,7 @@ importers: version: 8.4.40 tailwindcss: specifier: ^3.4.4 - version: 3.4.7(ts-node@10.9.2(@swc/core@1.7.3(@swc/helpers@0.5.5))(@types/node@20.14.13)(typescript@5.5.4)) + version: 3.4.7(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)) typescript: specifier: ^5.5.4 version: 5.5.4 @@ -354,6 +351,25 @@ importers: specifier: ^4.3.2 version: 4.3.2(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3)) + packages/vite-dynamic: + dependencies: + '@dmno/vite-integration': + specifier: link:../../../packages/integrations/vite + version: link:../../../packages/integrations/vite + dmno: + specifier: link:../../../packages/core + version: link:../../../packages/core + vite: + specifier: ^5.4.10 + version: 5.4.11(@types/node@20.14.13)(terser@5.31.3) + vite-node: + specifier: ^2.1.4 + version: 2.1.5(@types/node@20.14.13)(terser@5.31.3) + devDependencies: + '@types/node': + specifier: ^20.12.7 + version: 20.14.13 + packages/webapp: dependencies: vue: @@ -1952,81 +1968,171 @@ packages: cpu: [arm] os: [android] + '@rollup/rollup-android-arm-eabi@4.26.0': + resolution: {integrity: sha512-gJNwtPDGEaOEgejbaseY6xMFu+CPltsc8/T+diUTTbOQLqD+bnrJq9ulH6WD69TqwqWmrfRAtUv30cCFZlbGTQ==} + cpu: [arm] + os: [android] + '@rollup/rollup-android-arm64@4.19.1': resolution: {integrity: sha512-thFUbkHteM20BGShD6P08aungq4irbIZKUNbG70LN8RkO7YztcGPiKTTGZS7Kw+x5h8hOXs0i4OaHwFxlpQN6A==} cpu: [arm64] os: [android] + '@rollup/rollup-android-arm64@4.26.0': + resolution: {integrity: sha512-YJa5Gy8mEZgz5JquFruhJODMq3lTHWLm1fOy+HIANquLzfIOzE9RA5ie3JjCdVb9r46qfAQY/l947V0zfGJ0OQ==} + cpu: [arm64] + os: [android] + '@rollup/rollup-darwin-arm64@4.19.1': resolution: {integrity: sha512-8o6eqeFZzVLia2hKPUZk4jdE3zW7LCcZr+MD18tXkgBBid3lssGVAYuox8x6YHoEPDdDa9ixTaStcmx88lio5Q==} cpu: [arm64] os: [darwin] + '@rollup/rollup-darwin-arm64@4.26.0': + resolution: {integrity: sha512-ErTASs8YKbqTBoPLp/kA1B1Um5YSom8QAc4rKhg7b9tyyVqDBlQxy7Bf2wW7yIlPGPg2UODDQcbkTlruPzDosw==} + cpu: [arm64] + os: [darwin] + '@rollup/rollup-darwin-x64@4.19.1': resolution: {integrity: sha512-4T42heKsnbjkn7ovYiAdDVRRWZLU9Kmhdt6HafZxFcUdpjlBlxj4wDrt1yFWLk7G4+E+8p2C9tcmSu0KA6auGA==} cpu: [x64] os: [darwin] + '@rollup/rollup-darwin-x64@4.26.0': + resolution: {integrity: sha512-wbgkYDHcdWW+NqP2mnf2NOuEbOLzDblalrOWcPyY6+BRbVhliavon15UploG7PpBRQ2bZJnbmh8o3yLoBvDIHA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.26.0': + resolution: {integrity: sha512-Y9vpjfp9CDkAG4q/uwuhZk96LP11fBz/bYdyg9oaHYhtGZp7NrbkQrj/66DYMMP2Yo/QPAsVHkV891KyO52fhg==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.26.0': + resolution: {integrity: sha512-A/jvfCZ55EYPsqeaAt/yDAG4q5tt1ZboWMHEvKAH9Zl92DWvMIbnZe/f/eOXze65aJaaKbL+YeM0Hz4kLQvdwg==} + cpu: [x64] + os: [freebsd] + '@rollup/rollup-linux-arm-gnueabihf@4.19.1': resolution: {integrity: sha512-MXg1xp+e5GhZ3Vit1gGEyoC+dyQUBy2JgVQ+3hUrD9wZMkUw/ywgkpK7oZgnB6kPpGrxJ41clkPPnsknuD6M2Q==} cpu: [arm] os: [linux] + '@rollup/rollup-linux-arm-gnueabihf@4.26.0': + resolution: {integrity: sha512-paHF1bMXKDuizaMODm2bBTjRiHxESWiIyIdMugKeLnjuS1TCS54MF5+Y5Dx8Ui/1RBPVRE09i5OUlaLnv8OGnA==} + cpu: [arm] + os: [linux] + '@rollup/rollup-linux-arm-musleabihf@4.19.1': resolution: {integrity: sha512-DZNLwIY4ftPSRVkJEaxYkq7u2zel7aah57HESuNkUnz+3bZHxwkCUkrfS2IWC1sxK6F2QNIR0Qr/YXw7nkF3Pw==} cpu: [arm] os: [linux] + '@rollup/rollup-linux-arm-musleabihf@4.26.0': + resolution: {integrity: sha512-cwxiHZU1GAs+TMxvgPfUDtVZjdBdTsQwVnNlzRXC5QzIJ6nhfB4I1ahKoe9yPmoaA/Vhf7m9dB1chGPpDRdGXg==} + cpu: [arm] + os: [linux] + '@rollup/rollup-linux-arm64-gnu@4.19.1': resolution: {integrity: sha512-C7evongnjyxdngSDRRSQv5GvyfISizgtk9RM+z2biV5kY6S/NF/wta7K+DanmktC5DkuaJQgoKGf7KUDmA7RUw==} cpu: [arm64] os: [linux] + '@rollup/rollup-linux-arm64-gnu@4.26.0': + resolution: {integrity: sha512-4daeEUQutGRCW/9zEo8JtdAgtJ1q2g5oHaoQaZbMSKaIWKDQwQ3Yx0/3jJNmpzrsScIPtx/V+1AfibLisb3AMQ==} + cpu: [arm64] + os: [linux] + '@rollup/rollup-linux-arm64-musl@4.19.1': resolution: {integrity: sha512-89tFWqxfxLLHkAthAcrTs9etAoBFRduNfWdl2xUs/yLV+7XDrJ5yuXMHptNqf1Zw0UCA3cAutkAiAokYCkaPtw==} cpu: [arm64] os: [linux] + '@rollup/rollup-linux-arm64-musl@4.26.0': + resolution: {integrity: sha512-eGkX7zzkNxvvS05ROzJ/cO/AKqNvR/7t1jA3VZDi2vRniLKwAWxUr85fH3NsvtxU5vnUUKFHKh8flIBdlo2b3Q==} + cpu: [arm64] + os: [linux] + '@rollup/rollup-linux-powerpc64le-gnu@4.19.1': resolution: {integrity: sha512-PromGeV50sq+YfaisG8W3fd+Cl6mnOOiNv2qKKqKCpiiEke2KiKVyDqG/Mb9GWKbYMHj5a01fq/qlUR28PFhCQ==} cpu: [ppc64] os: [linux] + '@rollup/rollup-linux-powerpc64le-gnu@4.26.0': + resolution: {integrity: sha512-Odp/lgHbW/mAqw/pU21goo5ruWsytP7/HCC/liOt0zcGG0llYWKrd10k9Fj0pdj3prQ63N5yQLCLiE7HTX+MYw==} + cpu: [ppc64] + os: [linux] + '@rollup/rollup-linux-riscv64-gnu@4.19.1': resolution: {integrity: sha512-/1BmHYh+iz0cNCP0oHCuF8CSiNj0JOGf0jRlSo3L/FAyZyG2rGBuKpkZVH9YF+x58r1jgWxvm1aRg3DHrLDt6A==} cpu: [riscv64] os: [linux] + '@rollup/rollup-linux-riscv64-gnu@4.26.0': + resolution: {integrity: sha512-MBR2ZhCTzUgVD0OJdTzNeF4+zsVogIR1U/FsyuFerwcqjZGvg2nYe24SAHp8O5sN8ZkRVbHwlYeHqcSQ8tcYew==} + cpu: [riscv64] + os: [linux] + '@rollup/rollup-linux-s390x-gnu@4.19.1': resolution: {integrity: sha512-0cYP5rGkQWRZKy9/HtsWVStLXzCF3cCBTRI+qRL8Z+wkYlqN7zrSYm6FuY5Kd5ysS5aH0q5lVgb/WbG4jqXN1Q==} cpu: [s390x] os: [linux] + '@rollup/rollup-linux-s390x-gnu@4.26.0': + resolution: {integrity: sha512-YYcg8MkbN17fMbRMZuxwmxWqsmQufh3ZJFxFGoHjrE7bv0X+T6l3glcdzd7IKLiwhT+PZOJCblpnNlz1/C3kGQ==} + cpu: [s390x] + os: [linux] + '@rollup/rollup-linux-x64-gnu@4.19.1': resolution: {integrity: sha512-XUXeI9eM8rMP8aGvii/aOOiMvTs7xlCosq9xCjcqI9+5hBxtjDpD+7Abm1ZhVIFE1J2h2VIg0t2DX/gjespC2Q==} cpu: [x64] os: [linux] + '@rollup/rollup-linux-x64-gnu@4.26.0': + resolution: {integrity: sha512-ZuwpfjCwjPkAOxpjAEjabg6LRSfL7cAJb6gSQGZYjGhadlzKKywDkCUnJ+KEfrNY1jH5EEoSIKLCb572jSiglA==} + cpu: [x64] + os: [linux] + '@rollup/rollup-linux-x64-musl@4.19.1': resolution: {integrity: sha512-V7cBw/cKXMfEVhpSvVZhC+iGifD6U1zJ4tbibjjN+Xi3blSXaj/rJynAkCFFQfoG6VZrAiP7uGVzL440Q6Me2Q==} cpu: [x64] os: [linux] + '@rollup/rollup-linux-x64-musl@4.26.0': + resolution: {integrity: sha512-+HJD2lFS86qkeF8kNu0kALtifMpPCZU80HvwztIKnYwym3KnA1os6nsX4BGSTLtS2QVAGG1P3guRgsYyMA0Yhg==} + cpu: [x64] + os: [linux] + '@rollup/rollup-win32-arm64-msvc@4.19.1': resolution: {integrity: sha512-88brja2vldW/76jWATlBqHEoGjJLRnP0WOEKAUbMcXaAZnemNhlAHSyj4jIwMoP2T750LE9lblvD4e2jXleZsA==} cpu: [arm64] os: [win32] + '@rollup/rollup-win32-arm64-msvc@4.26.0': + resolution: {integrity: sha512-WUQzVFWPSw2uJzX4j6YEbMAiLbs0BUysgysh8s817doAYhR5ybqTI1wtKARQKo6cGop3pHnrUJPFCsXdoFaimQ==} + cpu: [arm64] + os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.19.1': resolution: {integrity: sha512-LdxxcqRVSXi6k6JUrTah1rHuaupoeuiv38du8Mt4r4IPer3kwlTo+RuvfE8KzZ/tL6BhaPlzJ3835i6CxrFIRQ==} cpu: [ia32] os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.26.0': + resolution: {integrity: sha512-D4CxkazFKBfN1akAIY6ieyOqzoOoBV1OICxgUblWxff/pSjCA2khXlASUx7mK6W1oP4McqhgcCsu6QaLj3WMWg==} + cpu: [ia32] + os: [win32] + '@rollup/rollup-win32-x64-msvc@4.19.1': resolution: {integrity: sha512-2bIrL28PcK3YCqD9anGxDxamxdiJAxA+l7fWIwM5o8UqNy1t3d1NdAweO2XhA0KTDJ5aH1FsuiT5+7VhtHliXg==} cpu: [x64] os: [win32] + '@rollup/rollup-win32-x64-msvc@4.26.0': + resolution: {integrity: sha512-2x8MO1rm4PGEP0xWbubJW5RtbNLk3puzAMaLQd3B3JHVw4KcHlmXcO+Wewx9zCoo7EUFiMlu/aZbCJ7VjMzAag==} + cpu: [x64] + os: [win32] + '@rushstack/eslint-patch@1.10.4': resolution: {integrity: sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==} @@ -2173,6 +2279,9 @@ packages: '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/express-serve-static-core@4.19.5': resolution: {integrity: sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==} @@ -3267,6 +3376,15 @@ packages: supports-color: optional: true + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decode-named-character-reference@1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} @@ -5648,6 +5766,9 @@ packages: picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -5940,6 +6061,10 @@ packages: resolution: {integrity: sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==} engines: {node: ^10 || ^12 || >=14} + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + engines: {node: ^10 || ^12 || >=14} + posthog-node@2.6.0: resolution: {integrity: sha512-/BiFw/jwdP0uJSRAIoYqLoBTjZ612xv74b1L/a3T/p1nJVL8e0OrHuxbJW56c6WVW/IKm9gBF/zhbqfaz0XgJQ==} engines: {node: '>=15.0.0'} @@ -6319,6 +6444,11 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rollup@4.26.0: + resolution: {integrity: sha512-ilcl12hnWonG8f+NxU6BlgysVA0gvY2l8N0R84S1HcINbW20bvwuCngJkkInV6LXhwRpucsW5k1ovDwEdBVrNg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + run-applescript@7.0.0: resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} engines: {node: '>=18'} @@ -6461,6 +6591,10 @@ packages: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -6722,10 +6856,6 @@ packages: tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - tinyrainbow@1.2.0: - resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} - engines: {node: '>=14.0.0'} - to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} @@ -7155,8 +7285,8 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite-node@2.0.4: - resolution: {integrity: sha512-ZpJVkxcakYtig5iakNeL7N3trufe3M6vGuzYAr4GsbCTwobDeyPJpE4cjDhhPluv8OvQCFzu2LWp6GkoKRITXA==} + vite-node@2.1.5: + resolution: {integrity: sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -7251,6 +7381,37 @@ packages: terser: optional: true + vite@5.4.11: + resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + vitefu@0.2.5: resolution: {integrity: sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==} peerDependencies: @@ -7697,13 +7858,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/vue@4.5.0(astro@4.12.2(@types/node@20.14.13)(terser@5.31.3)(typescript@5.5.4))(rollup@4.19.1)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4))': + '@astrojs/vue@4.5.0(astro@4.12.2(@types/node@20.14.13)(terser@5.31.3)(typescript@5.5.4))(rollup@4.26.0)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4))': dependencies: '@vitejs/plugin-vue': 5.1.1(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4)) '@vitejs/plugin-vue-jsx': 4.0.0(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4)) '@vue/compiler-sfc': 3.4.34 astro: 4.12.2(@types/node@20.14.13)(terser@5.31.3)(typescript@5.5.4) - vite-plugin-vue-devtools: 7.3.7(rollup@4.19.1)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4)) + vite-plugin-vue-devtools: 7.3.7(rollup@4.26.0)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4)) vue: 3.4.34(typescript@5.5.4) transitivePeerDependencies: - '@nuxt/kit' @@ -8670,12 +8831,12 @@ snapshots: '@nuxt/devalue@2.0.2': {} - '@nuxt/devtools-kit@1.3.9(magicast@0.3.4)(rollup@4.19.1)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))': + '@nuxt/devtools-kit@1.3.9(magicast@0.3.4)(rollup@4.19.1)(vite@5.4.11(@types/node@20.14.13)(terser@5.31.3))': dependencies: '@nuxt/kit': 3.12.4(magicast@0.3.4)(rollup@4.19.1) '@nuxt/schema': 3.12.4(rollup@4.19.1) execa: 7.2.0 - vite: 5.3.5(@types/node@20.14.13)(terser@5.31.3) + vite: 5.4.11(@types/node@20.14.13)(terser@5.31.3) transitivePeerDependencies: - magicast - rollup @@ -8694,13 +8855,13 @@ snapshots: rc9: 2.1.2 semver: 7.6.3 - '@nuxt/devtools@1.3.9(rollup@4.19.1)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))': + '@nuxt/devtools@1.3.9(rollup@4.19.1)(vite@5.4.11(@types/node@20.14.13)(terser@5.31.3))': dependencies: '@antfu/utils': 0.7.10 - '@nuxt/devtools-kit': 1.3.9(magicast@0.3.4)(rollup@4.19.1)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3)) + '@nuxt/devtools-kit': 1.3.9(magicast@0.3.4)(rollup@4.19.1)(vite@5.4.11(@types/node@20.14.13)(terser@5.31.3)) '@nuxt/devtools-wizard': 1.3.9 '@nuxt/kit': 3.12.4(magicast@0.3.4)(rollup@4.19.1) - '@vue/devtools-core': 7.3.3(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3)) + '@vue/devtools-core': 7.3.3(vite@5.4.11(@types/node@20.14.13)(terser@5.31.3)) '@vue/devtools-kit': 7.3.3 birpc: 0.2.17 consola: 3.2.3 @@ -8729,9 +8890,9 @@ snapshots: simple-git: 3.25.0 sirv: 2.0.4 unimport: 3.9.1(rollup@4.19.1) - vite: 5.3.5(@types/node@20.14.13)(terser@5.31.3) - vite-plugin-inspect: 0.8.5(@nuxt/kit@3.12.4(magicast@0.3.4)(rollup@4.19.1))(rollup@4.19.1)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3)) - vite-plugin-vue-inspector: 5.1.3(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3)) + vite: 5.4.11(@types/node@20.14.13)(terser@5.31.3) + vite-plugin-inspect: 0.8.5(@nuxt/kit@3.12.4(magicast@0.3.4)(rollup@4.19.1))(rollup@4.19.1)(vite@5.4.11(@types/node@20.14.13)(terser@5.31.3)) + vite-plugin-vue-inspector: 5.1.3(vite@5.4.11(@types/node@20.14.13)(terser@5.31.3)) which: 3.0.1 ws: 8.18.0 transitivePeerDependencies: @@ -8813,8 +8974,8 @@ snapshots: dependencies: '@nuxt/kit': 3.12.4(magicast@0.3.4)(rollup@4.19.1) '@rollup/plugin-replace': 5.0.7(rollup@4.19.1) - '@vitejs/plugin-vue': 5.1.1(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4)) - '@vitejs/plugin-vue-jsx': 4.0.0(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4)) + '@vitejs/plugin-vue': 5.1.1(vite@5.4.11(@types/node@20.14.13)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4)) + '@vitejs/plugin-vue-jsx': 4.0.0(vite@5.4.11(@types/node@20.14.13)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4)) autoprefixer: 10.4.19(postcss@8.4.40) clear: 0.1.0 consola: 3.2.3 @@ -8840,9 +9001,9 @@ snapshots: ufo: 1.5.4 unenv: 1.10.0 unplugin: 1.12.0 - vite: 5.3.5(@types/node@20.14.13)(terser@5.31.3) - vite-node: 2.0.4(@types/node@20.14.13)(terser@5.31.3) - vite-plugin-checker: 0.7.2(eslint@8.57.0)(optionator@0.9.4)(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))(vue-tsc@2.0.29(typescript@5.5.4)) + vite: 5.4.11(@types/node@20.14.13)(terser@5.31.3) + vite-node: 2.1.5(@types/node@20.14.13)(terser@5.31.3) + vite-plugin-checker: 0.7.2(eslint@8.57.0)(optionator@0.9.4)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.13)(terser@5.31.3))(vue-tsc@2.0.29(typescript@5.5.4)) vue: 3.4.34(typescript@5.5.4) vue-bundle-renderer: 2.1.0 transitivePeerDependencies: @@ -8856,6 +9017,7 @@ snapshots: - optionator - rollup - sass + - sass-embedded - stylelint - stylus - sugarss @@ -8935,7 +9097,7 @@ snapshots: '@prisma/engines@4.16.2': {} - '@remix-run/dev@2.10.3(@remix-run/react@2.10.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4))(@remix-run/serve@2.10.3(typescript@5.5.4))(@types/node@20.14.13)(terser@5.31.3)(ts-node@10.9.2(@swc/core@1.7.3(@swc/helpers@0.5.5))(@types/node@20.14.13)(typescript@5.5.4))(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))': + '@remix-run/dev@2.10.3(@remix-run/react@2.10.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.4))(@remix-run/serve@2.10.3(typescript@5.5.4))(@types/node@20.14.13)(terser@5.31.3)(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4))(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))': dependencies: '@babel/core': 7.24.9 '@babel/generator': 7.25.0 @@ -8979,7 +9141,7 @@ snapshots: pidtree: 0.6.0 postcss: 8.4.40 postcss-discard-duplicates: 5.1.0(postcss@8.4.40) - postcss-load-config: 4.0.2(postcss@8.4.40)(ts-node@10.9.2(@swc/core@1.7.3(@swc/helpers@0.5.5))(@types/node@20.14.13)(typescript@5.5.4)) + postcss-load-config: 4.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)) postcss-modules: 6.0.0(postcss@8.4.40) prettier: 2.8.8 pretty-ms: 7.0.1 @@ -9003,6 +9165,7 @@ snapshots: - less - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color @@ -9167,54 +9330,116 @@ snapshots: optionalDependencies: rollup: 4.19.1 + '@rollup/pluginutils@5.1.0(rollup@4.26.0)': + dependencies: + '@types/estree': 1.0.5 + estree-walker: 2.0.2 + picomatch: 2.3.1 + optionalDependencies: + rollup: 4.26.0 + '@rollup/rollup-android-arm-eabi@4.19.1': optional: true + '@rollup/rollup-android-arm-eabi@4.26.0': + optional: true + '@rollup/rollup-android-arm64@4.19.1': optional: true + '@rollup/rollup-android-arm64@4.26.0': + optional: true + '@rollup/rollup-darwin-arm64@4.19.1': optional: true + '@rollup/rollup-darwin-arm64@4.26.0': + optional: true + '@rollup/rollup-darwin-x64@4.19.1': optional: true + '@rollup/rollup-darwin-x64@4.26.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.26.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.26.0': + optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.19.1': optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.26.0': + optional: true + '@rollup/rollup-linux-arm-musleabihf@4.19.1': optional: true + '@rollup/rollup-linux-arm-musleabihf@4.26.0': + optional: true + '@rollup/rollup-linux-arm64-gnu@4.19.1': optional: true + '@rollup/rollup-linux-arm64-gnu@4.26.0': + optional: true + '@rollup/rollup-linux-arm64-musl@4.19.1': optional: true + '@rollup/rollup-linux-arm64-musl@4.26.0': + optional: true + '@rollup/rollup-linux-powerpc64le-gnu@4.19.1': optional: true + '@rollup/rollup-linux-powerpc64le-gnu@4.26.0': + optional: true + '@rollup/rollup-linux-riscv64-gnu@4.19.1': optional: true + '@rollup/rollup-linux-riscv64-gnu@4.26.0': + optional: true + '@rollup/rollup-linux-s390x-gnu@4.19.1': optional: true + '@rollup/rollup-linux-s390x-gnu@4.26.0': + optional: true + '@rollup/rollup-linux-x64-gnu@4.19.1': optional: true + '@rollup/rollup-linux-x64-gnu@4.26.0': + optional: true + '@rollup/rollup-linux-x64-musl@4.19.1': optional: true + '@rollup/rollup-linux-x64-musl@4.26.0': + optional: true + '@rollup/rollup-win32-arm64-msvc@4.19.1': optional: true + '@rollup/rollup-win32-arm64-msvc@4.26.0': + optional: true + '@rollup/rollup-win32-ia32-msvc@4.19.1': optional: true + '@rollup/rollup-win32-ia32-msvc@4.26.0': + optional: true + '@rollup/rollup-win32-x64-msvc@4.19.1': optional: true + '@rollup/rollup-win32-x64-msvc@4.26.0': + optional: true + '@rushstack/eslint-patch@1.10.4': {} '@shikijs/core@1.12.0': @@ -9350,6 +9575,8 @@ snapshots: '@types/estree@1.0.5': {} + '@types/estree@1.0.6': {} + '@types/express-serve-static-core@4.19.5': dependencies: '@types/node': 20.14.13 @@ -9648,7 +9875,7 @@ snapshots: lodash: 4.17.21 mlly: 1.7.1 outdent: 0.8.0 - vite: 5.3.5(@types/node@20.14.13)(terser@5.31.3) + vite: 5.4.11(@types/node@20.14.13)(terser@5.31.3) vite-node: 1.6.0(@types/node@20.14.13)(terser@5.31.3) transitivePeerDependencies: - '@types/node' @@ -9656,6 +9883,7 @@ snapshots: - less - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color @@ -9691,11 +9919,26 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitejs/plugin-vue-jsx@4.0.0(vite@5.4.11(@types/node@20.14.13)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4))': + dependencies: + '@babel/core': 7.24.9 + '@babel/plugin-transform-typescript': 7.25.0(@babel/core@7.24.9) + '@vue/babel-plugin-jsx': 1.2.2(@babel/core@7.24.9) + vite: 5.4.11(@types/node@20.14.13)(terser@5.31.3) + vue: 3.4.34(typescript@5.5.4) + transitivePeerDependencies: + - supports-color + '@vitejs/plugin-vue@5.1.1(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4))': dependencies: vite: 5.3.5(@types/node@20.14.13)(terser@5.31.3) vue: 3.4.34(typescript@5.5.4) + '@vitejs/plugin-vue@5.1.1(vite@5.4.11(@types/node@20.14.13)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4))': + dependencies: + vite: 5.4.11(@types/node@20.14.13)(terser@5.31.3) + vue: 3.4.34(typescript@5.5.4) + '@volar/kit@2.4.0-alpha.18(typescript@5.5.4)': dependencies: '@volar/language-service': 2.4.0-alpha.18 @@ -9831,14 +10074,14 @@ snapshots: '@vue/devtools-api@6.6.3': {} - '@vue/devtools-core@7.3.3(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))': + '@vue/devtools-core@7.3.3(vite@5.4.11(@types/node@20.14.13)(terser@5.31.3))': dependencies: '@vue/devtools-kit': 7.3.3 '@vue/devtools-shared': 7.3.7 mitt: 3.0.1 nanoid: 3.3.7 pathe: 1.1.2 - vite-hot-client: 0.2.3(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3)) + vite-hot-client: 0.2.3(vite@5.4.11(@types/node@20.14.13)(terser@5.31.3)) transitivePeerDependencies: - vite @@ -10772,6 +11015,10 @@ snapshots: dependencies: ms: 2.1.2 + debug@4.3.7: + dependencies: + ms: 2.1.3 + decode-named-character-reference@1.0.2: dependencies: character-entities: 2.0.2 @@ -13862,10 +14109,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - nuxt@3.12.4(@parcel/watcher@2.4.1)(@types/node@20.14.13)(eslint@8.57.0)(ioredis@5.4.1)(magicast@0.3.4)(optionator@0.9.4)(rollup@4.19.1)(terser@5.31.3)(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))(vue-tsc@2.0.29(typescript@5.5.4)): + nuxt@3.12.4(@parcel/watcher@2.4.1)(@types/node@20.14.13)(eslint@8.57.0)(ioredis@5.4.1)(magicast@0.3.4)(optionator@0.9.4)(rollup@4.19.1)(terser@5.31.3)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.13)(terser@5.31.3))(vue-tsc@2.0.29(typescript@5.5.4)): dependencies: '@nuxt/devalue': 2.0.2 - '@nuxt/devtools': 1.3.9(rollup@4.19.1)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3)) + '@nuxt/devtools': 1.3.9(rollup@4.19.1)(vite@5.4.11(@types/node@20.14.13)(terser@5.31.3)) '@nuxt/kit': 3.12.4(magicast@0.3.4)(rollup@4.19.1) '@nuxt/schema': 3.12.4(rollup@4.19.1) '@nuxt/telemetry': 2.5.4(magicast@0.3.4)(rollup@4.19.1) @@ -13954,6 +14201,7 @@ snapshots: - optionator - rollup - sass + - sass-embedded - stylelint - stylus - sugarss @@ -14244,6 +14492,8 @@ snapshots: picocolors@1.0.1: {} + picocolors@1.1.1: {} + picomatch@2.3.1: {} pidtree@0.6.0: {} @@ -14319,7 +14569,7 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.4.40 - postcss-load-config@4.0.2(postcss@8.4.40)(ts-node@10.9.2(@swc/core@1.7.3(@swc/helpers@0.5.5))(@types/node@20.14.13)(typescript@5.5.4)): + postcss-load-config@4.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)): dependencies: lilconfig: 3.1.2 yaml: 2.5.0 @@ -14327,12 +14577,12 @@ snapshots: postcss: 8.4.40 ts-node: 10.9.2(@swc/core@1.7.3(@swc/helpers@0.5.5))(@types/node@20.14.13)(typescript@5.5.4) - postcss-load-config@6.0.1(jiti@1.21.6)(postcss@8.4.40)(yaml@2.5.0): + postcss-load-config@6.0.1(jiti@1.21.6)(postcss@8.4.49)(yaml@2.5.0): dependencies: lilconfig: 3.1.2 optionalDependencies: jiti: 1.21.6 - postcss: 8.4.40 + postcss: 8.4.49 yaml: 2.5.0 postcss-merge-longhand@7.0.2(postcss@8.4.40): @@ -14504,6 +14754,12 @@ snapshots: picocolors: 1.0.1 source-map-js: 1.2.0 + postcss@8.4.49: + dependencies: + nanoid: 3.3.7 + picocolors: 1.1.1 + source-map-js: 1.2.1 + posthog-node@2.6.0(debug@4.3.6): dependencies: axios: 0.27.2(debug@4.3.6) @@ -14971,6 +15227,30 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.19.1 fsevents: 2.3.3 + rollup@4.26.0: + dependencies: + '@types/estree': 1.0.6 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.26.0 + '@rollup/rollup-android-arm64': 4.26.0 + '@rollup/rollup-darwin-arm64': 4.26.0 + '@rollup/rollup-darwin-x64': 4.26.0 + '@rollup/rollup-freebsd-arm64': 4.26.0 + '@rollup/rollup-freebsd-x64': 4.26.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.26.0 + '@rollup/rollup-linux-arm-musleabihf': 4.26.0 + '@rollup/rollup-linux-arm64-gnu': 4.26.0 + '@rollup/rollup-linux-arm64-musl': 4.26.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.26.0 + '@rollup/rollup-linux-riscv64-gnu': 4.26.0 + '@rollup/rollup-linux-s390x-gnu': 4.26.0 + '@rollup/rollup-linux-x64-gnu': 4.26.0 + '@rollup/rollup-linux-x64-musl': 4.26.0 + '@rollup/rollup-win32-arm64-msvc': 4.26.0 + '@rollup/rollup-win32-ia32-msvc': 4.26.0 + '@rollup/rollup-win32-x64-msvc': 4.26.0 + fsevents: 2.3.3 + run-applescript@7.0.0: {} run-parallel@1.2.0: @@ -15156,6 +15436,8 @@ snapshots: source-map-js@1.2.0: {} + source-map-js@1.2.1: {} + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -15379,7 +15661,7 @@ snapshots: system-architecture@0.1.0: {} - tailwindcss@3.4.7(ts-node@10.9.2(@swc/core@1.7.3(@swc/helpers@0.5.5))(@types/node@20.14.13)(typescript@5.5.4)): + tailwindcss@3.4.7(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -15398,7 +15680,7 @@ snapshots: postcss: 8.4.40 postcss-import: 15.1.0(postcss@8.4.40) postcss-js: 4.0.1(postcss@8.4.40) - postcss-load-config: 4.0.2(postcss@8.4.40)(ts-node@10.9.2(@swc/core@1.7.3(@swc/helpers@0.5.5))(@types/node@20.14.13)(typescript@5.5.4)) + postcss-load-config: 4.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@20.14.13)(typescript@5.5.4)) postcss-nested: 6.2.0(postcss@8.4.40) postcss-selector-parser: 6.1.1 resolve: 1.22.8 @@ -15466,8 +15748,6 @@ snapshots: tiny-invariant@1.3.3: {} - tinyrainbow@1.2.0: {} - to-fast-properties@2.0.0: {} to-regex-range@5.0.1: @@ -15539,7 +15819,7 @@ snapshots: tsscmp@1.0.6: {} - tsup@8.2.3(@swc/core@1.7.3(@swc/helpers@0.5.5))(jiti@1.21.6)(postcss@8.4.40)(typescript@5.5.4)(yaml@2.5.0): + tsup@8.2.3(@swc/core@1.7.3(@swc/helpers@0.5.5))(jiti@1.21.6)(postcss@8.4.49)(typescript@5.5.4)(yaml@2.5.0): dependencies: bundle-require: 5.0.0(esbuild@0.23.0) cac: 6.7.14 @@ -15551,7 +15831,7 @@ snapshots: globby: 11.1.0 joycon: 3.1.1 picocolors: 1.0.1 - postcss-load-config: 6.0.1(jiti@1.21.6)(postcss@8.4.40)(yaml@2.5.0) + postcss-load-config: 6.0.1(jiti@1.21.6)(postcss@8.4.49)(yaml@2.5.0) resolve-from: 5.0.0 rollup: 4.19.1 source-map: 0.8.0-beta.0 @@ -15559,7 +15839,7 @@ snapshots: tree-kill: 1.2.2 optionalDependencies: '@swc/core': 1.7.3(@swc/helpers@0.5.5) - postcss: 8.4.40 + postcss: 8.4.49 typescript: 5.5.4 transitivePeerDependencies: - jiti @@ -15970,41 +16250,47 @@ snapshots: dependencies: vite: 5.3.5(@types/node@20.14.13)(terser@5.31.3) + vite-hot-client@0.2.3(vite@5.4.11(@types/node@20.14.13)(terser@5.31.3)): + dependencies: + vite: 5.4.11(@types/node@20.14.13)(terser@5.31.3) + vite-node@1.6.0(@types/node@20.14.13)(terser@5.31.3): dependencies: cac: 6.7.14 debug: 4.3.6 pathe: 1.1.2 picocolors: 1.0.1 - vite: 5.3.5(@types/node@20.14.13)(terser@5.31.3) + vite: 5.4.11(@types/node@20.14.13)(terser@5.31.3) transitivePeerDependencies: - '@types/node' - less - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color - terser - vite-node@2.0.4(@types/node@20.14.13)(terser@5.31.3): + vite-node@2.1.5(@types/node@20.14.13)(terser@5.31.3): dependencies: cac: 6.7.14 - debug: 4.3.6 + debug: 4.3.7 + es-module-lexer: 1.5.4 pathe: 1.1.2 - tinyrainbow: 1.2.0 - vite: 5.3.5(@types/node@20.14.13)(terser@5.31.3) + vite: 5.4.11(@types/node@20.14.13)(terser@5.31.3) transitivePeerDependencies: - '@types/node' - less - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color - terser - vite-plugin-checker@0.7.2(eslint@8.57.0)(optionator@0.9.4)(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))(vue-tsc@2.0.29(typescript@5.5.4)): + vite-plugin-checker@0.7.2(eslint@8.57.0)(optionator@0.9.4)(typescript@5.5.4)(vite@5.4.11(@types/node@20.14.13)(terser@5.31.3))(vue-tsc@2.0.29(typescript@5.5.4)): dependencies: '@babel/code-frame': 7.24.7 ansi-escapes: 4.3.2 @@ -16016,7 +16302,7 @@ snapshots: npm-run-path: 4.0.1 strip-ansi: 6.0.1 tiny-invariant: 1.3.3 - vite: 5.3.5(@types/node@20.14.13)(terser@5.31.3) + vite: 5.4.11(@types/node@20.14.13)(terser@5.31.3) vscode-languageclient: 7.0.0 vscode-languageserver: 7.0.0 vscode-languageserver-textdocument: 1.0.11 @@ -16027,7 +16313,7 @@ snapshots: typescript: 5.5.4 vue-tsc: 2.0.29(typescript@5.5.4) - vite-plugin-inspect@0.8.5(@nuxt/kit@3.12.4(magicast@0.3.4)(rollup@4.19.1))(rollup@4.19.1)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3)): + vite-plugin-inspect@0.8.5(@nuxt/kit@3.12.4(magicast@0.3.4)(rollup@4.19.1))(rollup@4.19.1)(vite@5.4.11(@types/node@20.14.13)(terser@5.31.3)): dependencies: '@antfu/utils': 0.7.10 '@rollup/pluginutils': 5.1.0(rollup@4.19.1) @@ -16038,14 +16324,30 @@ snapshots: perfect-debounce: 1.0.0 picocolors: 1.0.1 sirv: 2.0.4 - vite: 5.3.5(@types/node@20.14.13)(terser@5.31.3) + vite: 5.4.11(@types/node@20.14.13)(terser@5.31.3) optionalDependencies: '@nuxt/kit': 3.12.4(magicast@0.3.4)(rollup@4.19.1) transitivePeerDependencies: - rollup - supports-color - vite-plugin-vue-devtools@7.3.7(rollup@4.19.1)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4)): + vite-plugin-inspect@0.8.5(rollup@4.26.0)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3)): + dependencies: + '@antfu/utils': 0.7.10 + '@rollup/pluginutils': 5.1.0(rollup@4.26.0) + debug: 4.3.6 + error-stack-parser-es: 0.1.5 + fs-extra: 11.2.0 + open: 10.1.0 + perfect-debounce: 1.0.0 + picocolors: 1.0.1 + sirv: 2.0.4 + vite: 5.3.5(@types/node@20.14.13)(terser@5.31.3) + transitivePeerDependencies: + - rollup + - supports-color + + vite-plugin-vue-devtools@7.3.7(rollup@4.26.0)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4)): dependencies: '@vue/devtools-core': 7.3.7(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3))(vue@3.4.34(typescript@5.5.4)) '@vue/devtools-kit': 7.3.7 @@ -16053,7 +16355,7 @@ snapshots: execa: 8.0.1 sirv: 2.0.4 vite: 5.3.5(@types/node@20.14.13)(terser@5.31.3) - vite-plugin-inspect: 0.8.5(@nuxt/kit@3.12.4(magicast@0.3.4)(rollup@4.19.1))(rollup@4.19.1)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3)) + vite-plugin-inspect: 0.8.5(rollup@4.26.0)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3)) vite-plugin-vue-inspector: 5.1.3(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3)) transitivePeerDependencies: - '@nuxt/kit' @@ -16076,6 +16378,21 @@ snapshots: transitivePeerDependencies: - supports-color + vite-plugin-vue-inspector@5.1.3(vite@5.4.11(@types/node@20.14.13)(terser@5.31.3)): + dependencies: + '@babel/core': 7.24.9 + '@babel/plugin-proposal-decorators': 7.24.7(@babel/core@7.24.9) + '@babel/plugin-syntax-import-attributes': 7.24.7(@babel/core@7.24.9) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.9) + '@babel/plugin-transform-typescript': 7.25.0(@babel/core@7.24.9) + '@vue/babel-plugin-jsx': 1.2.2(@babel/core@7.24.9) + '@vue/compiler-dom': 3.4.34 + kolorist: 1.8.0 + magic-string: 0.30.10 + vite: 5.4.11(@types/node@20.14.13)(terser@5.31.3) + transitivePeerDependencies: + - supports-color + vite-tsconfig-paths@4.3.2(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3)): dependencies: debug: 4.3.6 @@ -16097,6 +16414,16 @@ snapshots: fsevents: 2.3.3 terser: 5.31.3 + vite@5.4.11(@types/node@20.14.13)(terser@5.31.3): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.49 + rollup: 4.26.0 + optionalDependencies: + '@types/node': 20.14.13 + fsevents: 2.3.3 + terser: 5.31.3 + vitefu@0.2.5(vite@5.3.5(@types/node@20.14.13)(terser@5.31.3)): optionalDependencies: vite: 5.3.5(@types/node@20.14.13)(terser@5.31.3) diff --git a/package.json b/package.json index 211b8388..171fdca3 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "dmno": "workspace:*", "only-allow": "^1.2.1", "read-yaml-file": "^2.1.0", - "turbo": "^1.13.3", + "turbo": "^2.2.3", "typescript": "catalog:" }, "engines": { diff --git a/packages/configraph/package.json b/packages/configraph/package.json index 214bd5ea..cb40ffb2 100644 --- a/packages/configraph/package.json +++ b/packages/configraph/package.json @@ -11,7 +11,7 @@ }, "bugs": "https://github.com/dmno-dev/dmno/issues", "homepage": "https://dmno.dev", - "keywords": [], + "keywords": [ ], "scripts": { "clean": "rm -rf dist", "build": "pnpm run clean && tsup", @@ -53,7 +53,6 @@ "debug": "catalog:", "kleur": "catalog:", "lodash-es": "catalog:", - "mitt": "catalog:", "svgo": "catalog:", "typescript": "catalog:" } diff --git a/packages/configraph/src/config-node.ts b/packages/configraph/src/config-node.ts index b4892d68..e8ff2077 100644 --- a/packages/configraph/src/config-node.ts +++ b/packages/configraph/src/config-node.ts @@ -110,8 +110,7 @@ export class ConfigraphNode { typeDef = { extends: shorthandFnResult }; } } else if (ConfigraphDataType.checkInstanceOf(defOrShorthand)) { - // TODO: without proper instanceof check, we must resort to `as any` - typeDef = { extends: defOrShorthand as any }; + typeDef = { extends: defOrShorthand }; } else if (_.isObject(defOrShorthand)) { typeDef = defOrShorthand; } else { diff --git a/packages/configraph/src/data-types.ts b/packages/configraph/src/data-types.ts index 2b9c3949..b34ab502 100644 --- a/packages/configraph/src/data-types.ts +++ b/packages/configraph/src/data-types.ts @@ -154,9 +154,11 @@ export class ConfigraphDataType { /** use instead of `instanceof ConfigraphDataType` * because there can be a different copy of dmno being used within vite from the dmno config loading process * */ - static checkInstanceOf(other: any) { - return other?.typeDef && other?.primitiveTypeFactory; + static checkInstanceOf(other: any): other is ConfigraphDataType { + return other?._dmnoInstanceType === this.name; } + readonly _dmnoInstanceType = this.constructor.name; + parentType?: ConfigraphDataType; diff --git a/packages/configraph/src/resolvers.ts b/packages/configraph/src/resolvers.ts index d968685a..032ff916 100644 --- a/packages/configraph/src/resolvers.ts +++ b/packages/configraph/src/resolvers.ts @@ -73,7 +73,7 @@ export function createResolver( if (_.isFunction(defOrFn)) { try { const result = defOrFn(); - if (result instanceof ConfigValueResolver) return result; + if (ConfigValueResolver.checkInstanceOf(result)) return result; return new ConfigValueResolver(result); } catch (err) { return new ConfigValueResolver({ @@ -111,6 +111,16 @@ type ResolverBranchDefinition = { }; export class ConfigValueResolver { + /** use instead of `instanceof ConfigValueResolver` + * because there can be a different copy of dmno being used within vite from the dmno config loading process + * */ + static checkInstanceOf(other: any): other is ConfigValueResolver { + // return other instanceof ConfigValueResolver + return other?._dmnoInstanceType === this.name; + } + readonly _dmnoInstanceType = this.constructor.name; + + constructor(readonly def: ConfigValueResolverDef) { // TODO: figure out this pattern... we'll have several bits of setings that // are either static or need some basic resolution @@ -396,7 +406,7 @@ export function processInlineResolverDef(resolverDef: InlineValueResolverDef) { }); // already a resolver case - } else if (resolverDef instanceof ConfigValueResolver) { + } else if (ConfigValueResolver.checkInstanceOf(resolverDef)) { return resolverDef; // static value case - including explicitly setting to `undefined @@ -412,6 +422,7 @@ export function processInlineResolverDef(resolverDef: InlineValueResolverDef) { resolve: async () => resolverDef, }); } else { + console.log(resolverDef); throw new Error('invalid resolver definition'); } } @@ -425,7 +436,7 @@ export class ResolverContext { // private configItem: DmnoConfigItemBase, resolverOrNode: ConfigValueResolver | ConfigraphNode, ) { - if (resolverOrNode instanceof ConfigValueResolver) { + if (ConfigValueResolver.checkInstanceOf(resolverOrNode)) { this.resolver = resolverOrNode; this.configNode = this.resolver.configNode!; } else { @@ -523,7 +534,12 @@ export class ResolverContext { } } -export const resolverCtxAls = new AsyncLocalStorage(); +//! Temporary workaround for the fact that ALS does not work when where are multiple copies of dmno being loaded +// currently when vite-node loads the config files, it loads a new copy of `dmno`, breaking things like ALS and instanceof checks +// we'll work on a deeper fix, but using a global here works for now\ +(globalThis as any).resolverCtxAls ||= new AsyncLocalStorage(); +export const resolverCtxAls = (globalThis as any).resolverCtxAls as AsyncLocalStorage; +// export const resolverCtxAls = new AsyncLocalStorage(); export function getResolverCtx() { const ctx = resolverCtxAls.getStore(); if (!ctx) throw new Error('unable to find resolver ctx in ALS'); diff --git a/packages/core/package.json b/packages/core/package.json index 08c18135..065f4d24 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -97,7 +97,7 @@ "@types/gradient-string": "^1.1.6", "@types/lodash-es": "catalog:", "@types/node": "catalog:", - "@types/node-ipc": "^9.2.3", + "@types/node-forge": "^1.3.11", "@types/picomatch": "^3.0.1", "@types/validate-npm-package-name": "^4.0.2", "@types/which": "^3.0.3", @@ -117,7 +117,6 @@ "commander": "^12.0.0", "debug": "catalog:", "diff": "^5.2.0", - "dotenv": "^16.4.5", "esm-resolve": "^1.0.11", "execa": "^8.0.1", "fdir": "^6.1.1", @@ -128,16 +127,15 @@ "lodash-es": "catalog:", "log-update": "^6.0.0", "magic-string": "^0.30.12", - "mitt": "catalog:", - "mkcert": "^3.2.0", "modern-async": "^2.0.0", - "node-ipc": "npm:@achrinza/node-ipc@^10.1.10", + "node-forge": "^1.3.1", "outdent": "^0.8.0", "picomatch": "^3.0.1", "read-yaml-file": "^2.1.0", "socket.io": "^4.8.0", "svgo": "catalog:", "typescript": "catalog:", + "uWebSockets.js": "github:uNetworking/uWebSockets.js#semver:^20.49.0", "validate-npm-package-name": "^5.0.0", "vite": "catalog:", "vite-node": "catalog:", diff --git a/packages/core/src/cli/cli-executable.ts b/packages/core/src/cli/cli-executable.ts index 40d04587..b1ad7194 100644 --- a/packages/core/src/cli/cli-executable.ts +++ b/packages/core/src/cli/cli-executable.ts @@ -5,6 +5,9 @@ // import first - we add global exception handler here const startBoot = new Date().getTime(); +const cliExecId = new Date().toISOString(); +console.time(`cli ${cliExecId}`); + import './lib/init-process'; import _ from 'lodash-es'; @@ -51,10 +54,16 @@ customizeHelp(program); program .hook('preAction', (thisCommand, actionCommand) => { // we need to know up front whether to enable the file watchers when initializing the vite server - const enableWatch = actionCommand.name() === 'dev' || actionCommand.opts().watch; - initCliRunCtx(enableWatch); + initCliRunCtx({ + enableWebUi: actionCommand.name() === 'dev', + watch: actionCommand.name() === 'dev' || actionCommand.opts().watch, + }); }); +process.on('exit', () => { + console.timeEnd(`cli ${cliExecId}`); +}); + debug(`finish loading - begin parse ${+new Date() - startBoot}ms`); try { await program.parseAsync(); diff --git a/packages/core/src/cli/commands/clear-cache.command.ts b/packages/core/src/cli/commands/clear-cache.command.ts index 885c9f25..386b5996 100644 --- a/packages/core/src/cli/commands/clear-cache.command.ts +++ b/packages/core/src/cli/commands/clear-cache.command.ts @@ -20,21 +20,21 @@ const program = new DmnoCommand('clear-cache') program.action(async (opts, more) => { const ctx = getCliRunCtx(); - const workspace = await ctx.configLoader.getWorkspace(); - const cacheFilePath = workspace.configraph.cacheProvider.cacheFilePath; + const { + wasDeleted, + cacheFilePath, + } = await ctx.dmnoServer.makeRequest('clearCache'); - if (!await pathExists(cacheFilePath)) { + if (wasDeleted) { + console.log('šŸ§²šŸ’¾ Workspace cache file erased'); + console.log(kleur.italic().gray(cacheFilePath)); + console.log(); + } else { console.log('šŸ‘» Workspace cache file already gone!\n'); process.exit(0); } - await workspace.configraph.cacheProvider.reset(); - - console.log('šŸ§²šŸ’¾ Workspace cache file erased'); - console.log(kleur.italic().gray(cacheFilePath)); - console.log(); - process.exit(0); }); diff --git a/packages/core/src/cli/commands/dev.command.ts b/packages/core/src/cli/commands/dev.command.ts index 86a2d719..81781e69 100644 --- a/packages/core/src/cli/commands/dev.command.ts +++ b/packages/core/src/cli/commands/dev.command.ts @@ -34,20 +34,15 @@ program.action(async (opts: { }, more) => { const ctx = getCliRunCtx(); - const configServer = new ConfigServer(ctx.configLoader, { - ipcOnly: opts?.ipcOnly, - }); - ctx.configLoader.devMode = true; - if (!opts.silent) { console.log(DMNO_DEV_BANNER); await fallingDmnosAnimation(); } - await configServer.webServerListening; + await ctx.dmnoServer.webServerReady; console.log(boxen( [ - `Local DMNO UI running @ ${kleur.bold().magenta(configServer.webServerUrl || 'ERROR')}`, + `Local DMNO UI running @ ${kleur.bold().magenta(ctx.dmnoServer.webServerUrl || 'ERROR')}`, ].join('\n'), { padding: 1, borderStyle: 'round', borderColor: 'blueBright', @@ -64,12 +59,12 @@ program.action(async (opts: { } firstLoad = false; console.log(''); - const workspace = await ctx.configLoader.getWorkspace(); + const workspace = await ctx.dmnoServer.getWorkspace(); if (opts.service) { - const service = workspace.getService(opts.service); + const service = workspace.services[opts.service]; - _.each(service.config, (item) => { - console.log(getItemSummary(item.toJSON())); + _.each(service.configNodes, (item) => { + console.log(getItemSummary(item)); }); } else { console.log('config loaded!'); @@ -82,12 +77,13 @@ program.action(async (opts: { // calling reload will regenerate types and resolve the config // TODO: we may want to change how the initial load in dev mode works so we dont need to reload here... - await ctx.configLoader.reload(); + // await ctx.configLoader.reload(); await logResult(); - configServer.onReload = () => logResult(); - + ctx.dmnoServer.enableWatchMode(async () => { + await logResult(); + }); // console.log(ctx.configLoader.uuid); diff --git a/packages/core/src/cli/commands/init.command.ts b/packages/core/src/cli/commands/init.command.ts index a96951d8..0c22711f 100644 --- a/packages/core/src/cli/commands/init.command.ts +++ b/packages/core/src/cli/commands/init.command.ts @@ -107,7 +107,7 @@ program.action(async (opts: { kleur.bold('Which of your workspace packages would like to initialize as "DMNO services"?'), '', 'You should select everything except shared libs that do not use any env vars.', - kleur.italic(`See ${kleur.gray('https://dmno.dev/docs/get-started/concepts/#service')} for more info`), + kleur.italic(`See ${kleur.gray('https://dmno.dev/docs/get-started/concepts/#dmno-service')} for more info`), '', 'šŸ’” You can also always run `dmno init` in those folders later!', diff --git a/packages/core/src/cli/commands/plugin.command.ts b/packages/core/src/cli/commands/plugin.command.ts index 5818e19c..4490912e 100644 --- a/packages/core/src/cli/commands/plugin.command.ts +++ b/packages/core/src/cli/commands/plugin.command.ts @@ -90,19 +90,19 @@ program.action(async (opts: { // reload the workspace and resolve values const workspace = await tryCatch(async () => { - return await ctx.configLoader.getWorkspace(); + return await ctx.dmnoServer.getWorkspace(); }, (err) => { console.log(kleur.red().bold('Loading config failed')); console.log(err.message); process.exit(1); }); - await workspace.resolveConfig(); + //! await workspace.resolveConfig(); const resolvedPlugin = workspace.plugins[opts.plugin!]; pluginCliProcess.send(['init', { - workspace: workspace.toJSON(), - plugin: resolvedPlugin.toJSON(), + workspace, + plugin: resolvedPlugin, selectedServiceName: opts.service, }]); }); diff --git a/packages/core/src/cli/commands/printenv.command.ts b/packages/core/src/cli/commands/printenv.command.ts index af2dc201..66c83a5a 100644 --- a/packages/core/src/cli/commands/printenv.command.ts +++ b/packages/core/src/cli/commands/printenv.command.ts @@ -7,7 +7,7 @@ import { getCliRunCtx } from '../lib/cli-ctx'; import { addCacheFlags } from '../lib/cache-helpers'; import { addWatchMode } from '../lib/watch-mode-helpers'; import { CliExitError } from '../lib/cli-error'; -import { checkForConfigErrors, checkForSchemaErrors } from '../lib/check-errors-helpers'; +import { checkForConfigErrors, checkForSchemaErrors } from '../../config-engine/check-errors-helpers'; const program = new DmnoCommand('printenv') @@ -20,7 +20,6 @@ addWatchMode(program); // must be first addCacheFlags(program); addServiceSelection(program); - program.action(async (itemPath: string, opts: {}, thisCommand) => { const ctx = getCliRunCtx(); @@ -30,19 +29,22 @@ program.action(async (itemPath: string, opts: {}, thisCommand) => { const workspace = ctx.workspace!; const service = ctx.selectedService; + const resolvedConfig = service.configNodes; + checkForSchemaErrors(workspace); - await workspace.resolveConfig(); checkForConfigErrors(service); + + const injectedEnv = await ctx.dmnoServer.makeRequest('getInjectedJson', service.serviceName); // 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 (!service.config[itemPath]) { + if (!injectedEnv[itemPath]) { throw new CliExitError(`Config item ${itemPath} not found in config schema`, { details: [ 'Perhaps you meant one of:', - ..._.map(service.config, (val, key) => `${kleur.gray('-')} ${key}`), + ..._.map(resolvedConfig, (val, key) => `${kleur.gray('-')} ${key}`), ], }); } @@ -50,7 +52,7 @@ 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(service.config[itemPath].resolvedValue); + ctx.logOutput(injectedEnv[itemPath].value); }); export const PrintEnvCommand = program; diff --git a/packages/core/src/cli/commands/resolve.command.ts b/packages/core/src/cli/commands/resolve.command.ts index c12d368e..f538abe7 100644 --- a/packages/core/src/cli/commands/resolve.command.ts +++ b/packages/core/src/cli/commands/resolve.command.ts @@ -13,7 +13,7 @@ import { getCliRunCtx } from '../lib/cli-ctx'; import { addCacheFlags } from '../lib/cache-helpers'; import { addWatchMode } from '../lib/watch-mode-helpers'; import { CliExitError } from '../lib/cli-error'; -import { checkForConfigErrors, checkForSchemaErrors } from '../lib/check-errors-helpers'; +import { checkForConfigErrors, checkForSchemaErrors } from '../../config-engine/check-errors-helpers'; import { stringifyObjectAsEnvFile } from '../lib/env-file-helpers'; import { isSubshell } from '../lib/shell-helpers'; @@ -56,29 +56,33 @@ program.action(async (opts: { const workspace = ctx.workspace!; const service = ctx.selectedService; checkForSchemaErrors(workspace); - await workspace.resolveConfig(); checkForConfigErrors(service, { showAll: opts?.showAll }); - const getExposedConfigValues = () => { - let exposedConfig = service.config; - if (opts.public) { - exposedConfig = _.pickBy(exposedConfig, (c) => !c.type.getMetadata('sensitive')); + async function getExposedConfigValues() { + const injectedJson = await ctx.dmnoServer.makeRequest('getInjectedJson', service.serviceName); + let exposedConfig = service.configNodes; + const values = {} as Record; + for (const itemKey in injectedJson) { + if (itemKey.startsWith('$')) continue; + if (injectedJson[itemKey].value && opts.public) continue; + values[itemKey] = injectedJson[itemKey].value; } - return _.mapValues(exposedConfig, (val) => val.resolvedValue); - }; + return values; + } // console.log(service.config); if (opts.format === 'json') { console.log(JSON.stringify(getExposedConfigValues())); } else if (opts.format === 'json-full') { - console.dir(service.toJSON(), { depth: null }); + console.dir(service, { depth: null }); } else if (opts.format === 'json-injected') { - console.log(JSON.stringify(service.configraphEntity.getInjectedEnvJSON())); + const injectedJson = await ctx.dmnoServer.makeRequest('getInjectedJson', ctx.selectedService.serviceName); + console.log(JSON.stringify(injectedJson)); } else if (opts.format === 'env') { console.log(stringifyObjectAsEnvFile(getExposedConfigValues())); } else { - _.each(service.config, (item) => { - console.log(getItemSummary(item.toJSON())); + _.each(service.configNodes, (item) => { + console.log(getItemSummary(item)); }); } }); diff --git a/packages/core/src/cli/commands/run.command.ts b/packages/core/src/cli/commands/run.command.ts index 07e2b227..67860a8b 100644 --- a/packages/core/src/cli/commands/run.command.ts +++ b/packages/core/src/cli/commands/run.command.ts @@ -7,7 +7,7 @@ import { addServiceSelection } from '../lib/selection-helpers'; import { getCliRunCtx } from '../lib/cli-ctx'; import { addCacheFlags } from '../lib/cache-helpers'; import { addWatchMode } from '../lib/watch-mode-helpers'; -import { checkForConfigErrors, checkForSchemaErrors } from '../lib/check-errors-helpers'; +import { checkForConfigErrors, checkForSchemaErrors } from '../../config-engine/check-errors-helpers'; const program = new DmnoCommand('run') @@ -56,24 +56,34 @@ program.action(async (_command, opts: { const workspace = ctx.workspace!; const service = ctx.selectedService; checkForSchemaErrors(workspace); - await workspace.resolveConfig(); + //! await workspace.resolveConfig(); checkForConfigErrors(service); - const serviceEnv = service.getEnv(); + console.log(ctx.selectedService.serviceName); + + const injectedJson = await ctx.dmnoServer.makeRequest('getInjectedJson', ctx.selectedService.serviceName); const fullInjectedEnv = { ...process.env, }; // 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 serviceEnv) { + 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 = serviceEnv[key]?.toString(); + const strVal = injectedJson[key]?.toString(); if (strVal !== undefined) fullInjectedEnv[key] = strVal; } } - fullInjectedEnv.DMNO_INJECTED_ENV = JSON.stringify(service.configraphEntity.getInjectedEnvJSON()); - fullInjectedEnv.DMNO_PROCESS_UUID = 'abc123'; + fullInjectedEnv.DMNO_INJECTED_ENV = JSON.stringify(injectedJson); + // this is what signals to the child process that is has a parent dmno server to use + fullInjectedEnv.DMNO_CONFIG_SERVER_UUID = ctx.dmnoServer.serverId; + + console.time('execa'); commandProcess = execa(pathAwareCommand || rawCommand, commandArgsOnly, { stdio: 'inherit', env: fullInjectedEnv, @@ -84,6 +94,7 @@ program.action(async (_command, opts: { let exitCode: number; try { const commandResult = await commandProcess; + console.timeEnd('execa'); // console.log(commandResult); exitCode = commandResult.exitCode; } catch (error) { diff --git a/packages/core/src/cli/lib/cache-helpers.ts b/packages/core/src/cli/lib/cache-helpers.ts index 15d8f06c..fba63b52 100644 --- a/packages/core/src/cli/lib/cache-helpers.ts +++ b/packages/core/src/cli/lib/cache-helpers.ts @@ -14,10 +14,10 @@ export function addCacheFlags(program: Command) { }); } const ctx = getCliRunCtx(); - ctx.configLoader.cacheMode = ( + ctx.dmnoServer.setCacheMode( (thisCommand.opts().skipCache && 'skip') || (thisCommand.opts().clearCache && 'clear') - || true + || true, ); }); } diff --git a/packages/core/src/cli/lib/cli-ctx.ts b/packages/core/src/cli/lib/cli-ctx.ts index 0baeffec..a5a8b0f5 100644 --- a/packages/core/src/cli/lib/cli-ctx.ts +++ b/packages/core/src/cli/lib/cli-ctx.ts @@ -1,14 +1,15 @@ import { AsyncLocalStorage } from 'node:async_hooks'; -import { ConfigLoader } from '../../config-loader/config-loader'; -import { DmnoService, DmnoWorkspace } from '../../config-engine/config-engine'; import { DmnoPlugin } from '../../config-engine/dmno-plugin'; +import { SerializedDmnoPlugin, SerializedService, SerializedWorkspace } from '../../config-loader/serialization-types'; +import { DmnoServer } from '../../config-loader/dmno-server'; export type CliRunCtx = { - configLoader: ConfigLoader; - workspace?: DmnoWorkspace; - selectedService?: DmnoService, + + dmnoServer: DmnoServer, + workspace?: SerializedWorkspace; + selectedService?: SerializedService, autoSelectedService?: boolean, - selectedPlugin?: DmnoPlugin, + selectedPlugin?: SerializedDmnoPlugin, /** true if watch mode is enabled */ watchEnabled?: boolean, @@ -40,9 +41,9 @@ const ctxHelpers = { export const cliRunContext = new AsyncLocalStorage(); -export function initCliRunCtx(enableWatch = false) { +export function initCliRunCtx(dmnoServerOptions?: ConstructorParameters[0]) { cliRunContext.enterWith({ - configLoader: new ConfigLoader(enableWatch), + dmnoServer: new DmnoServer(dmnoServerOptions), ...ctxHelpers, }); } diff --git a/packages/core/src/cli/lib/env-file-helpers.ts b/packages/core/src/cli/lib/env-file-helpers.ts index d6610023..bb898f62 100644 --- a/packages/core/src/cli/lib/env-file-helpers.ts +++ b/packages/core/src/cli/lib/env-file-helpers.ts @@ -1,4 +1,4 @@ -export function stringifyObjectAsEnvFile(obj: Record) { +export function stringifyObjectAsEnvFile(obj: Record) { return Object.entries(obj).map(([key, value]) => { // Handle newlines and quotes by wrapping in double quotes and escaping const formattedValue = String(value) diff --git a/packages/core/src/cli/lib/selection-helpers.ts b/packages/core/src/cli/lib/selection-helpers.ts index 67f26d15..83804f27 100644 --- a/packages/core/src/cli/lib/selection-helpers.ts +++ b/packages/core/src/cli/lib/selection-helpers.ts @@ -8,9 +8,10 @@ import { DmnoPlugin } from '../../config-engine/dmno-plugin'; import { getMaxLength } from './string-utils'; import { joinAndCompact } from './formatting'; import { CliExitError } from './cli-error'; +import { SerializedDmnoPlugin } from '../../config-loader/serialization-types'; -function getServiceLabel(s: DmnoService, padNameEnd: number) { +function getServiceLabel(s: { serviceName: string, packageName: string, configLoadError?: any }, padNameEnd: number) { return joinAndCompact([ `- ${s.serviceName.padEnd(padNameEnd)}`, kleur.gray(s.packageName), @@ -30,10 +31,11 @@ export function addServiceSelection(program: Command, opts?: { .hook('preAction', async (thisCommand, actionCommand) => { const ctx = getCliRunCtx(); - const workspace = await ctx.configLoader.getWorkspace(); + + const workspace = await ctx.dmnoServer.makeRequest('loadFullSchema'); ctx.workspace = workspace; - const namesMaxLen = getMaxLength(_.map(workspace.allServices, (s) => s.serviceName)); + const namesMaxLen = getMaxLength(_.map(workspace.services, (s) => s.serviceName)); const disablePrompt = thisCommand.opts().noPrompt || opts?.disablePrompt; // // first display loading errors (which would likely cascade into schema errors) @@ -59,8 +61,9 @@ export function addServiceSelection(program: Command, opts?: { // handle re-selecting the same service on a restart, which could be a bit weird if the name(s) have changed // but we try to just select the same one and not worry too much if (ctx.isWatchModeRestart && ctx.selectedService) { - ctx.selectedService = ctx.workspace.getService({ serviceName: ctx.selectedService.serviceName }) - || ctx.workspace.getService({ packageName: ctx.selectedService.packageName }); + ctx.selectedService = _.find(ctx.workspace.services, (s) => s.serviceName === ctx.selectedService!.serviceName) + || _.find(ctx.workspace.services, (s) => s.packageName === ctx.selectedService!.packageName); + if (ctx.selectedService) return; } @@ -74,13 +77,13 @@ export function addServiceSelection(program: Command, opts?: { const explicitSelection = thisCommand.opts().service; if (!explicitMenuOptIn && explicitSelection) { - ctx.selectedService = _.find(workspace.allServices, (s) => s.serviceName === explicitSelection); + ctx.selectedService = _.find(workspace.services, (s) => s.serviceName === explicitSelection); if (ctx.selectedService) return; throw new CliExitError(`Invalid service selection: ${kleur.bold(explicitSelection)}`, { suggestion: [ 'Maybe you meant one of:', - ..._.map(workspace.allServices, (s) => getServiceLabel(s, namesMaxLen)), + ..._.map(workspace.services, (s) => getServiceLabel(s, namesMaxLen)), ], }); } @@ -91,7 +94,7 @@ export function addServiceSelection(program: Command, opts?: { const packageName = process.env.npm_package_name || process.env.PNPM_PACKAGE_NAME; if (packageName) { // console.log('auto select package name', packageName); - const autoServiceFromPackageManager = _.find(workspace.allServices, (service) => { + const autoServiceFromPackageManager = _.find(workspace.services, (service) => { return service.packageName === packageName; }); @@ -111,7 +114,7 @@ export function addServiceSelection(program: Command, opts?: { if (!thisCommand.opts().silent && (explicitMenuOptIn || !opts?.disableMenuSelect)) { // order our services by folder depth (descending) // so we can look for whiuch folder the user is in - const servicesOrderedByDirDepth = _.orderBy(workspace.allServices, (s) => s.path.split('/').length, ['desc']); + const servicesOrderedByDirDepth = _.orderBy(workspace.services, (s) => s.path.split('/').length, ['desc']); const cwd = process.cwd(); const autoServiceFromCwd = _.find(servicesOrderedByDirDepth, (service) => { @@ -120,14 +123,14 @@ export function addServiceSelection(program: Command, opts?: { const menuSelection = await select({ message: 'Please select a service?', - choices: _.map(workspace.allServices, (service) => ({ + choices: _.map(workspace.services, (service) => ({ name: getServiceLabel(service, namesMaxLen), value: service.serviceName, })), default: autoServiceFromCwd?.serviceName, }); - ctx.selectedService = _.find(workspace.allServices, (s) => s.serviceName === menuSelection); + ctx.selectedService = _.find(workspace.services, (s) => s.serviceName === menuSelection); ctx.autoSelectedService = false; return; } @@ -140,7 +143,7 @@ export function addServiceSelection(program: Command, opts?: { }); } -function getPluginLabel(p: DmnoPlugin, padNameEnd: number) { +function getPluginLabel(p: SerializedDmnoPlugin, padNameEnd: number) { return [ `- ${p.instanceId}`.padEnd(padNameEnd), kleur.gray(`${p.pluginType}`), @@ -154,9 +157,7 @@ export function addPluginSelection(program: Command) { .hook('preAction', async (thisCommand, actionCommand) => { const ctx = getCliRunCtx(); - const workspace = await ctx.configLoader.getWorkspace(); - await workspace.resolveConfig(); - + const workspace = await ctx.dmnoServer.getWorkspace(); const pluginsArray = _.values(workspace.plugins); const namesMaxLen = getMaxLength(_.map(pluginsArray, (p) => p.instanceId)); diff --git a/packages/core/src/cli/lib/watch-mode-helpers.ts b/packages/core/src/cli/lib/watch-mode-helpers.ts index c082fc0a..03b13a6a 100644 --- a/packages/core/src/cli/lib/watch-mode-helpers.ts +++ b/packages/core/src/cli/lib/watch-mode-helpers.ts @@ -1,3 +1,4 @@ +import { createReadStream } from 'fs'; import { Command } from 'commander'; import kleur from 'kleur'; import { CliRunCtx, getCliRunCtx } from './cli-ctx'; @@ -16,7 +17,7 @@ let enqueueRerun = false; async function rerunCliAction(ctx: CliRunCtx, thisCommand: Command) { console.log(kleur.blue().italic('reloading due to config change')); - ctx.workspace = await ctx.configLoader.getWorkspace(); + ctx.workspace = await ctx.dmnoServer.getWorkspace(); // going to try to re-execute the command's action handler // probably a bad idea... but let's try it? @@ -62,8 +63,8 @@ export function addWatchMode(program: Command) { if (!ctx.watchEnabled) return; // enable dev-mode and attach reload handler that re-runs the command's action - ctx.configLoader.devMode = true; - ctx.configLoader.onReload = async () => { + ctx.dmnoServer.enableWatchMode(async () => { + console.log('watch mode reload handler!'); try { await rerunCliAction(ctx, thisCommand); } catch (err) { @@ -80,7 +81,7 @@ export function addWatchMode(program: Command) { // print "watching your files..." console.log(WATCHING_FILES_MESSAGE); } - }; + }); }) .hook('postAction', async (thisCommand, actionCommand) => { const ctx = getCliRunCtx(); @@ -96,6 +97,7 @@ export function addWatchMode(program: Command) { } else { console.log(WATCHING_FILES_MESSAGE); } + console.log('post action complete'); }); } diff --git a/packages/core/src/cli/lib/check-errors-helpers.ts b/packages/core/src/config-engine/check-errors-helpers.ts similarity index 64% rename from packages/core/src/cli/lib/check-errors-helpers.ts rename to packages/core/src/config-engine/check-errors-helpers.ts index aba14330..a27b5a21 100644 --- a/packages/core/src/cli/lib/check-errors-helpers.ts +++ b/packages/core/src/config-engine/check-errors-helpers.ts @@ -1,21 +1,21 @@ import kleur from 'kleur'; import _ from 'lodash-es'; -import { DmnoService, DmnoWorkspace } from '../../config-engine/config-engine'; -import { CliExitError } from './cli-error'; +import { CliExitError } from '../cli/lib/cli-error'; import { formatError, formattedValue, getItemSummary, joinAndCompact, -} from './formatting'; +} from '../cli/lib/formatting'; +import { SerializedService, SerializedWorkspace } from '../config-loader/serialization-types'; -export function checkForSchemaErrors(workspace: DmnoWorkspace) { +export function checkForSchemaErrors(workspace: SerializedWorkspace) { // first display loading errors (which would likely cascade into schema errors) - if (_.some(_.values(workspace.allServices), (s) => s.configLoadError)) { + if (_.some(_.values(workspace.services), (s) => s.configLoadError)) { console.log(`\nšŸšØ šŸšØ šŸšØ ${kleur.bold().underline('We were unable to load all of your config')} šŸšØ šŸšØ šŸšØ\n`); console.log(kleur.gray('The following services are failing to load:\n')); // NOTE - we dont use a table here because word wrapping within the table // breaks clicking/linking into your code - _.each(workspace.allServices, (service) => { + _.each(workspace.services, (service) => { if (!service.configLoadError) return; console.log(kleur.bold().red(`šŸ’„ Service ${kleur.underline(service.serviceName)} failed to load šŸ’„\n`)); @@ -47,7 +47,7 @@ export function checkForSchemaErrors(workspace: DmnoWorkspace) { const errors = _.compact([ item.coercionError, ...item.validationErrors || [], - item.schemaError, + ...item.schemaErrors || [], ]); console.log(`\n${kleur.underline('Error(s)')}:`); console.log(errors?.map((err) => `- ${err.message}`).join('\n')); @@ -59,8 +59,8 @@ export function checkForSchemaErrors(workspace: DmnoWorkspace) { } // now show schema errors - const servicesWithSchemaErrors = _.values(workspace.allServices).filter( - (s) => s.schemaErrors?.length || _.some(_.values(s.config), (i) => !i.isSchemaValid), + const servicesWithSchemaErrors = _.values(workspace.services).filter( + (s) => s.schemaErrors?.length || _.some(_.values(s.configNodes), (i) => !i.isSchemaValid), ); if (servicesWithSchemaErrors.length) { console.log(`\nšŸšØ šŸšØ šŸšØ ${kleur.bold().underline('Your config schema is invalid')} šŸšØ šŸšØ šŸšØ\n`); @@ -72,20 +72,20 @@ export function checkForSchemaErrors(workspace: DmnoWorkspace) { _.each(service.schemaErrors, (err) => { console.log(formatError(err)); }); - const invalidSchemaItems = _.values(service.config).filter((i) => !i.isSchemaValid); + const invalidSchemaItems = _.values(service.configNodes).filter((i) => !i.isSchemaValid); _.each(invalidSchemaItems, (item) => { console.log(`> ${item.key}`); - console.log(item.schemaErrors.map(formatError).join('\n')); + console.log(item.schemaErrors?.map(formatError).join('\n')); }); }); throw new CliExitError('Config schema errors'); } } -export function checkForConfigErrors(service: DmnoService, opts?: { +export function checkForConfigErrors(service: SerializedService, opts?: { showAll?: boolean }) { - const failingItems = _.filter(service.config, (item) => !item.isValid); + const failingItems = _.filter(service.configNodes, (item) => !item.isValid); // TODO: make isValid flag on service to work if (failingItems.length > 0) { @@ -93,7 +93,7 @@ export function checkForConfigErrors(service: DmnoService, opts?: { console.log('Invalid items:\n'); _.each(failingItems, (item) => { - console.log(getItemSummary(item.toJSON())); + console.log(getItemSummary(item)); console.log(); }); if (opts?.showAll) { @@ -103,12 +103,42 @@ export function checkForConfigErrors(service: DmnoService, opts?: { kleur.italic().gray('(remove `--show-all` flag to hide)'), ])); console.log(); - const validItems = _.filter(service.config, (i) => !!i.isValid); + const validItems = _.filter(service.configNodes, (i) => !!i.isValid); _.each(validItems, (item) => { - console.log(getItemSummary(item.toJSON())); + console.log(getItemSummary(item)); }); } throw new CliExitError('Resolved config did not pass validation'); } } + + +export function checkServiceIsValid(service: SerializedService, log = true) { + if (service.configLoadError) { + console.log('šŸšØ šŸšØ šŸšØ unable to load config schema šŸšØ šŸšØ šŸšØ'); + console.log(formatError(service.configLoadError)); + return false; + } + // plugins! + + if (service.schemaErrors?.length) { + console.log('šŸšØ šŸšØ šŸšØ config schema is invalid šŸšØ šŸšØ šŸšØ'); + console.log(service.schemaErrors.forEach((err) => { + console.log(formatError(err)); + })); + return false; + } + + const failingItems = Object.values(service.configNodes).filter((c) => !c.isValid); + if (failingItems.length) { + console.log('šŸšØ šŸšØ šŸšØ config is invalid šŸšØ šŸšØ šŸšØ'); + failingItems.forEach((item) => { + console.log(getItemSummary(item)); + console.log(); + }); + return false; + } + + return true; +} diff --git a/packages/core/src/config-engine/dmno-plugin.ts b/packages/core/src/config-engine/dmno-plugin.ts index 54b07bc8..346ee9b8 100644 --- a/packages/core/src/config-engine/dmno-plugin.ts +++ b/packages/core/src/config-engine/dmno-plugin.ts @@ -63,6 +63,7 @@ DmnoDataTypeMetadata, DmnoConfigraphServiceEntity toJSON(): SerializedDmnoPlugin { return { ...this.toCoreJSON(), + cliPath: this.cliPath, inputNodes: _.mapValues(this.internalEntity?.configNodes, (n) => n.toJSON()), }; } diff --git a/packages/core/src/config-loader/config-loader.ts b/packages/core/src/config-loader/config-loader.ts index c62ff0b8..ea2caa27 100644 --- a/packages/core/src/config-loader/config-loader.ts +++ b/packages/core/src/config-loader/config-loader.ts @@ -1,21 +1,17 @@ -import crypto from 'node:crypto'; -import fs from 'node:fs'; import path from 'node:path'; -import kleur from 'kleur'; import _ from 'lodash-es'; import Debug from 'debug'; -import { ConfigLoadError, Configraph, CacheMode } from '@dmno/configraph'; +import { ConfigLoadError, CacheMode } from '@dmno/configraph'; -import { DeferredPromise, createDeferredPromise } from '@dmno/ts-lib'; -import { HmrContext } from 'vite'; +import { HmrContext, ViteDevServer } from 'vite'; import { ViteNodeRunner } from 'vite-node/client'; -import { ConfigLoaderRequestMap } from './ipc-requests'; +import { ViteNodeServer } from 'vite-node/server'; import { createDebugTimer } from '../cli/lib/debug-timer'; import { setupViteServer } from './vite-server'; -import { ScannedWorkspaceInfo, WorkspacePackagesListing, findDmnoServices } from './find-services'; +import { ScannedWorkspaceInfo, findDmnoServices } from './find-services'; import { DmnoService, DmnoWorkspace, DmnoServiceConfig, } from '../config-engine/config-engine'; @@ -27,7 +23,7 @@ import { const debugTimer = createDebugTimer('dmno:config-loader'); -const debug = Debug('dmno'); +const debug = Debug('dmno:config-loader'); export class ConfigLoader { startAt: Date; @@ -38,11 +34,12 @@ export class ConfigLoader { isReady: Promise; constructor(private enableWatch: boolean) { - this.isReady = this.finishInit(); this.startAt = new Date(); + this.isReady = this.finishInit(); } viteRunner?: ViteNodeRunner; + viteServer?: ViteDevServer; workspaceInfo!: ScannedWorkspaceInfo; get workspacePackagesData() { @@ -56,23 +53,25 @@ export class ConfigLoader { } private async finishInit() { - // console.time('find-services'); this.workspaceInfo = await findDmnoServices(); - const dmnoServicePackages = this.workspaceInfo.workspacePackages.filter((p) => p.dmnoFolder); + // already filtered to only services with a .dmno folder + const dmnoServicePackages = this.workspaceInfo.workspacePackages; + // during init there may be no services at all if (!dmnoServicePackages.length) return; - // console.timeEnd('find-services'); + // TODO: we may want to do this on demand // so it does not slow down `dmno init` or other commands that don't need it - const { viteRunner } = await setupViteServer({ + const { viteRunner, viteServer } = await setupViteServer({ workspaceRootPath: this.workspaceRootPath, enableWatch: this.enableWatch, hotReloadHandler: (ctx) => this.viteHotReloadHandler(ctx), }); this.viteRunner = viteRunner; + this.viteServer = viteServer; } onReload?: () => void | Promise; @@ -90,6 +89,10 @@ export class ConfigLoader { } } + async shutdown() { + await this.viteServer?.close(); + } + devMode = false; schemaLoaded = false; dmnoWorkspace?: DmnoWorkspace; @@ -120,7 +123,6 @@ export class ConfigLoader { this.dmnoWorkspace.configraph.setCacheMode(this.cacheMode); - beginWorkspaceLoadPlugins(this.dmnoWorkspace); let servicesToLoad = [...this.workspacePackagesData]; @@ -129,9 +131,6 @@ export class ConfigLoader { const toLoadCount = servicesToLoad.length; for (const w of servicesToLoad) { if (!w.dmnoFolder) continue; - // not sure yet about naming the root file differently? - // especially in the 1 service context, it may feel odd - // const configFilePath = `${w.path}/.dmno/${isRoot ? 'workspace-' : ''}config.mts`; const configFilePath = `${w.path}/.dmno/config.mts`; const serviceInitOpts = { diff --git a/packages/core/src/config-loader/config-server-client.ts b/packages/core/src/config-loader/config-server-client.ts deleted file mode 100644 index 90dd92cf..00000000 --- a/packages/core/src/config-loader/config-server-client.ts +++ /dev/null @@ -1,253 +0,0 @@ -import { ChildProcess, spawn } from 'node:child_process'; -import ipc from 'node-ipc'; -import mitt, { Handler } from 'mitt'; -import Debug from 'debug'; - -import { DeferredPromise, createDeferredPromise } from '@dmno/ts-lib'; -import { createDebugTimer } from '../cli/lib/debug-timer'; -import { ConfigLoaderRequestMap } from './ipc-requests'; -import { SerializedService } from './serialization-types'; -import { formatError, getItemSummary } from '../cli/lib/formatting'; -import { detectJsPackageManager } from '../lib/detect-package-manager'; - -const debug = Debug('dmno'); -const debugTimer = createDebugTimer('dmno:loader-client'); - -function getCurrentPackageName() { - if (process.env.npm_package_name !== undefined) return process.env.npm_package_name; - if (process.env.PNPM_PACKAGE_NAME !== undefined) return process.env.PNPM_PACKAGE_NAME; -} - - -export class ConfigServerClient { - eventBus = mitt(); - - readonly serverId: string; - private ipc: typeof ipc; - constructor() { - this.ipc = ipc; - - if (process.env.DMNO_CONFIG_SERVER_UUID) { - this.serverId = process.env.DMNO_CONFIG_SERVER_UUID; - } else { - this.serverId = crypto.randomUUID(); - this.initOwnedConfigServer(); - } - - this.initIpcClient(); - } - - private isShuttingDown = false; - - shutdown() { - if (this.isShuttingDown) return; - this.isShuttingDown = true; - this.ipc.disconnect('dmno'); - if (this.ownedDmnoConfigServerProcess) { - this.ownedDmnoConfigServerProcess.kill(2); - } - } - - private ownedDmnoConfigServerProcess?: ChildProcess; - private initOwnedConfigServer() { - const packageManager = detectJsPackageManager(); - const execCommand = packageManager.exec; - const execCommandParts = execCommand.split(' '); - const execCommandBaseCommand = execCommandParts.shift()!; - let execCommandRest = execCommandParts.join(' '); - if (!execCommandRest.endsWith('--')) execCommandRest += ' --'; - - // use `pnpm exec` or `npm exec` etc... - this.ownedDmnoConfigServerProcess = spawn(execCommandBaseCommand, `${execCommandRest} dmno dev --silent --ipc-only`.split(' '), { - stdio: 'inherit', - env: { - ...process.env, - DMNO_CONFIG_SERVER_UUID: this.serverId, - // PATH: process.env.PATH, - }, - }); - // console.log(this.ownedDmnoConfigServerProcess); - - process.on('SIGTERM', () => { - // console.log('client process - sigterm!'); - this.shutdown(); - }); - - process.on('exit', () => { - // console.log('client process - exit!'); - this.shutdown(); - }); - - this.ownedDmnoConfigServerProcess.on('close', (code, signal) => { - // console.log('dmno config server - close'); - }); - - this.ownedDmnoConfigServerProcess.on('disconnect', () => { - // console.log('dmno config server - disconnect'); - // process.exit(1); - }); - - this.ownedDmnoConfigServerProcess.on('error', (err) => { - // console.log('dmno config server process - error', err); - // process.exit(0); - }); - - this.ownedDmnoConfigServerProcess.on('exit', (code, signal) => { - // console.log('dmno config server process exit', code, signal); - if (!this.isShuttingDown) process.exit(code || 1); - }); - } - - - - private ipcReadyDeferred = createDeferredPromise(); - private initIpcClient() { - this.ipc.config.id = 'dmno'; - this.ipc.config.retry = 1500; - this.ipc.config.silent = true; - - // we pass in a uuid to identify the running process IPC socket - // this allows us to run multiple concurrent loaders... - // TBD whether that makes sense or if we should share a single process? - - debugTimer('begin ipc client connection'); - this.ipc.connectTo('dmno', `/tmp/${this.serverId}.dmno.sock`, () => { - debugTimer('ipc client connectTo callback'); - - this.ipc.of.dmno.on('connect', () => { - debugTimer('ipc client connect event + emit ready'); - this.ipc.log('## connected to dmno ##', this.ipc.config.retry); - this.ipc.of.dmno.emit('ready'); - this.ipcReadyDeferred.resolve(); - }); - - this.ipc.of.dmno.on('disconnect', () => { - this.ipc.log('disconnected from dmno'); - }); - - this.ipc.of.dmno.on('event', (eventMessage) => { - // console.log('received IPC event message', eventMessage); - this.eventBus.emit(eventMessage.type, eventMessage.payload); - }); - - this.ipc.of.dmno.on('request-response', this.handleRequestResponse.bind(this)); - }); - } - - // Tools for request/response communication with the loader proces - // by default IPC just lets us send messages. This tooling allows us to make "requests" - // and then receive a response - with type-safety throughout the process - - private requestCounter = 1; - private requests = {} as Record; - - // TS magic here lets us auto-complete the available request types - // and have a typed payload and response :) - async makeRequest( - key: K, - - // some TS trickery to support passing no second arg when payload is undefined - // see https://minajevs.medium.com/how-to-make-function-parameters-optional-in-typescript-8cb4fa22171d - ...args: ConfigLoaderRequestMap[K]['payload'] extends undefined ? [] : [ConfigLoaderRequestMap[K]['payload']] - ): Promise { - await this.ipcReadyDeferred.promise; - debug('making IPC request', key); - - const payload = args?.[0]; - - // make sure IPC and the process is booted before we do anything - // await this.isReady; - - - // in order to make multiple concurrent requests, we create a "request id" - // and use it to match up the reply. We'll use a simple counter for now... - // we may want to add a random client id prefix too? - const requestId = this.requestCounter++; - - this.requests[requestId] = { - startedAt: new Date(), - deferredPromise: createDeferredPromise(), - }; - - // TODO: we may want to store more metadata so we can handle things like timeouts? - ipc.of.dmno.emit('request', { - requestId, - requestType: key, - payload, - }); - - return this.requests[requestId].deferredPromise.promise as any; - } - - /** internal method called when receiving a request response */ - private handleRequestResponse(responseMessage: { - requestId: string, - response: any, - error?: any, - }) { - debug('handle req response', responseMessage); - const req = this.requests[responseMessage.requestId]; - // we just look up the request using the requestId, and resolve the deffered - // promise with the response payload - if (!req) { - throw new Error(`IPC request not found: ${responseMessage.requestId}`); - } - if (responseMessage.error) { - const e = new Error(responseMessage.error.message); - e.stack = responseMessage.error.stack; - req.deferredPromise.reject(e); - } else { - req.deferredPromise.resolve(responseMessage.response); - } - - const reqTimeMs = +new Date() - +req.startedAt; - debug(`request took ${reqTimeMs}ms`); - - // clean up...? - delete this.requests[responseMessage.requestId]; - } - - - async getServiceConfig() { - const packageName = getCurrentPackageName(); - if (packageName === '') { - throw new Error('To use dmno, you must set a package "name" in your package.json file'); - } - // what to do if we can't figure out a package name? - - return await this.makeRequest('get-resolved-config', { packageName }); - } - - static checkServiceIsValid(service: SerializedService, log = true) { - if (service.configLoadError) { - console.log('šŸšØ šŸšØ šŸšØ unable to load config schema šŸšØ šŸšØ šŸšØ'); - console.log(formatError(service.configLoadError)); - return false; - } - // plugins! - - if (service.schemaErrors?.length) { - console.log('šŸšØ šŸšØ šŸšØ config schema is invalid šŸšØ šŸšØ šŸšØ'); - console.log(service.schemaErrors.forEach((err) => { - console.log(formatError(err)); - })); - return false; - } - - const failingItems = Object.values(service.configNodes).filter((c) => !c.isValid); - if (failingItems.length) { - console.log('šŸšØ šŸšØ šŸšØ config is invalid šŸšØ šŸšØ šŸšØ'); - failingItems.forEach((item) => { - console.log(getItemSummary(item)); - console.log(); - }); - return false; - } - - return true; - } -} - diff --git a/packages/core/src/config-loader/config-server.ts b/packages/core/src/config-loader/config-server.ts deleted file mode 100644 index 27881f84..00000000 --- a/packages/core/src/config-loader/config-server.ts +++ /dev/null @@ -1,416 +0,0 @@ -import { createServer, Server } from 'node:http'; -import { createServer as createHttpsServer, Server as HttpsServer } from 'node:https'; - -import fs from 'node:fs'; -import path, { dirname } from 'node:path'; -import { fileURLToPath } from 'node:url'; - -import { execSync } from 'node:child_process'; -import { Server as SocketIoServer } from 'socket.io'; -import ipc from 'node-ipc'; -import mitt, { Handler } from 'mitt'; -import Debug from 'debug'; -import launchEditor from 'launch-editor'; -import { createDeferredPromise } from '@dmno/ts-lib'; - -import kleur from 'kleur'; -import { ConfigLoader } from './config-loader'; -import { createDebugTimer } from '../cli/lib/debug-timer'; -import { ConfigLoaderRequestMap } from './ipc-requests'; -import { detectJsPackageManager } from '../lib/detect-package-manager'; -import { createLocalSslCert } from '../lib/certs'; -import { DmnoBaseTypes } from '../config-engine/configraph-adapter'; - - -const debug = Debug('dmno'); -const debugTimer = createDebugTimer('dmno:config-server'); - -const __dirname = dirname(fileURLToPath(import.meta.url)); - -// TODO: these should probably be read from a workspace-level yaml file -const DEFAULT_DEV_PORT = 3666; -const DEFAULT_DEV_HOST = 'localhost'; -const DEFAULT_DEV_SSL_ENABLED = false; - -const MIME_TYPES = { - js: 'text/javascript', - html: 'text/html', - css: 'text/css', - ico: 'image/x-icon', - // TODO: will need more for images -}; - -process.on('uncaughtException', (err) => { - console.log('UNCAUGHT EXCEPTION!', err); -}); -process.on('unhandledRejection', (err) => { - console.log('UNCAUGHT REJECTION!', err); - console.log((err as any).stack); -}); - -export class ConfigServer { - readonly uuid = process.env.DMNO_CONFIG_SERVER_UUID || crypto.randomUUID(); - - constructor(private configLoader: ConfigLoader, opts?: { - ipcOnly?: boolean - }) { - this.registerIpcRequestHandlers(); - this.initIpcServer(); - this.configLoader.onReload = this.onConfigReload.bind(this); - - if (!opts?.ipcOnly) { - this.initWebServer().catch((err) => { - console.log(err); - process.exit(1); - }); - } - } - - get workspace() { return this.configLoader.dmnoWorkspace!; } - - - private httpServer: HttpsServer | Server | undefined; - private socketIoServer: SocketIoServer | undefined; - - private webServerListeningDeferred = createDeferredPromise(); - public get webServerListening() { return this.webServerListeningDeferred.promise; } - private _webServerUrl?: string; - public get webServerUrl() { - return this._webServerUrl; - } - - private async initWebServer() { - const debugWeb = Debug('dmno:webserver'); - - await this.configLoader.isReady; - - // TODO: this should probably be part of the initial workspace loading? - let devPort = DEFAULT_DEV_PORT; - let devHost = DEFAULT_DEV_HOST; - let devSsl = DEFAULT_DEV_SSL_ENABLED; - const workspaceSettings = this.configLoader.workspaceInfo.settings; - if (workspaceSettings?.dev?.port) { - try { - devPort = DmnoBaseTypes.port().coerceAndValidate(workspaceSettings?.dev?.port); - } catch (err) { - console.log(`Invalid devUi.port in workspace settings - defaulting to ${devPort}`); - } - } - if (workspaceSettings?.dev?.host) { - try { - // TODO: need a new "host" type and to validate it - devHost = DmnoBaseTypes.string().coerceAndValidate(workspaceSettings?.dev?.host); - } catch (err) { - console.log(`Invalid devUi.host in workspace settings - defaulting to ${devHost}`); - } - } - if (workspaceSettings?.dev?.ssl) { - try { - devSsl = DmnoBaseTypes.boolean().coerceAndValidate(workspaceSettings?.dev?.ssl); - } catch (err) { - console.log(`Invalid devUi.ssl in workspace settings - defaulting to ${devSsl}`); - } - } - debugWeb('dev settings', { host: devHost, port: devPort, ssl: devSsl }); - - // check if the host we are serving on will work - try { - // NOTE - trying to use node dns.lookup but there is no way to set the timeout so it's quite slow when it fails - // see https://github.com/nodejs/node/issues/55525 - - // copied from `getent` - just checking the hosts file for an entry - await execSync(`sed 's/#.*//' /etc/hosts | grep -w "${devHost}"`); - } catch (err) { - console.log(kleur.bold().red(`\nšŸ”šŸ’„ /etc/hosts is missing ${devHost}\n`)); - console.log([ - '> Please add "', - kleur.green(`127.0.0.1 ${devHost}`), - '" to your /etc/hosts file', - ].join('')); - console.log('\n\n'); - process.exit(1); - } - - const devUiPath = path.resolve(`${__dirname}/../../dev-ui-dist/`); - - // ensure the dev ui dist files actually exist (should only be a problem during local dev) - let devUiIndexHtml: string; - try { - devUiIndexHtml = await fs.promises.readFile(path.join(devUiPath, '/index.html'), 'utf-8'); - } catch (err) { - throw new Error('dev ui dist files not found'); - } - - if (devSsl) { - if (devSsl && devHost === 'localhost') { - throw new Error('To enable local ssl, dev host must not be localhost. Use something like "dmno.dev.local"'); - } - const certDir = path.join(this.configLoader.workspaceRootPath, '.dmno', 'certs'); - const { key, cert } = await createLocalSslCert(devHost, certDir); - this.httpServer = createHttpsServer({ key, cert }); - } else { - this.httpServer = createServer(); - } - this.httpServer.on('request', async (request, response) => { - let reqPath = request.url; - if (!reqPath || reqPath === '/') reqPath = '/index.html'; - debugWeb('http request', reqPath); - const fullPath = path.join(devUiPath, reqPath); - const extension = fullPath.split('.').pop(); - try { - const fileContents = await fs.promises.readFile(fullPath, 'utf-8'); - const responseMimeType = (MIME_TYPES as any)[extension || '']; - response.writeHead(200, { 'content-type': responseMimeType }); - response.end(fileContents, 'utf-8'); - } catch (err) { - if ((err as any).code === 'ENOENT') { - if (reqPath.startsWith('/assets/')) { - response.writeHead(404, { 'content-type': 'text/html' }); - response.end('Oops! File does not exist'); - } else { - response.writeHead(200, { 'content-type': 'text/html' }); - response.end(devUiIndexHtml); - } - } else { - throw err; - } - } - }); - - this.socketIoServer = new SocketIoServer(this.httpServer, { - path: '/ws', - serveClient: false, - // allowRequest: (req, callback) => { - // console.log('checking', req); - // callback(null, true); - // }, - cors: { origin: '*' }, - }); - this.socketIoServer.on('connection', (socket) => { - debugWeb('socket connection'); - // let handshake = socket.handshake; - - socket.on('reload', async () => { - await this.workspace.resolveConfig(); - socket.emit('workspace-update', this.workspace.toJSON()); - }); - - socket.on('launch-editor', async (fileUrl) => { - launchEditor(fileUrl); - }); - - socket.onAny((event, args) => { - debugWeb('socket event!', event, args); - }); - - socket.on('request', async (message) => { - debugWeb('received request over websocket', message); - const handler = (this.requestHandlers as any)[message.requestType]; - if (!handler) { - throw new Error(`No handler for request type: ${message.requestType}`); - } - - // we may receive a request before the config loader is ready - await this.configLoader.isReady; - await this.ipcReady; // probably not necessary - const result = await handler(message.payload); - socket.emit('request-response', { - requestId: message.requestId, - response: result, - }); - }); - - debugWeb('connected!'); - }); - - this._webServerUrl = `${devSsl ? 'https' : 'http'}://${devHost}:${devPort}`; - debugWeb('booting web server', this._webServerUrl); - this.httpServer.listen(devPort, devHost, () => { - this.webServerListeningDeferred.resolve(); - }); - } - - - - private requestHandlers = {} as Record; - private registerRequestHandler( - requestType: K, - handler: (payload: ConfigLoaderRequestMap[K]['payload']) => Promise, - ) { - // console.log(`registered handler for requestType: ${requestType}`); - if (this.requestHandlers[requestType]) { - throw new Error(`Duplicate IPC request handler detected for requestType "${requestType}"`); - } - this.requestHandlers[requestType] = handler; - } - - - // eslint-disable-next-line class-methods-use-this - shutdown() { - ipc.disconnect('dmno'); - } - - private ipcReadyDeferred = createDeferredPromise(); - private get ipcReady() { return this.ipcReadyDeferred.promise; } - private initIpcServer() { - // NOTE - we may want to initialize an ipc instance rather than using the global setup - // but the TS types (from DefinitelyTyped) aren't working well for that :( - ipc.config.id = 'dmno'; - ipc.config.retry = 1500; - ipc.config.silent = true; - - // currently this defaults to using a socket at `/tmp/app.dmno` - // we could put the socket in the root .dmno folder? - // or at least name it differently? - ipc.serve(`/tmp/${this.uuid}.dmno.sock`); // this has a callback... we aren't waiting here - - ipc.server.on('start', () => { - debugTimer('IPC server started'); - }); - - ipc.server.on('connect', (msg) => { - debugTimer('ipc server connect event'); - }); - - ipc.server.on('error', (err) => { - debug('IPC error: ', err); - }); - - ipc.server.on('socket.disconnected', (socket, destroyedSocketID) => { - ipc.log(`client ${destroyedSocketID} has disconnected!`); - }); - - ipc.server.on('request', async (message, socket) => { - debug('received request from IPC client', message); - const handler = (this.requestHandlers as any)[message.requestType]; - if (!handler) { - throw new Error(`No handler for request type: ${message.requestType}`); - } - - // we may receive a request before the config loader is ready - await this.configLoader.isReady; - await this.ipcReady; // probably not necessary - const result = await handler(message.payload); - ipc.server.emit(socket, 'request-response', { - requestId: message.requestId, - response: result, - }); - }); - - // ipc.server.on('event', (eventMessage) => { - // console.log('ipc server received event', eventMessage); - // return this.eventBus.emit(eventMessage.eventType, eventMessage.payload); - // }); - - ipc.server.on('ready', (response) => { - debugTimer('IPC server received ready signal'); - this.ipcReadyDeferred.resolve(); - // this.readyAt = new Date(); - - // debug(kleur.yellow(`took ${+this.readyAt - +this.startAt} ms to boot`)); - }); - - debugTimer('ipc server start!'); - ipc.server.start(); - - - // process.on('SIGKILL', () => { - // console.log('CONFIG SERVER - SIGKILL'); - // }); - process.on('SIGTERM', () => { - // console.log('CONFIG SERVER PROCESS - SIGTERM'); - }); - process.on('SIGINT', () => { - // console.log('CONFIG SERVER PROCESS - SIGINT'); - }); - - process.on('exit', (code) => { - // TODO: can be smarter about tracking what needs to be shut down - try { - ipc.server.stop(); - } catch (err) { - - } - }); - } - - eventBus = mitt(); - onEvent(eventType: string, handler: Handler) { - // console.log('loader process subscribe to event', eventType, handler); - this.eventBus.on(eventType, handler); - } - - - - // eslint-disable-next-line class-methods-use-this - private broadcastIpcEvent(type: string, payload: any) { - ipc.server.broadcast('event', { type, payload }); - } - - onReload?: () => void; - private onConfigReload() { - this.broadcastIpcEvent('reload', {}); - if (this.onReload) this.onReload(); - - this.socketIoServer?.emit('workspace-update', this.workspace.toJSON()); - } - - - // request handlers ////////////////////////////////////////// - - registerIpcRequestHandlers() { - this.registerRequestHandler('load-full-schema', async (payload) => { - await this.workspace.resolveConfig(); - return this.workspace.toJSON(); - }); - - this.registerRequestHandler('get-resolved-config', async (payload) => { - // if selecting by package name, we'll first make sure the package is valid and initialized - // this may need to move somewher else / happen earlier when setting up `dmno dev`? - if (payload.packageName) { - const selectedPackageInfo = this.configLoader.workspacePackagesData.find((p) => p.name === payload.packageName); - if (selectedPackageInfo) { - if (!selectedPackageInfo.dmnoFolder) { - const packageManager = detectJsPackageManager(); - console.log(`\nšŸšØ Package ${selectedPackageInfo.name} has not yet been initialized as a DMNO service`); - console.log(); - // TODO we'll want a helper to get commands for the current package manager (pnpm exec dmno) - // could also detect current directory and skip the cd - console.log('Please run the following command to get it set up:'); - console.log(kleur.cyan(` cd ${selectedPackageInfo.path} && ${packageManager.exec} dmno init`)); - console.log(); - process.exit(1); - } - } else { - throw new Error(`Package ${payload.packageName} does not exist in your workspace`); - } - } - - - await this.workspace.resolveConfig(); - const service = this.workspace.getService(payload); - if (!service) { - throw new Error(`Unable to select service - ${payload.serviceName || payload.packageName}`); - } - - return { - serviceDetails: service.toJSON(), - injectedEnv: service.getInjectedEnvJSON(), - }; - }); - - - // registerRequestHandler('generate-types', async (payload) => { - // if (!schemaLoaded) await reloadAllConfig(); - // const service = dmnoWorkspace.getService(payload); - // if (!service) throw new Error('Unable to select a service'); - - // return { tsSrc: await generateTypescriptTypes(service) }; - // }); - } -} - - - - - diff --git a/packages/core/src/config-loader/dmno-server.ts b/packages/core/src/config-loader/dmno-server.ts new file mode 100644 index 00000000..f2c3a096 --- /dev/null +++ b/packages/core/src/config-loader/dmno-server.ts @@ -0,0 +1,455 @@ +import https from 'node:https'; +import path, { dirname } from 'node:path'; +import fs from 'node:fs'; +import crypto from 'node:crypto'; +import { fileURLToPath } from 'node:url'; + +import { Server as SocketIoServer } from 'socket.io'; +import uWS from 'uWebSockets.js'; +import launchEditor from 'launch-editor'; +import Debug from 'debug'; +import { CacheMode } from '@dmno/configraph'; +import { createDeferredPromise } from '@dmno/ts-lib'; +import { ConfigLoader } from './config-loader'; +import { loadOrCreateTlsCerts } from '../lib/certs'; +import { pathExists } from '../lib/fs-utils'; +import { findDmnoServices } from './find-services'; +import { MIME_TYPES_BY_EXT, uwsBodyParser, uwsValidateClientCert } from '../lib/uws-utils'; + + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +// TODO: read port from workspace yaml file, or just pick an available one? +const STATIC_PORT = 3666; + +// TODO: do we want to allow changing the host? or just always use localhost? +// see old config-server.ts for details + +// TODO: do we want to allow toggling OFF ssl for the web ui? + + + +function getCurrentPackageName() { + if (process.env.npm_package_name !== undefined) return process.env.npm_package_name; + if (process.env.PNPM_PACKAGE_NAME !== undefined) return process.env.PNPM_PACKAGE_NAME; +} + + +export class DmnoServer { + readonly serverId: string; + private isChildServer: boolean = false; + private configLoader?: ConfigLoader; + + constructor(readonly opts?: { + watch?: boolean, + enableWebUi?: boolean, + }) { + if (process.env.DMNO_CONFIG_SERVER_UUID) { + this.serverId = process.env.DMNO_CONFIG_SERVER_UUID; + this.isChildServer = true; + this.webServerReady = this.initChildServer(); + } else { + this.serverId = crypto.randomUUID(); + console.time('init configloader'); + this.configLoader = new ConfigLoader(!!opts?.watch); + this.webServerReady = this.bootWsServer(); + } + } + + + private async initChildServer() { + const workspaceInfo = await findDmnoServices(); + const workspaceRootPath = workspaceInfo.workspacePackages[0].path; + const certDir = `${workspaceRootPath}/.dmno/certs`; + this.certs = await loadOrCreateTlsCerts('localhost', certDir); + } + + + readonly webServerReady?: Promise; + private uwsServer?: uWS.TemplatedApp; + private uwsPort = STATIC_PORT; + private certs?: Awaited>; + + private _webServerUrl?: string; + public get webServerUrl() { return this._webServerUrl; } + + private socketIoServer: SocketIoServer | undefined; + + async bootWsServer() { + // TODO: we could wait until the workspace info is loaded only + if (!this.configLoader) throw new Error('no configLoader'); + await this.configLoader.isReady; + + const uwsServerListeningDeferred = createDeferredPromise(); + + const certDir = `${this.configLoader.workspaceRootPath}/.dmno/certs`; + this.certs = await loadOrCreateTlsCerts('localhost', certDir); + + + const devUiPath = path.resolve(`${__dirname}/../dev-ui-dist/`); + + // ensure the dev ui dist files actually exist (should only be a problem during local dev) + let devUiIndexHtml: string; + if (this.opts?.enableWebUi) { + try { + devUiIndexHtml = await fs.promises.readFile(path.join(devUiPath, '/index.html'), 'utf-8'); + } catch (err) { + throw new Error(`dev ui dist files not found @ ${devUiPath}`); + } + } + + this.uwsServer = uWS.SSLApp({ + cert_file_name: path.join(certDir, 'SERVER.crt'), + key_file_name: path.join(certDir, 'SERVER_key.pem'), + ca_file_name: path.join(certDir, 'CA.crt'), + // passphrase: '1234', + }) + .any('/api/:requestName', async (res, req) => { + /* Can't return or yield from here without responding or attaching an abort handler */ + res.onAborted(() => { + res.aborted = true; + }); + + if (!uwsValidateClientCert(res, this.certs!.caCert)) return; + + const reqName = req.getParameter(0) || ''; + if (!(reqName in this.commands)) { + res.writeStatus('404'); + res.end(JSON.stringify({ error: 'Not found!' })); + return; + } + + // parse the body to get function args + let args; + if (req.getMethod() === 'post') { + try { + args = await uwsBodyParser(res); + } catch (err) { + res.writeStatus('400'); + console.log(err); + res.end(JSON.stringify({ error: 'body parsing failed' })); + return; + } + } else if (req.getMethod() === 'get') { + args = []; + } else { + res.writeStatus('404'); + res.end(JSON.stringify({ error: 'unsupported method' })); + return; + } + + + // @ts-ignore + const rawResponse = await this.commands[reqName].call(this, ...args); + + /* If we were aborted, you cannot respond */ + if (!res.aborted) { + res.cork(() => { + res.end(JSON.stringify(rawResponse)); + }); + } + }) + .any('/*', async (res, req) => { + res.onAborted(() => { + res.aborted = true; + }); + + if (!uwsValidateClientCert(res, this.certs!.caCert)) return; + + if (!this.opts?.enableWebUi) { + res.writeStatus('404'); + res.writeHeader('content-type', 'text/html'); + res.end('

dmno web ui is disabled

Run `dmno dev` to boot the web dashboard

'); + return; + } + + // have to use .any for the route matching to work properly + if (req.getMethod() !== 'get') { + res.writeStatus('404'); + res.end(JSON.stringify({ error: 'method not supported' })); + } + + + let reqPath = req.getUrl(); + if (!reqPath || reqPath === '/') reqPath = '/index.html'; + // debugWeb('http request', reqPath); + + + const fullPath = path.join(devUiPath, reqPath); + const extension = fullPath.split('.').pop(); + + let fileContents = devUiIndexHtml; + let contentType = 'text/html'; + + try { + fileContents = await fs.promises.readFile(fullPath, 'utf-8'); + contentType = (MIME_TYPES_BY_EXT as any)[extension || '']; + } catch (err) { + if ((err as any).code === 'ENOENT') { + if (reqPath.startsWith('/assets/')) { + res.writeStatus('404'); + res.writeHeader('content-type', 'text/html'); + res.end('

oops! file does not exist

'); + return; + } + } else { + throw err; + } + } + + if (!res.aborted) { + res.cork(() => { + res.writeStatus('200'); + res.writeHeader('content-type', contentType); + res.end(fileContents); + }); + } + }) + .listen(this.uwsPort, (token) => { + this.uwsPort = uWS.us_socket_local_port(token); + this._webServerUrl = `https://localhost:${this.uwsPort}`; + if (token) { + console.log(`Listening to port ${this.uwsPort}`); + } else { + console.log('Failed finding available port'); + } + uwsServerListeningDeferred.resolve(); + }); + + process.on('exit', (code) => { + // TODO: can be smarter about tracking what needs to be shut down + try { + this.uwsServer?.close(); + } catch (err) { + + } + }); + + await uwsServerListeningDeferred.promise; + + if (this.opts?.enableWebUi) { + await this.initSocketIoServer(); + } + } + + async initSocketIoServer() { + if (!this.configLoader) throw new Error('configLoader not found'); + + const debugWeb = Debug('dmno:webserver'); + + this.socketIoServer = new SocketIoServer({ + path: '/ws', + serveClient: false, + // allowRequest: (req, callback) => { + // console.log('checking', req); + // callback(null, true); + // }, + cors: { origin: '*' }, + }); + this.socketIoServer.attachApp(this.uwsServer); + this.socketIoServer.on('connection', (socket) => { + debugWeb('socket connection'); + // let handshake = socket.handshake; + + socket.on('reload', async () => { + const workspace = await this.configLoader?.getWorkspace(); + socket.emit('workspace-update', workspace); + }); + + socket.on('launch-editor', async (fileUrl) => { + launchEditor(fileUrl); + }); + + socket.onAny((event, args) => { + debugWeb('socket event!', event, args); + }); + + socket.on('request', async (message) => { + debugWeb('received request over websocket', message); + + await this.configLoader!.isReady; + + const result = await this.makeRequest(message.requestType, ...message.args); + + socket.emit('request-response', { + requestId: message.requestId, + response: result, + }); + }); + + debugWeb('connected!'); + }); + } + + + shutdown() { + this.uwsServer?.close(); + this.configLoader?.shutdown().catch(() => { + console.log('error shutting down dmno vite dev server'); + }); + } + + async makeRequest< + K extends keyof typeof this.commands, + >( + requestName: K, + ...args: Parameters + ): Promise> { + console.log('make request', requestName); + // have to wait for server to be ready before we can send a request + await this.webServerReady; + + // In theory, we could check if we are currently in the parent server + // and if so skip communicating over http/uws + // but the overhead seems negligible? + + // if not a child, we will wait for the config loader to finish loading + if (!this.isChildServer) { + await this.configLoader?.isReady; + console.timeEnd('init configloader'); + + // we can bypass the http request if this is not a child server + // but the overhead is very minimal, so we will re-enable this later + // return (this.commands[requestName] as any).apply(this, args); + } + + const rawResult = await this.mTlsFetchHelper(`/api/${requestName}`, args); + const result = JSON.parse(rawResult as any); + // const result = await rawResult.json(); + return result; + } + + private async mTlsFetchHelper(urlPath: string, data?: any) { + const deferred = createDeferredPromise(); + if (!this.certs) throw new Error('missing certs!'); + + const postData = JSON.stringify(data); + + const clientOptions = { + hostname: 'localhost', + port: this.uwsPort, + path: urlPath, + method: 'POST', + ...data !== undefined && { + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(postData), + }, + }, + key: this.certs.clientKey, + cert: this.certs.clientCert, + ca: [this.certs.caCert], + rejectUnauthorized: false, + }; + + const req = https.request(clientOptions, (res) => { + let data = ''; + res.on('data', (chunk) => { + data += chunk; + }); + res.on('end', () => { + // console.log('Response from server:', data); + deferred.resolve(data); + }); + }); + + req.on('error', (e) => { + // console.error('errp', e); + deferred.reject(e); + }); + + if (data !== undefined) req.write(postData); + req.end(); + + return deferred.promise; + } + + + + setCacheMode(cacheMode: CacheMode) { + if (this.configLoader) { + this.configLoader.cacheMode = cacheMode; + } + } + enableWatchMode(onReload: () => void | Promise) { + if (this.configLoader) { + console.log('enable watch mode'); + this.configLoader.devMode = true; + this.configLoader.onReload = async () => { + console.log('configLoader triggered reload handler'); + await onReload(); + + if (this.socketIoServer) { + const workspace = await this.configLoader?.getWorkspace(); + this.socketIoServer.emit('workspace-update', workspace); + } + }; + } + } + + // ~ external facing functions to interact with config + commands = { + ping() { + return { pong: true }; + }, + loadFullSchema: async () => { + console.time('get workspace'); + const workspace = await this.configLoader?.getWorkspace()!; + console.timeEnd('get workspace'); + + console.time('resolve'); + await workspace.resolveConfig(); + console.timeEnd('resolve'); + return workspace.toJSON(); + }, + getInjectedJson: async (serviceId: string) => { + const workspace = await this.configLoader?.getWorkspace()!; + const service = workspace.getService(serviceId); + return service.getInjectedEnvJSON(); + }, + clearCache: async () => { + const workspace = await this.configLoader?.getWorkspace()!; + const cacheFilePath = workspace.configraph.cacheProvider.cacheFilePath; + + let wasDeleted = false; + if (await pathExists(cacheFilePath)) { + await workspace.configraph.cacheProvider.reset(); + wasDeleted = true; + } + + return { + cacheFilePath, + wasDeleted, + }; + }, + }; + + async getWorkspace() { + return this.makeRequest('loadFullSchema'); + } + + // TODO: this isnt necessarily in the right place + // but the logic was moved from ConfigServerClient and it is convenient to have + // this within whatever will be imported and used within integrations (vite plugins) + async getCurrentPackageConfig() { + const packageName = getCurrentPackageName(); + if (packageName === '') { + throw new Error('To use dmno, you must set a package "name" in your package.json file'); + } + // what to do if we can't figure out a package name? + + const workspace = await this.getWorkspace(); + const service = Object.values(workspace.services).find((s) => s.packageName === packageName); + if (!service) { + throw new Error(`Unable to select service - ${packageName}`); + } + + const injectedEnv = await this.makeRequest('getInjectedJson', service.serviceName); + + return { + serviceDetails: service, + injectedEnv, + }; + } +} + diff --git a/packages/core/src/config-loader/find-services.ts b/packages/core/src/config-loader/find-services.ts index 5b611a32..f4f3efe9 100644 --- a/packages/core/src/config-loader/find-services.ts +++ b/packages/core/src/config-loader/find-services.ts @@ -1,6 +1,5 @@ import fs from 'node:fs'; import path from 'node:path'; -import { fileURLToPath } from 'node:url'; import _ from 'lodash-es'; import readYamlFile from 'read-yaml-file'; import { fdir } from 'fdir'; @@ -11,8 +10,12 @@ import { pathExists } from '../lib/fs-utils'; const debug = Debug('dmno:find-services'); +const jsonFileCache: Record = {}; export async function readJsonFile(path: string) { - return JSON.parse(await fs.promises.readFile(path, 'utf8')); + if (jsonFileCache[path]) return jsonFileCache[path]; + const packageJsonObj = JSON.parse(await fs.promises.readFile(path, 'utf8')); + jsonFileCache[path] = packageJsonObj; + return packageJsonObj; } @@ -56,7 +59,7 @@ const WORKSPACE_SETTINGS_LOCATIONS = [ // { file: 'deno.jsonc', path: 'workspace', optional: true }, ]; -export async function findDmnoServices(includeUnitialized = true): Promise { +export async function findDmnoServices(includeUnitialized = false): Promise { const startAt = new Date(); let gitRootPath: string | undefined; @@ -134,14 +137,17 @@ export async function findDmnoServices(includeUnitialized = true): Promise path.join(dmnoWorkspaceRootPath, gi)); - const packageGlobs = fullPackagePatterns.filter((s) => s.includes('*')); - const packageDirs = fullPackagePatterns.filter((s) => !s.includes('*')); + const patternsByType = _.groupBy( + fullPackagePatterns, + (s) => (s.includes('*') ? 'globs' : 'dirs'), + ); + const expandedPathsFromGlobs = await ( // tried a few different libs here (tiny-glob being the other main contender) and this is WAY faster especially with some tuning :) new fdir() // eslint-disable-line new-cap .withBasePath() .onlyDirs() - .glob(...packageGlobs) + .glob(...patternsByType.globs || []) .exclude((dirName, _dirPath) => { // this helps speed things up since it stops recursing into these directories return ( @@ -156,7 +162,7 @@ export async function findDmnoServices(includeUnitialized = true): Promise p.replace(/\/$/, '')); // remove trailing slash packagePaths = _.uniq(packagePaths); diff --git a/packages/core/src/config-loader/ipc-requests.ts b/packages/core/src/config-loader/ipc-requests.ts deleted file mode 100644 index 47e74722..00000000 --- a/packages/core/src/config-loader/ipc-requests.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { InjectedDmnoEnv } from '../config-engine/config-engine'; -import { SerializedService, SerializedWorkspace } from './serialization-types'; - -export type ConfigLoaderRequestMap = { - 'load-full-schema': { - payload: undefined | { resolve?: boolean }, - response: SerializedWorkspace, - }, - 'get-resolved-config': { - payload: { - serviceName?: string, - packageName?: string, - }, - response: { serviceDetails: SerializedService, injectedEnv: InjectedDmnoEnv }, - }, - 'generate-types': { - payload: { serviceName?: string }, - response: { tsSrc: string }, - }, -}; diff --git a/packages/core/src/config-loader/serialization-types.ts b/packages/core/src/config-loader/serialization-types.ts index ab5e0bda..10c85a7a 100644 --- a/packages/core/src/config-loader/serialization-types.ts +++ b/packages/core/src/config-loader/serialization-types.ts @@ -83,6 +83,7 @@ ConfigraphDataTypeDefinition, export type SerializedDmnoPlugin = Omit & { + cliPath?: string, inputNodes: Record }; diff --git a/packages/core/src/config-loader/vite-server.ts b/packages/core/src/config-loader/vite-server.ts index 07f1f3a1..86283175 100644 --- a/packages/core/src/config-loader/vite-server.ts +++ b/packages/core/src/config-loader/vite-server.ts @@ -1,8 +1,18 @@ +import { fileURLToPath } from 'url'; import { HmrContext, Plugin, createServer } from 'vite'; import { ViteNodeRunner } from 'vite-node/client'; import { ViteNodeServer } from 'vite-node/server'; import { installSourcemapsSupport } from 'vite-node/source-map'; import MagicString from 'magic-string'; +import buildEsmResolver from 'esm-resolve'; + +// this lets us detect what is the current executing dmno +// const esmResolver = buildEsmResolver(process.cwd(), { +// isDir: true, +// constraints: 'node', +// resolveToAbsolute: true, +// }); +// const currentDmnoPath = esmResolver('dmno'); export async function setupViteServer(opts: { workspaceRootPath: string, @@ -28,6 +38,7 @@ export async function setupViteServer(opts: { // pointing at dist/index is hard-coded... // we could extract the main entry point from the resolution instead? id: `${opts.workspaceRootPath}/node_modules/dmno/dist/index.js`, + // id: currentDmnoPath, external: 'absolute', }; } @@ -38,7 +49,7 @@ export async function setupViteServer(opts: { // TODO: we probably should limit which files this applies in const fixedCode = new MagicString(code); if (!code.includes("import { getResolverCtx } from 'dmno'")) { - fixedCode.prepend('import { getResolverCtx } from \'dmno\';\n'); + fixedCode.prepend("import { getResolverCtx } from 'dmno';\n"); } fixedCode.replaceAll(/DMNO_CONFIG\.([\w\d.]+)/g, 'getResolverCtx().get(\'$1\')'); @@ -58,8 +69,9 @@ export async function setupViteServer(opts: { // ignore updates to the generated type files if (ctx.file.includes('/.dmno/.typegen/')) return; - // TODO: not too sure about this, but we shouldn't be reloading the config when the user's app code is updated // ignore files outside of the .dmno folder(s)? + // generally, we shouldn't be reloading the config when the user's app code is updated + // maybe there are exceptions (package.json? something else?) if (!ctx.file.includes('/.dmno/')) return; // console.log('hot reload in vite plugin', ctx); @@ -95,11 +107,12 @@ export async function setupViteServer(opts: { // }, // ssr: true, }, + // TODO: when watch is enabled, maybe we can we narrow down which files? ...!opts.enableWatch && { server: { watch: null } }, }); // console.log(server.config); - // this is need to initialize the plugins + // required for plugins await server.pluginContainer.buildStart({}); // create vite-node server @@ -122,18 +135,17 @@ export async function setupViteServer(opts: { debug: true, root: server.config.root, base: server.config.base, - // when having the server and runner in a different context, - // you will need to handle the communication between them - // and pass to this function + // when having the server and runner in a different context + // you will need to handle the communication between them and pass to this function async fetchModule(id) { - // console.log('fetch module', id); + // console.log('fetch module', id); return node.fetchModule(id); }, async resolveId(id, importer) { - // console.log('resolve id', id, importer); + // console.log('resolve id', id, importer); return node.resolveId(id, importer); }, }); - return { viteRunner }; + return { viteRunner, viteServer: server }; } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index bf77293b..fdcd44ec 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -4,9 +4,12 @@ export * from './config-engine/authoring-utils'; export * from './config-engine/configraph-adapter'; export * from './config-engine/dmno-plugin'; export * from './config-engine/data-types'; -export * from './config-loader/config-server-client'; +export * from './config-engine/check-errors-helpers'; +export * from './config-loader/dmno-server'; export * from './globals-injector/injector'; + + export * from './config-loader/serialization-types'; // used by 1pass plugin - will likely extract eventually diff --git a/packages/core/src/lib/certs.ts b/packages/core/src/lib/certs.ts index 745273f9..9c148350 100644 --- a/packages/core/src/lib/certs.ts +++ b/packages/core/src/lib/certs.ts @@ -1,53 +1,347 @@ +// many mTLS examples led to certs that did not actually work in the browser +// this code was heavily inspired by https://github.com/BenEdridge/mutual-tls +// which actually gives good complete examples that work + import fs from 'node:fs'; import path from 'node:path'; -import { createCA, createCert } from 'mkcert'; +import forge from 'node-forge'; import { pathExists } from './fs-utils'; -export async function createLocalSslCert(domain: string, certDirPath: string = '') { - const certPath = path.join(certDirPath, 'local.crt'); - const keyPath = path.join(certDirPath, 'local.key'); - let cert: string | undefined; - let key: string | undefined; +const { pki } = forge; + +type CertSubjects = Array; +type CertConfig = { + attrs: CertSubjects, + extensions?: Array, +}; + +const CERT_KEYSIZE = 2048; +// TODO: do we want to let you use a non localhost host? +const CERT_HOST = 'localhost'; +// TODO: do we want to let the user pick the password? Or use none? +const CERT_PASS = 'password'; + +async function loadCertPem(certDirPath: string, prefix: string, isKey = false) { + return fs.promises.readFile(path.join(certDirPath, `${prefix}${isKey ? '_key.pem' : '.crt'}`), 'utf8'); +} + +export async function loadOrCreateTlsCerts(domain: string, certDirPath: string) { + try { + const clientKey = await loadCertPem(certDirPath, 'CLIENT', true); + const clientCert = await loadCertPem(certDirPath, 'CLIENT', false); + const serverKey = await loadCertPem(certDirPath, 'SERVER', true); + const serverCert = await loadCertPem(certDirPath, 'SERVER', false); + const caCert = await loadCertPem(certDirPath, 'CA', false); - if (certDirPath) { - try { - cert = await fs.promises.readFile(certPath, 'utf-8'); - key = await fs.promises.readFile(keyPath, 'utf-8'); - } catch (err) { - // console.log('error...?', err); - } + // console.log('loaded existing tls certs'); + return { + clientKey, + clientCert, + serverKey, + serverCert, + caCert, + }; + } catch (err) { + // console.log('generating new tls certs'); + return createTlsCerts(domain, certDirPath); } +} + +const CA: CertConfig = Object.freeze({ + attrs: [ + { + name: 'commonName', + value: 'DMNO Local Certificate Authority', + }, + { + name: 'countryName', + value: 'US', + }, + { + shortName: 'ST', + value: 'Virginia', + }, + { + name: 'localityName', + value: 'Blacksburg', + }, + { + name: 'organizationName', + value: 'CA LTD', + }, + { + shortName: 'OU', + value: 'Local Cert Auth', + }, + ], + extensions: [ + { + name: 'basicConstraints', + cA: true, + }, + { + name: 'keyUsage', + keyCertSign: true, + digitalSignature: true, + nonRepudiation: true, + keyEncipherment: true, + dataEncipherment: true, + }, + { + name: 'extKeyUsage', + serverAuth: true, + clientAuth: true, + codeSigning: true, + emailProtection: true, + timeStamping: true, + }, + { + name: 'nsCertType', + client: true, + server: true, + email: true, + objsign: true, + sslCA: true, + emailCA: true, + objCA: true, + }, + { + name: 'subjectAltName', + altNames: [ + { + type: 6, // URI + value: 'http://localhost', + }, + { + type: 7, // IP + ip: '127.0.0.1', + }, + ], + }, + { + name: 'subjectKeyIdentifier', + }, + ], +}); + +const SERVER: CertConfig = Object.freeze({ + attrs: [{ + name: 'commonName', + value: CERT_HOST, + }, { + name: 'countryName', + value: 'US', + }, { + shortName: 'ST', + value: 'Virginia', + }, { + name: 'localityName', + value: 'Blacksburg', + }, { + name: 'organizationName', + value: CERT_HOST, + }, { + shortName: 'OU', + value: CERT_HOST, + }], + extensions: [ + { + name: 'basicConstraints', + cA: true, + }, + { + name: 'keyUsage', + keyCertSign: true, + digitalSignature: true, + nonRepudiation: true, + keyEncipherment: true, + dataEncipherment: true, + }, + { + name: 'extKeyUsage', + serverAuth: true, + clientAuth: true, + codeSigning: true, + emailProtection: true, + timeStamping: true, + }, + { + name: 'nsCertType', + client: true, + server: true, + email: true, + objsign: true, + sslCA: true, + emailCA: true, + objCA: true, + }, + { + name: 'subjectAltName', + altNames: [ + { + type: 6, // URI + value: 'http://localhost', + }, + { + type: 7, // IP + ip: '127.0.0.1', + }, + ], + }, + { + name: 'subjectKeyIdentifier', + }, + ], +}); + +const CLIENT = Object.freeze({ + attrs: [{ + name: 'commonName', + value: 'dmno.dev local client cert', + }, { + name: 'countryName', + value: 'US', + }, { + shortName: 'ST', + value: 'Virginia', + }, { + name: 'localityName', + value: 'Blacksburg', + }, { + name: 'organizationName', + value: 'test client', + }, { + shortName: 'OU', + value: 'dmno.dev local client cert', + }], +}); - if (!cert) { - const ca = await createCA({ - organization: 'DMNO', - countryCode: 'CA', - state: 'Toronto', - locality: 'Canada', - validity: 365, - }); - - const certPair = await createCert({ - ca: { key: ca.key, cert: ca.cert }, - domains: [domain], - validity: 365, - }); - cert = certPair.cert; - key = certPair.key; - - if (certDirPath) { - if (!await pathExists(certDirPath)) { - await fs.promises.mkdir(certDirPath); - } - await fs.promises.writeFile(certPath, cert); - await fs.promises.writeFile(keyPath, key); - console.log('Generated local SSL cert'); - } + + +let counter = 1; +function buildCert( + certDirPath: string, + prefix: string, + config: CertConfig, + issuer?: CertConfig, + signer?: forge.pki.rsa.KeyPair, +) { + console.log('Building ', prefix, ' ...'); + const kp = pki.rsa.generateKeyPair(CERT_KEYSIZE); + const cert = pki.createCertificate(); + cert.publicKey = kp.publicKey; + + // NOTE: serialNumber is the hex encoded value of an ASN.1 INTEGER. + // Conforming CAs should ensure serialNumber is: no more than 20 octets, non-negative (prefix a '00' if your value starts with a '1' bit) + // cert.serialNumber = `00${crypto.randomBytes(4).toString('hex')}`; + cert.serialNumber = `00${counter++}`; + + cert.validity.notBefore = new Date(); + cert.validity.notAfter = new Date(); + cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1); + + cert.setSubject(config.attrs); + + // Set the CA.attrs as an issuer for both server and client + if (issuer) { + cert.setIssuer(CA.attrs); + } else { + cert.setIssuer(config.attrs); + } + + if (config.extensions) { + cert.setExtensions(config.extensions); } + /* optionally add more extensions + extensions.push.apply(extensions, [{ + name: 'basicConstraints', + cA: true + }, { + name: 'keyUsage', + keyCertSign: true, + digitalSignature: true, + nonRepudiation: true, + keyEncipherment: true, + dataEncipherment: true + }]); + cert.setExtensions(extensions); + */ + + signCert(cert, kp, issuer, signer); + writeToFile(certDirPath, prefix, cert, kp); + + return { keyPair: kp, certificate: cert }; +} + + + + +function signCert( + cert: forge.pki.Certificate, + keyPair: forge.pki.rsa.KeyPair, + issuer?: CertConfig, + signer?: forge.pki.rsa.KeyPair, +) { + if (!issuer && !signer) { + cert.sign(keyPair.privateKey, forge.md.sha256.create()); + } else if (signer) { + cert.sign(signer.privateKey, forge.md.sha256.create()); + } else { + throw new Error('signer not set'); + } +} + +function writeToFile( + certDirPath: string, + prefix: string, + cert: forge.pki.Certificate, + keyPair: forge.pki.rsa.KeyPair, +) { + const pem = pki.certificateToPem(cert); + try { + fs.writeFileSync(`${certDirPath}/${prefix}.crt`, pem, 'utf8'); + fs.writeFileSync(`${certDirPath}/${prefix}_key.pem`, pki.privateKeyToPem(keyPair.privateKey), 'utf8'); + } catch (e) { + console.error('Error writing files out', e); + } + console.log('Output files', `${certDirPath}/${prefix}.crt`, ' and ', `${certDirPath}/${prefix}_key.pem`); +} + +function buildAndWriteP12( + certDirPath: string, + prefix: string, + privateKey: forge.pki.rsa.PrivateKey, + cert: forge.pki.Certificate, + password: string | null = null, +) { + console.log('Building P12', prefix, ' ...'); + // generate a p12 that can be imported by Chrome/Firefox/iOS + // (requires the use of Triple DES instead of AES) + const p12Asn1 = forge.pkcs12.toPkcs12Asn1(privateKey, cert, password, { algorithm: '3des' }); + const der = forge.asn1.toDer(p12Asn1).getBytes(); + fs.writeFileSync(`${certDirPath}/${prefix}.p12`, der, 'binary'); + console.log(`Output file: ${certDirPath}/${prefix}.p12 with PASSWORD: ${password}`); +} + + +export async function createTlsCerts(domain: string, certDirPath: string) { + if (!await pathExists(certDirPath)) { + await fs.promises.mkdir(certDirPath); + } + + const ca = buildCert(certDirPath, 'CA', CA); + const client = buildCert(certDirPath, 'CLIENT', CLIENT, CA, ca.keyPair); + const server = buildCert(certDirPath, 'SERVER', SERVER, CA, ca.keyPair); + + buildAndWriteP12(certDirPath, 'CLIENT', client.keyPair.privateKey, client.certificate, CERT_PASS); return { - key, cert, + clientKey: pki.privateKeyToPem(client.keyPair.privateKey), + clientCert: pki.certificateToPem(client.certificate), + serverKey: pki.privateKeyToPem(server.keyPair.privateKey), + serverCert: pki.certificateToPem(server.certificate), + caCert: pki.certificateToPem(ca.certificate), }; } diff --git a/packages/core/src/lib/env-vars.ts b/packages/core/src/lib/env-vars.ts index 15463e58..88da88f2 100644 --- a/packages/core/src/lib/env-vars.ts +++ b/packages/core/src/lib/env-vars.ts @@ -1,5 +1,7 @@ import _ from 'lodash-es'; +let _originalProcessEnv: NodeJS.ProcessEnv; + /** * parse env vars into an object, using a special separator to denote nesting. * This idea comes from https://www.npmjs.com/package/nconf @@ -11,11 +13,16 @@ export function getConfigFromEnvVars( /** separator to interpret as nesting, defaults to "__" */ separator = '__', ) { - const config = {} as Record; + // when we are reloading within the same process + // we must ignore any _new_ process.env vars that dmno injected + if (_originalProcessEnv) return _originalProcessEnv; + + const configFromProcessEnv = {} as Record; _.each(process.env, (val, key) => { const path = key.replaceAll(separator, '.'); // _.set deals with initializing objects when necessary - _.set(config, path, val); + _.set(configFromProcessEnv, path, val); }); - return config; + _originalProcessEnv = configFromProcessEnv; + return configFromProcessEnv; } diff --git a/packages/core/src/lib/uws-utils.ts b/packages/core/src/lib/uws-utils.ts new file mode 100644 index 00000000..f0be3b6a --- /dev/null +++ b/packages/core/src/lib/uws-utils.ts @@ -0,0 +1,69 @@ +import crypto from 'node:crypto'; +import uWS from 'uWebSockets.js'; + +const MAX_BODY_SIZE = 1024; + +export const MIME_TYPES_BY_EXT = { + js: 'text/javascript', + html: 'text/html', + css: 'text/css', + ico: 'image/x-icon', + // TODO: will need more for images +}; + +export function uwsBodyParser(res: uWS.HttpResponse): Promise<{ [key: string]: any } | null> { + return new Promise((resolve, reject) => { + let buffer: Buffer = Buffer.alloc(0); + let totalSize = 0; + + res.onData((ab, isLast) => { + try { + if (res.aborted) { + reject(new Error('Request aborted')); + return; + } + + if (ab.byteLength > 0) { // I found some non-last onData with 0 byte length + // Immediately copy the ArrayBuffer into a Buffer, every return of onData neuters the ArrayBuffer + // const copy = copyArrayBuffer(ab); + const copy = ab; + totalSize += copy.byteLength; + buffer = Buffer.concat([buffer, Buffer.from(copy)]); + } + + if (totalSize > MAX_BODY_SIZE) { // define your allowed max size if it applies to you + reject(new Error('Request body too large: max 4MB allowed')); + return; + } + + if (isLast) { + // If this is the last chunk, process the final buffer + // Convert the buffer to a string and parse it as JSON + // this will fail if the buffer doesn't contain a valid JSON (e.g. length = 0) + const resolveValue = JSON.parse(buffer.toString()); + resolve(resolveValue); + } + } catch (err: any) { + reject(new Error(`Failed to parse JSON: ${err.message}`)); + } + }); + }); +} + +export function uwsValidateClientCert(res: uWS.HttpResponse, caCert: string) { + // validate client certs (mTLS) + try { + const clientCert = res.getX509Certificate(); + const x509 = new crypto.X509Certificate(clientCert); + if (!x509.verify(crypto.createPublicKey(caCert))) { + res.writeStatus('401'); + res.end(JSON.stringify({ error: 'Unauthorized!' })); + return false; + } + return true; + } catch (err) { + res.writeStatus('401'); + res.end('Unauthorized!'); + return false; + } +} diff --git a/packages/core/tsup.config.ts b/packages/core/tsup.config.ts index c22ac80f..259cf298 100644 --- a/packages/core/tsup.config.ts +++ b/packages/core/tsup.config.ts @@ -16,10 +16,6 @@ export default defineConfig({ // imported as TS directly, so we have to tell tsup to compile it instead of leaving it external noExternal: [ '@dmno/ts-lib', '@dmno/encryption-lib', - - // yarn was having issues with finding the strong-type package for some reason - // so we'll just bundle them in as a short term solution - 'node-ipc', '@achrinza/node-ipc', '@achrinza/event-pubsub', '@achrinza/strong-type' ], external: [ // mark self-imports as external so it will leave them as-is diff --git a/packages/integrations/astro/src/index.ts b/packages/integrations/astro/src/index.ts index 477dddeb..6f65c75f 100644 --- a/packages/integrations/astro/src/index.ts +++ b/packages/integrations/astro/src/index.ts @@ -3,7 +3,8 @@ import fs from 'node:fs'; import { fileURLToPath } from 'url'; import Debug from 'debug'; import { - ConfigServerClient, injectDmnoGlobals, + checkServiceIsValid, + DmnoServer, injectDmnoGlobals, } from 'dmno'; import type { AstroIntegration } from 'astro'; @@ -19,7 +20,7 @@ let dmnoHasTriggeredReload = false; let enableDynamicPublicClientLoading = false; let configItemKeysAccessed: Record = {}; let dmnoConfigValid = true; -let dmnoConfigClient: ConfigServerClient; +let dmnoServer: DmnoServer; let dmnoInjectionResult: ReturnType; let ssrOutputDirPath: string; @@ -33,15 +34,15 @@ async function reloadDmnoConfig() { dmnoInjectionResult = injectDmnoGlobals(); } else { debug('using injected dmno config server'); - (process as any).dmnoConfigClient ||= new ConfigServerClient(); - dmnoConfigClient = (process as any).dmnoConfigClient; - const resolvedService = await dmnoConfigClient.getServiceConfig(); + (process as any).dmnoServer ||= new DmnoServer({ watch: true }); + dmnoServer = (process as any).dmnoServer; + const resolvedService = await dmnoServer.getCurrentPackageConfig(); const injectedConfig = resolvedService.injectedEnv; dmnoConfigValid = resolvedService.serviceDetails.isValid; configItemKeysAccessed = {}; // shows nicely formatted errors in the terminal - ConfigServerClient.checkServiceIsValid(resolvedService.serviceDetails); + checkServiceIsValid(resolvedService.serviceDetails); dmnoInjectionResult = injectDmnoGlobals({ injectedConfig, @@ -127,9 +128,9 @@ function dmnoAstroIntegration(dmnoIntegrationOpts?: DmnoAstroIntegrationOptions) ...astroCommand === 'dev' && { async configureServer(server) { - if (!isRestart && !!dmnoConfigClient) { + if (!isRestart && !!dmnoServer) { debug('initializing dmno reload > astro restart trigger'); - dmnoConfigClient.eventBus.on('reload', () => { + dmnoServer.enableWatchMode(() => { opts.logger.info('šŸ’« dmno config updated - restarting astro server'); // eslint-disable-next-line @typescript-eslint/no-floating-promises server.restart(); diff --git a/packages/integrations/remix/src/index.ts b/packages/integrations/remix/src/index.ts index 9d7e3d3d..10cd79c8 100644 --- a/packages/integrations/remix/src/index.ts +++ b/packages/integrations/remix/src/index.ts @@ -3,7 +3,7 @@ import { fileURLToPath } from 'url'; import _ from 'lodash-es'; import Debug from 'debug'; -import { ConfigServerClient, injectDmnoGlobals } from 'dmno'; +import { checkServiceIsValid, DmnoServer, injectDmnoGlobals } from 'dmno'; import type { Plugin } from 'vite'; import type { @@ -13,7 +13,7 @@ import type { const debug = Debug('dmno:remix-integration'); const __dirname = dirname(fileURLToPath(import.meta.url)); -let firstLoad = !(process as any).dmnoConfigClient; +let firstLoad = !(process as any).dmnoServer; debug('dmno remix+vite plugin loaded. first load = ', firstLoad); @@ -21,7 +21,7 @@ let isDevMode: boolean; let dmnoHasTriggeredReload = false; let configItemKeysAccessed: Record = {}; let dmnoConfigValid = true; -let dmnoConfigClient: ConfigServerClient; +let dmnoServer: DmnoServer; let dmnoInjectionResult: ReturnType; let enableDynamicPublicClientLoading = false; @@ -33,15 +33,15 @@ async function reloadDmnoConfig() { dmnoInjectionResult = injectDmnoGlobals(); } else { debug('using injected dmno config server'); - (process as any).dmnoConfigClient ||= new ConfigServerClient(); - dmnoConfigClient = (process as any).dmnoConfigClient; - const resolvedService = await dmnoConfigClient.getServiceConfig(); + (process as any).dmnoServer ||= new DmnoServer({ watch: true }); + dmnoServer = (process as any).dmnoServer; + const resolvedService = await dmnoServer.getCurrentPackageConfig(); const injectedConfig = resolvedService.injectedEnv; dmnoConfigValid = resolvedService.serviceDetails.isValid; configItemKeysAccessed = {}; // shows nicely formatted errors in the terminal - ConfigServerClient.checkServiceIsValid(resolvedService.serviceDetails); + checkServiceIsValid(resolvedService.serviceDetails); dmnoInjectionResult = injectDmnoGlobals({ injectedConfig, @@ -63,7 +63,7 @@ type DmnoPluginOptions = { }; export function dmnoRemixVitePlugin(dmnoOptions?: DmnoPluginOptions) { - const dmnoConfigClient: ConfigServerClient = (process as any).dmnoConfigClient; + const dmnoServer: DmnoServer = (process as any).dmnoServer; // detect if we need to build the resolved config into the output // which is needed when running on external platforms where we dont have ability to use `dmno run` @@ -121,7 +121,7 @@ export function dmnoRemixVitePlugin(dmnoOptions?: DmnoPluginOptions) { // adjust vite's setting so it doesnt bury the error messages config.clearScreen = false; } else { - dmnoConfigClient.shutdown(); + dmnoServer.shutdown(); console.log('šŸ’„ DMNO config validation failed šŸ’„'); // throwing an error spits out a big useless stack trace... so better to just exit? process.exit(1); @@ -139,7 +139,7 @@ export function dmnoRemixVitePlugin(dmnoOptions?: DmnoPluginOptions) { // there are 2 vite servers running, we need to trigger the reload on the non "spa" one if (firstLoad && server.config.command === 'serve' && server.config.appType !== 'spa') { firstLoad = false; - dmnoConfigClient.eventBus.on('reload', () => { + dmnoServer.enableWatchMode(() => { debug('dmno config client received reload event - restarting vite server'); // eslint-disable-next-line @typescript-eslint/no-floating-promises server.restart(); diff --git a/packages/integrations/vite/src/index.ts b/packages/integrations/vite/src/index.ts index b2065676..a906dbd3 100644 --- a/packages/integrations/vite/src/index.ts +++ b/packages/integrations/vite/src/index.ts @@ -1,11 +1,11 @@ import _ from 'lodash-es'; import Debug from 'debug'; -import { ConfigServerClient, injectDmnoGlobals } from 'dmno'; +import { checkServiceIsValid, DmnoServer, injectDmnoGlobals } from 'dmno'; import type { Plugin } from 'vite'; const debug = Debug('dmno:vite-integration'); -let firstLoad = !(process as any).dmnoConfigClient; +let firstLoad = !(process as any).dmnoServer; debug('dmno vite plugin loaded. first load = ', firstLoad); @@ -13,7 +13,7 @@ let isDevMode: boolean; let dmnoHasTriggeredReload = false; let configItemKeysAccessed: Record = {}; let dmnoConfigValid = true; -let dmnoConfigClient: ConfigServerClient; +let dmnoServer: DmnoServer; let dmnoInjectionResult: ReturnType; async function reloadDmnoConfig() { @@ -24,15 +24,15 @@ async function reloadDmnoConfig() { dmnoInjectionResult = injectDmnoGlobals(); } else { debug('using injected dmno config server'); - (process as any).dmnoConfigClient ||= new ConfigServerClient(); - dmnoConfigClient = (process as any).dmnoConfigClient; - const resolvedService = await dmnoConfigClient.getServiceConfig(); + (process as any).dmnoServer ||= new DmnoServer({ watch: true }); + dmnoServer = (process as any).dmnoServer; + const resolvedService = await dmnoServer.getCurrentPackageConfig(); const injectedConfig = resolvedService.injectedEnv; dmnoConfigValid = resolvedService.serviceDetails.isValid; configItemKeysAccessed = {}; // shows nicely formatted errors in the terminal - ConfigServerClient.checkServiceIsValid(resolvedService.serviceDetails); + checkServiceIsValid(resolvedService.serviceDetails); dmnoInjectionResult = injectDmnoGlobals({ injectedConfig, @@ -56,7 +56,7 @@ export function injectDmnoConfigVitePlugin( injectSensitiveConfig: boolean, }, ): Plugin { - const dmnoConfigClient: ConfigServerClient = (process as any).dmnoConfigClient; + const dmnoServer: DmnoServer = (process as any).dmnoServer; return { name: 'inject-dmno-config', @@ -85,7 +85,7 @@ export function injectDmnoConfigVitePlugin( // adjust vite's setting so it doesnt bury the error messages config.clearScreen = false; } else { - dmnoConfigClient.shutdown(); + dmnoServer.shutdown(); console.log('šŸ’„ DMNO config validation failed šŸ’„'); // throwing an error spits out a big useless stack trace... so better to just exit? process.exit(1); @@ -96,7 +96,7 @@ export function injectDmnoConfigVitePlugin( // not sure about this... if (firstLoad && server.config.command === 'serve') { firstLoad = false; - dmnoConfigClient.eventBus.on('reload', () => { + dmnoServer.enableWatchMode(() => { // console.log('vite config received reload event'); // eslint-disable-next-line @typescript-eslint/no-floating-promises server.restart(); @@ -166,7 +166,7 @@ export function injectDmnoConfigVitePlugin( // console.log('hot update', file); // }, buildEnd() { - if (!isDevMode) dmnoConfigClient.shutdown(); + if (!isDevMode) dmnoServer.shutdown(); }, }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c73d5d17..65fd1da5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,9 +30,6 @@ catalogs: lodash-es: specifier: ^4.17.21 version: 4.17.21 - mitt: - specifier: ^3.0.1 - version: 3.0.1 svgo: specifier: ^3.2.0 version: 3.3.2 @@ -72,8 +69,8 @@ importers: specifier: ^2.1.0 version: 2.1.0 turbo: - specifier: ^1.13.3 - version: 1.13.3 + specifier: ^2.2.3 + version: 2.2.3 typescript: specifier: 'catalog:' version: 5.5.4 @@ -92,9 +89,6 @@ importers: lodash-es: specifier: 'catalog:' version: 4.17.21 - mitt: - specifier: 'catalog:' - version: 3.0.1 svgo: specifier: 'catalog:' version: 3.3.2 @@ -174,9 +168,6 @@ importers: diff: specifier: ^5.2.0 version: 5.2.0 - dotenv: - specifier: ^16.4.5 - version: 16.4.5 esm-resolve: specifier: ^1.0.11 version: 1.0.11 @@ -207,18 +198,12 @@ importers: magic-string: specifier: ^0.30.12 version: 0.30.12 - mitt: - specifier: 'catalog:' - version: 3.0.1 - mkcert: - specifier: ^3.2.0 - version: 3.2.0 modern-async: specifier: ^2.0.0 version: 2.0.0 - node-ipc: - specifier: npm:@achrinza/node-ipc@^10.1.10 - version: '@achrinza/node-ipc@10.1.10' + node-forge: + specifier: ^1.3.1 + version: 1.3.1 outdent: specifier: ^0.8.0 version: 0.8.0 @@ -237,6 +222,9 @@ importers: typescript: specifier: 'catalog:' version: 5.5.4 + uWebSockets.js: + specifier: github:uNetworking/uWebSockets.js#semver:^20.49.0 + version: https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/442087c0a01bf146acb7386910739ec81df06700 validate-npm-package-name: specifier: ^5.0.0 version: 5.0.0 @@ -280,9 +268,9 @@ importers: '@types/node': specifier: 'catalog:' version: 20.14.12 - '@types/node-ipc': - specifier: ^9.2.3 - version: 9.2.3 + '@types/node-forge': + specifier: ^1.3.11 + version: 1.3.11 '@types/picomatch': specifier: ^3.0.1 version: 3.0.1 @@ -1013,22 +1001,6 @@ packages: resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} engines: {node: '>=0.10.0'} - '@achrinza/event-pubsub@5.0.9': - resolution: {integrity: sha512-49RyPP7w4abKMsLRHKgB9neAH085mdHQtv+0H5TnJeOq3oK57qQRd/JD9K5Z5D3vFODsvBODiBjYkRQZKdqiAA==} - engines: {node: 13 || 14 || 16 || 17 || 18 || 19 || 20} - - '@achrinza/node-ipc@10.1.10': - resolution: {integrity: sha512-eBP/YMdiaxFHQ5OlKfYpnarMtuMmdtv+iI21UeXL1yATZEGEvRm2gqPsnio6SRlarLt9/IxTQJl0b/6S5yX+1g==} - engines: {node: 14 || 16 || 17 || 18 || 19 || 20 || 21} - - '@achrinza/strong-type@0.1.11': - resolution: {integrity: sha512-35Ou0jcGHGKQS8a6lgdan+u3BajmL315ZpDhH2/WSRNgG1jPpizhe87epgLaotN4uY3kXaEbfRU56sBPMdR5LA==} - engines: {node: 12 || 13 || 14 || 15 || 16 || 17 || 18 || 19 || 20} - - '@achrinza/strong-type@1.1.8': - resolution: {integrity: sha512-ub/mgPRjbqZmwhU3rGya745IMn9znh3cm4g2iGepzvzhZUlKhLRfdkVoUD+H1NLaHE30pRp/FQHEoHTAVcleBQ==} - engines: {node: ^12.21.0 || 14 || 15 || 16 || 17 || 18 || 19 || 20 || 21} - '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -3111,10 +3083,6 @@ packages: cpu: [x64] os: [win32] - '@node-ipc/js-queue@2.0.3': - resolution: {integrity: sha512-fL1wpr8hhD5gT2dA1qifeVaoDFlQR5es8tFuKqjHX+kdOtdNHnxkVZbtIrR2rxnMFvehkjaZRNV2H/gPXlb0hw==} - engines: {node: '>=1.0.0'} - '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -3844,8 +3812,8 @@ packages: '@types/nlcst@2.0.3': resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==} - '@types/node-ipc@9.2.3': - resolution: {integrity: sha512-/MvSiF71fYf3+zwqkh/zkVkZj1hl1Uobre9EMFy08mqfJNAmpR0vmPgOUdEIDVgifxHj6G1vYMPLSBLLxoDACQ==} + '@types/node-forge@1.3.11': + resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} @@ -5218,10 +5186,6 @@ packages: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} - commander@11.1.0: - resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} - engines: {node: '>=16'} - commander@12.0.0: resolution: {integrity: sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==} engines: {node: '>=18'} @@ -5776,10 +5740,6 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - easy-stack@1.0.1: - resolution: {integrity: sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==} - engines: {node: '>=6.0.0'} - ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} @@ -7540,10 +7500,6 @@ packages: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} - js-message@1.0.7: - resolution: {integrity: sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==} - engines: {node: '>=0.6.0'} - js-string-escape@1.0.1: resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==} engines: {node: '>= 0.8'} @@ -8439,11 +8395,6 @@ packages: resolution: {integrity: sha512-5H76ANWinB1H3twpJ6JY8uvAtpmFvHNArpilJAjXRKXSDDLPIMoZArw5SH0q9z+lLs8IrMw7Q2VWpWimFKFT1Q==} engines: {node: '>= 8.0.0'} - mkcert@3.2.0: - resolution: {integrity: sha512-026Eivq9RoOjOuLJGzbhGwXUAjBxRX11Z7Jbm4/7lqT/Av+XNy9SPrJte6+UpEt7i+W3e/HZYxQqlQcqXZWSzg==} - engines: {node: '>=16'} - hasBin: true - mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} @@ -10782,41 +10733,41 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - turbo-darwin-64@1.13.3: - resolution: {integrity: sha512-glup8Qx1qEFB5jerAnXbS8WrL92OKyMmg5Hnd4PleLljAeYmx+cmmnsmLT7tpaVZIN58EAAwu8wHC6kIIqhbWA==} + turbo-darwin-64@2.2.3: + resolution: {integrity: sha512-Rcm10CuMKQGcdIBS3R/9PMeuYnv6beYIHqfZFeKWVYEWH69sauj4INs83zKMTUiZJ3/hWGZ4jet9AOwhsssLyg==} cpu: [x64] os: [darwin] - turbo-darwin-arm64@1.13.3: - resolution: {integrity: sha512-/np2xD+f/+9qY8BVtuOQXRq5f9LehCFxamiQnwdqWm5iZmdjygC5T3uVSYuagVFsZKMvX3ycySwh8dylGTl6lg==} + turbo-darwin-arm64@2.2.3: + resolution: {integrity: sha512-+EIMHkuLFqUdJYsA3roj66t9+9IciCajgj+DVek+QezEdOJKcRxlvDOS2BUaeN8kEzVSsNiAGnoysFWYw4K0HA==} cpu: [arm64] os: [darwin] - turbo-linux-64@1.13.3: - resolution: {integrity: sha512-G+HGrau54iAnbXLfl+N/PynqpDwi/uDzb6iM9hXEDG+yJnSJxaHMShhOkXYJPk9offm9prH33Khx2scXrYVW1g==} + turbo-linux-64@2.2.3: + resolution: {integrity: sha512-UBhJCYnqtaeOBQLmLo8BAisWbc9v9daL9G8upLR+XGj6vuN/Nz6qUAhverN4Pyej1g4Nt1BhROnj6GLOPYyqxQ==} cpu: [x64] os: [linux] - turbo-linux-arm64@1.13.3: - resolution: {integrity: sha512-qWwEl5VR02NqRyl68/3pwp3c/olZuSp+vwlwrunuoNTm6JXGLG5pTeme4zoHNnk0qn4cCX7DFrOboArlYxv0wQ==} + turbo-linux-arm64@2.2.3: + resolution: {integrity: sha512-hJYT9dN06XCQ3jBka/EWvvAETnHRs3xuO/rb5bESmDfG+d9yQjeTMlhRXKrr4eyIMt6cLDt1LBfyi+6CQ+VAwQ==} cpu: [arm64] os: [linux] turbo-stream@2.2.0: resolution: {integrity: sha512-FKFg7A0To1VU4CH9YmSMON5QphK0BXjSoiC7D9yMh+mEEbXLUP9qJ4hEt1qcjKtzncs1OpcnjZO8NgrlVbZH+g==} - turbo-windows-64@1.13.3: - resolution: {integrity: sha512-Nudr4bRChfJzBPzEmpVV85VwUYRCGKecwkBFpbp2a4NtrJ3+UP1VZES653ckqCu2FRyRuS0n03v9euMbAvzH+Q==} + turbo-windows-64@2.2.3: + resolution: {integrity: sha512-NPrjacrZypMBF31b4HE4ROg4P3nhMBPHKS5WTpMwf7wydZ8uvdEHpESVNMOtqhlp857zbnKYgP+yJF30H3N2dQ==} cpu: [x64] os: [win32] - turbo-windows-arm64@1.13.3: - resolution: {integrity: sha512-ouJCgsVLd3icjRLmRvHQDDZnmGzT64GBupM1Y+TjtYn2LVaEBoV6hicFy8x5DUpnqdLy+YpCzRMkWlwhmkX7sQ==} + turbo-windows-arm64@2.2.3: + resolution: {integrity: sha512-fnNrYBCqn6zgKPKLHu4sOkihBI/+0oYFr075duRxqUZ+1aLWTAGfHZLgjVeLh3zR37CVzuerGIPWAEkNhkWEIw==} cpu: [arm64] os: [win32] - turbo@1.13.3: - resolution: {integrity: sha512-n17HJv4F4CpsYTvKzUJhLbyewbXjq1oLCi90i5tW1TiWDz16ML1eDG7wi5dHaKxzh5efIM56SITnuVbMq5dk4g==} + turbo@2.2.3: + resolution: {integrity: sha512-5lDvSqIxCYJ/BAd6rQGK/AzFRhBkbu4JHVMLmGh/hCb7U3CqSnr5Tjwfy9vc+/5wG2DJ6wttgAaA7MoCgvBKZQ==} hasBin: true type-check@0.4.0: @@ -10913,6 +10864,10 @@ packages: engines: {node: '>=14.17'} hasBin: true + uWebSockets.js@https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/442087c0a01bf146acb7386910739ec81df06700: + resolution: {tarball: https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/442087c0a01bf146acb7386910739ec81df06700} + version: 20.49.0 + ufo@1.5.3: resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} @@ -11982,21 +11937,6 @@ snapshots: '@aashutoshrathi/word-wrap@1.2.6': {} - '@achrinza/event-pubsub@5.0.9': - dependencies: - '@achrinza/strong-type': 0.1.11 - - '@achrinza/node-ipc@10.1.10': - dependencies: - '@achrinza/event-pubsub': 5.0.9 - '@achrinza/strong-type': 1.1.8 - '@node-ipc/js-queue': 2.0.3 - js-message: 1.0.7 - - '@achrinza/strong-type@0.1.11': {} - - '@achrinza/strong-type@1.1.8': {} - '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.5 @@ -14418,10 +14358,6 @@ snapshots: '@next/swc-win32-x64-msvc@14.2.3': optional: true - '@node-ipc/js-queue@2.0.3': - dependencies: - easy-stack: 1.0.1 - '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -15182,7 +15118,7 @@ snapshots: dependencies: '@types/unist': 3.0.2 - '@types/node-ipc@9.2.3': + '@types/node-forge@1.3.11': dependencies: '@types/node': 20.14.12 @@ -17134,8 +17070,6 @@ snapshots: commander@10.0.1: {} - commander@11.1.0: {} - commander@12.0.0: {} commander@2.20.3: {} @@ -17652,8 +17586,6 @@ snapshots: eastasianwidth@0.2.0: {} - easy-stack@1.0.1: {} - ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer: 5.2.1 @@ -19979,8 +19911,6 @@ snapshots: joycon@3.1.1: {} - js-message@1.0.7: {} - js-string-escape@1.0.1: {} js-stringify@1.0.2: {} @@ -21381,11 +21311,6 @@ snapshots: mixme@0.5.10: {} - mkcert@3.2.0: - dependencies: - commander: 11.1.0 - node-forge: 1.3.1 - mkdirp-classic@0.5.3: {} mkdirp@0.5.6: @@ -24385,34 +24310,34 @@ snapshots: dependencies: safe-buffer: 5.2.1 - turbo-darwin-64@1.13.3: + turbo-darwin-64@2.2.3: optional: true - turbo-darwin-arm64@1.13.3: + turbo-darwin-arm64@2.2.3: optional: true - turbo-linux-64@1.13.3: + turbo-linux-64@2.2.3: optional: true - turbo-linux-arm64@1.13.3: + turbo-linux-arm64@2.2.3: optional: true turbo-stream@2.2.0: {} - turbo-windows-64@1.13.3: + turbo-windows-64@2.2.3: optional: true - turbo-windows-arm64@1.13.3: + turbo-windows-arm64@2.2.3: optional: true - turbo@1.13.3: + turbo@2.2.3: optionalDependencies: - turbo-darwin-64: 1.13.3 - turbo-darwin-arm64: 1.13.3 - turbo-linux-64: 1.13.3 - turbo-linux-arm64: 1.13.3 - turbo-windows-64: 1.13.3 - turbo-windows-arm64: 1.13.3 + turbo-darwin-64: 2.2.3 + turbo-darwin-arm64: 2.2.3 + turbo-linux-64: 2.2.3 + turbo-linux-arm64: 2.2.3 + turbo-windows-64: 2.2.3 + turbo-windows-arm64: 2.2.3 type-check@0.4.0: dependencies: @@ -24506,6 +24431,8 @@ snapshots: typescript@5.5.4: {} + uWebSockets.js@https://codeload.github.com/uNetworking/uWebSockets.js/tar.gz/442087c0a01bf146acb7386910739ec81df06700: {} + ufo@1.5.3: {} uid-safe@2.1.5: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 227c90f5..2fd12ff2 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -10,7 +10,6 @@ catalog: "debug": "^4.3.4" "kleur": "^4.1.5" "lodash-es": "^4.17.21" - "mitt": "^3.0.1" "svgo": "^3.2.0" "tsup": "^8.2.4" "typescript": "^5.5.4" diff --git a/turbo.json b/turbo.json index 805f818d..fbb6018c 100644 --- a/turbo.json +++ b/turbo.json @@ -1,11 +1,10 @@ { "$schema": "https://turbo.build/schema.json", - "pipeline": { + "tasks": { "build": { "dependsOn": [ "^build" ], "inputs": [ "tsconfig.json", "tsconfig.*.json", "tsup.config.ts", "src/**" ], - "outputs": [ "dist/**" ], - "outputMode": "new-only" + "outputs": [ "dist/**" ] }, "build:tarball": { "dependsOn": [ "^build" ],