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" ],