@platformatic/watt-extra 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +87 -0
  2. package/app.js +124 -0
  3. package/cli.js +141 -0
  4. package/clients/compliance/compliance-types.d.ts +887 -0
  5. package/clients/compliance/compliance.mjs +1049 -0
  6. package/clients/compliance/compliance.openapi.json +6127 -0
  7. package/clients/control-plane/control-plane-types.d.ts +2696 -0
  8. package/clients/control-plane/control-plane.mjs +3051 -0
  9. package/clients/control-plane/control-plane.openapi.json +13693 -0
  10. package/clients/cron/cron-types.d.ts +1479 -0
  11. package/clients/cron/cron.mjs +872 -0
  12. package/clients/cron/cron.openapi.json +9330 -0
  13. package/compliance/index.js +21 -0
  14. package/compliance/rules/dependencies.js +76 -0
  15. package/compliance/rules/utils.js +12 -0
  16. package/eslint.config.js +11 -0
  17. package/help/start.txt +12 -0
  18. package/help/watt-extra.txt +12 -0
  19. package/index.js +45 -0
  20. package/lib/banner.js +22 -0
  21. package/lib/errors.js +34 -0
  22. package/lib/utils.js +34 -0
  23. package/lib/wattpro.js +580 -0
  24. package/package.json +50 -0
  25. package/plugins/alerts.js +115 -0
  26. package/plugins/auth.js +89 -0
  27. package/plugins/compliancy.js +70 -0
  28. package/plugins/env.js +58 -0
  29. package/plugins/flamegraphs.js +100 -0
  30. package/plugins/init.js +70 -0
  31. package/plugins/metadata.js +84 -0
  32. package/plugins/scheduler.js +48 -0
  33. package/plugins/update.js +128 -0
  34. package/renovate.json +6 -0
  35. package/test/alerts.test.js +607 -0
  36. package/test/auth.test.js +128 -0
  37. package/test/auto-cache.test.js +401 -0
  38. package/test/cli.test.js +75 -0
  39. package/test/compliancy.test.js +87 -0
  40. package/test/fixtures/runtime-domains/alpha/package.json +5 -0
  41. package/test/fixtures/runtime-domains/alpha/platformatic.json +6 -0
  42. package/test/fixtures/runtime-domains/alpha/plugin.js +16 -0
  43. package/test/fixtures/runtime-domains/beta/package.json +5 -0
  44. package/test/fixtures/runtime-domains/beta/platformatic.json +6 -0
  45. package/test/fixtures/runtime-domains/beta/plugin.js +7 -0
  46. package/test/fixtures/runtime-domains/composer/package.json +5 -0
  47. package/test/fixtures/runtime-domains/composer/platformatic.json +19 -0
  48. package/test/fixtures/runtime-domains/package.json +1 -0
  49. package/test/fixtures/runtime-domains/platformatic.json +27 -0
  50. package/test/fixtures/runtime-health/package.json +20 -0
  51. package/test/fixtures/runtime-health/platformatic.json +16 -0
  52. package/test/fixtures/runtime-health/services/service-1/package.json +17 -0
  53. package/test/fixtures/runtime-health/services/service-1/platformatic.json +16 -0
  54. package/test/fixtures/runtime-health/services/service-1/plugins/example.js +6 -0
  55. package/test/fixtures/runtime-health/services/service-1/routes/root.cjs +8 -0
  56. package/test/fixtures/runtime-health/services/service-2/package.json +17 -0
  57. package/test/fixtures/runtime-health/services/service-2/platformatic.json +16 -0
  58. package/test/fixtures/runtime-health/services/service-2/plugins/example.js +6 -0
  59. package/test/fixtures/runtime-health/services/service-2/routes/root.cjs +8 -0
  60. package/test/fixtures/runtime-next/package.json +5 -0
  61. package/test/fixtures/runtime-next/platformatic.json +9 -0
  62. package/test/fixtures/runtime-next/web/next/next.config.js +2 -0
  63. package/test/fixtures/runtime-next/web/next/package.json +7 -0
  64. package/test/fixtures/runtime-next/web/next/platformatic.json +9 -0
  65. package/test/fixtures/runtime-next/web/next/src/app/direct/route.js +3 -0
  66. package/test/fixtures/runtime-next/web/next/src/app/layout.jsx +7 -0
  67. package/test/fixtures/runtime-next/web/next/src/app/page.jsx +3 -0
  68. package/test/fixtures/runtime-scheduler/main/package.json +5 -0
  69. package/test/fixtures/runtime-scheduler/main/platformatic.json +9 -0
  70. package/test/fixtures/runtime-scheduler/main/routes/root.cjs +11 -0
  71. package/test/fixtures/runtime-scheduler/package.json +1 -0
  72. package/test/fixtures/runtime-scheduler/platformatic.json +27 -0
  73. package/test/fixtures/runtime-service/main/package.json +5 -0
  74. package/test/fixtures/runtime-service/main/platformatic.json +12 -0
  75. package/test/fixtures/runtime-service/main/routes/root.cjs +11 -0
  76. package/test/fixtures/runtime-service/package.json +1 -0
  77. package/test/fixtures/runtime-service/platformatic.json +19 -0
  78. package/test/fixtures/service-1/package.json +7 -0
  79. package/test/fixtures/service-1/platformatic.json +18 -0
  80. package/test/fixtures/service-1/routes/root.cjs +48 -0
  81. package/test/fixtures/service-2/platformatic.json +21 -0
  82. package/test/fixtures/service-2/routes/root.cjs +5 -0
  83. package/test/fixtures/service-3/package.json +5 -0
  84. package/test/fixtures/service-3/platformatic.json +21 -0
  85. package/test/fixtures/service-3/routes/root.cjs +8 -0
  86. package/test/health.test.js +44 -0
  87. package/test/helper.js +274 -0
  88. package/test/init.test.js +243 -0
  89. package/test/patch-config.test.js +434 -0
  90. package/test/scheduler.test.js +71 -0
  91. package/test/send-to-icc-retry.test.js +138 -0
  92. package/test/shared-context.test.js +82 -0
  93. package/test/spawn.test.js +110 -0
  94. package/test/trigger-flamegraphs.test.js +226 -0
  95. package/test/update.test.js +519 -0
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # WattPro
2
+
3
+ WattPro is an enterprise-ready runtime manager for Platformatic applications that provides production-grade capabilities including monitoring, compliance checking, caching, authentication, and integration with Infrastructure Control Center (ICC) services.
4
+
5
+ ## Overview
6
+
7
+ WattPro wraps existing Platformatic applications (Service, Composer, Node, or Next.js) to enhance them with enterprise features without requiring any code changes. It acts as a transparent layer that:
8
+
9
+ - **Monitors** application performance and health metrics
10
+ - **Enforces** compliance policies and security rules
11
+ - **Manages** HTTP caching strategies based on ICC configuration
12
+ - **Handles** authentication and authorization flows
13
+ - **Schedules** cron jobs and periodic tasks
14
+ - **Reports** metadata and telemetry to control plane services
15
+
16
+ The runtime manager dynamically patches Platformatic configurations at startup to inject these capabilities while maintaining full compatibility with your existing applications.
17
+
18
+ ## Environment Variables
19
+
20
+ ### Required
21
+
22
+ None.
23
+
24
+ ### Optional
25
+
26
+ - `PLT_ICC_URL` - Infrastructure Control Center URL for connecting to control plane services. When not set, WattPro runs in standalone mode without ICC integration
27
+ - `PLT_APP_NAME` - Unique identifier for your application instance. Optional when `PLT_ICC_URL` is set - if not provided, it will be automatically determined from Kubernetes labels following these rules:
28
+ 1. Uses `app.kubernetes.io/instance` label first
29
+ 2. Falls back to ReplicaSet naming convention (`{app-name}-{hash}`)
30
+ - `PLT_APP_DIR` - Application directory (defaults to current working directory)
31
+ - `PLT_TEST_TOKEN` - JWT token for authentication in non-Kubernetes environments
32
+ - `PLT_LOG_LEVEL` - Logging level for the application
33
+ - `PLT_CACHE_CONFIG` - HTTP caching configuration
34
+
35
+ ### Standalone Mode
36
+
37
+ WattPro can run without connecting to an Infrastructure Control Center (ICC). When `PLT_ICC_URL` is not set:
38
+
39
+ - No ICC connection attempts are made
40
+ - All ICC-dependent plugins (alerts, compliance, metadata, scheduler) skip their operations
41
+ - The application runs with local configuration only
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ npm install @platformatic/wattpro
47
+ ```
48
+
49
+ ## Usage
50
+
51
+ Add a script to your package.json:
52
+
53
+ ```json
54
+ "scripts": {
55
+ "wattpro": "wattpro start"
56
+ }
57
+ ```
58
+
59
+ Then run:
60
+
61
+ ```bash
62
+ npm run wattpro
63
+ ```
64
+
65
+ ### Command Line Interface
66
+
67
+ WattPro provides a command-line interface:
68
+
69
+ ```bash
70
+ # Show help
71
+ wattpro --help
72
+
73
+ # Start the runtime manager
74
+ wattpro start
75
+
76
+ # Set log level
77
+ wattpro start --log-level=debug
78
+
79
+ # Set ICC URL
80
+ wattpro start --icc-url=http://icc-server:3000
81
+
82
+ # Set application name
83
+ wattpro start --app-name=my-application
84
+
85
+ # Set application directory. This is useful for development and test
86
+ wattpro start --app-dir=/path/to/application
87
+ ```
package/app.js ADDED
@@ -0,0 +1,124 @@
1
+ import avvio from 'avvio'
2
+ import { setTimeout } from 'node:timers/promises'
3
+ import init from './plugins/init.js'
4
+ import env from './plugins/env.js'
5
+ import metadata from './plugins/metadata.js'
6
+ import compliancy from './plugins/compliancy.js'
7
+ import scheduler from './plugins/scheduler.js'
8
+ import auth from './plugins/auth.js'
9
+ import update from './plugins/update.js'
10
+ import alert from './plugins/alerts.js'
11
+ import flamegraphs from './plugins/flamegraphs.js'
12
+
13
+ async function buildApp (logger) {
14
+ const app = {
15
+ log: logger,
16
+ }
17
+
18
+ avvio(app)
19
+
20
+ app
21
+ .use(env)
22
+ .use(auth)
23
+ .use(init)
24
+ .use(alert)
25
+ .use(metadata)
26
+ .use(compliancy)
27
+ .use(scheduler)
28
+ .use(update)
29
+ .use(flamegraphs)
30
+
31
+ await app.ready()
32
+
33
+ app.startRuntime = async function startRuntime () {
34
+ app.log.info('Starting Runtime -app')
35
+ try {
36
+ app.log.info('Spawning the app')
37
+ await app.wattpro.spawn()
38
+ } catch (err) {
39
+ app.log.error(err, 'Failed to spawn the app')
40
+ throw new Error('Failed to spawn the app: ' + err.message)
41
+ }
42
+ }
43
+
44
+ app.sendToICC = async function sendToICC () {
45
+ // Skip if ICC is not configured
46
+ if (!app.env.PLT_ICC_URL) {
47
+ app.log.info('PLT_ICC_URL not set, skipping ICC operations')
48
+ return
49
+ }
50
+
51
+ if (!app.wattpro.runtime) {
52
+ throw new Error('Runtime not started, cannot send to ICC')
53
+ }
54
+ try {
55
+ if (!app.instanceConfig) {
56
+ await app.initApplication()
57
+ }
58
+ await app.connectToUpdates()
59
+ await app.sendMetadata()
60
+ await app.checkCompliancy()
61
+ await app.sendSchedulerInfo()
62
+ } catch (err) {
63
+ app.log.error({ err }, 'Failed in sending data to ICC')
64
+ throw err
65
+ }
66
+ }
67
+
68
+ app.sendToICCWithRetry = async function sendToICCWithRetry () {
69
+ // Skip all ICC operations if PLT_ICC_URL is not set
70
+ if (!app.env.PLT_ICC_URL) {
71
+ app.log.info('PLT_ICC_URL not set, skipping all ICC operations')
72
+ return
73
+ }
74
+
75
+ const baseRetryInterval = Number(app.env.PLT_ICC_RETRY_TIME)
76
+ const maxRetryInterval = 60000 // Max retry interval: 1 minute
77
+
78
+ let currentRetryInterval = baseRetryInterval
79
+ let retries = 0
80
+ let capReached = false
81
+
82
+ while (true) {
83
+ // Continue indefinitely
84
+ retries++
85
+
86
+ try {
87
+ await this.sendToICC()
88
+ logger.info('Successfully sent info to ICC after retry')
89
+ return
90
+ } catch (err) {
91
+ if (!capReached) {
92
+ const waitFor = 200 * Math.pow(2, retries)
93
+ if (waitFor >= maxRetryInterval) {
94
+ capReached = true
95
+ currentRetryInterval = maxRetryInterval
96
+ } else {
97
+ currentRetryInterval = waitFor
98
+ }
99
+ }
100
+ logger.error(
101
+ {
102
+ err: err.message,
103
+ attemptNumber: retries,
104
+ nextRetryMs: currentRetryInterval,
105
+ },
106
+ `Failed to send info to ICC, retrying in ${currentRetryInterval}ms`
107
+ )
108
+ await setTimeout(currentRetryInterval)
109
+ }
110
+ }
111
+ }
112
+
113
+ app.close = async function close () {
114
+ app.log.info('Closing runtime')
115
+ if (app.wattpro.runtime) {
116
+ await app.wattpro.close()
117
+ }
118
+ await app.closeUpdates()
119
+ }
120
+
121
+ return app
122
+ }
123
+
124
+ export default buildApp
package/cli.js ADDED
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env node
2
+ import commist from 'commist'
3
+ import minimist from 'minimist'
4
+ import { resolve, join, dirname } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+ import helpMeInit from 'help-me'
7
+ import { readFileSync } from 'node:fs'
8
+ import { start, logger } from './index.js'
9
+ import { getSimpleBanner } from './lib/banner.js'
10
+
11
+ const __filename = fileURLToPath(import.meta.url)
12
+ const __dirname = dirname(__filename)
13
+
14
+ const helpMe = helpMeInit({
15
+ dir: join(__dirname, 'help'),
16
+ ext: '.txt'
17
+ })
18
+
19
+ const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'))
20
+ const commistInstance = commist()
21
+
22
+ function version () {
23
+ console.log(getSimpleBanner(pkg.version))
24
+ logger.info(`WattExtra v${pkg.version}`)
25
+ }
26
+
27
+ // Handle start command
28
+ async function startCommand (argv) {
29
+ const banner = getSimpleBanner(pkg.version)
30
+ console.log(banner)
31
+
32
+ const args = minimist(argv, {
33
+ alias: {
34
+ h: 'help',
35
+ l: 'log-level',
36
+ i: 'icc-url',
37
+ a: 'app-name',
38
+ d: 'app-dir'
39
+ },
40
+ boolean: ['help'],
41
+ string: ['log-level', 'icc-url', 'app-name', 'app-dir'],
42
+ default: {
43
+ 'log-level': 'info',
44
+ 'app-dir': process.cwd()
45
+ }
46
+ })
47
+
48
+ logger.debug({ args, argv }, 'Start command arguments')
49
+
50
+ if (args.help) {
51
+ helpMe.toStdout('start')
52
+ return true
53
+ }
54
+
55
+ // Set environment variables based on CLI options
56
+ if (args['log-level']) {
57
+ process.env.PLT_LOG_LEVEL = args['log-level']
58
+ }
59
+
60
+ if (args['icc-url']) {
61
+ process.env.PLT_ICC_URL = args['icc-url']
62
+ }
63
+
64
+ if (args['app-name']) {
65
+ process.env.PLT_APP_NAME = args['app-name']
66
+ }
67
+
68
+ if (args['app-dir']) {
69
+ process.env.PLT_APP_DIR = resolve(args['app-dir']) // Ensure the path is absolute
70
+ }
71
+
72
+ await start()
73
+ return true
74
+ }
75
+
76
+ // Handle help command
77
+ function help (args) {
78
+ // Make sure args exists and has the expected structure
79
+ const command = args && args._ ? args._[0] : undefined
80
+ helpMe.toStdout(command || 'watt-extra')
81
+ }
82
+
83
+ // Register commands
84
+ commistInstance.register('start', startCommand)
85
+ commistInstance.register('help', help)
86
+ commistInstance.register('version', version)
87
+ commistInstance.register('-h', help)
88
+ commistInstance.register('--help', help)
89
+
90
+ async function run () {
91
+ try {
92
+ logger.debug('Parsing command line arguments')
93
+ const args = process.argv.slice(2)
94
+
95
+ // Show help if no arguments are provided
96
+ if (args.length === 0) {
97
+ helpMe.toStdout('watt-extra')
98
+ return
99
+ }
100
+
101
+ // Handle help flag directly
102
+ if (args[0] === '--help' || args[0] === '-h') {
103
+ helpMe.toStdout('watt-extra')
104
+ return
105
+ }
106
+
107
+ // Handle the case where the first argument is a recognized command
108
+ if (args.length > 0) {
109
+ const command = args[0]
110
+
111
+ if (command === 'start') {
112
+ // Pass the rest of the arguments to the start command
113
+ await startCommand(args.slice(1))
114
+ return
115
+ }
116
+
117
+ if (command === 'help') {
118
+ // Handle the 'help' command with optional subcommand
119
+ const subcommand = args[1]
120
+ helpMe.toStdout(subcommand || 'watt-extra')
121
+ return
122
+ }
123
+
124
+ if (command === 'version') {
125
+ version()
126
+ return
127
+ }
128
+
129
+ logger.error(`Command "${command}" does not exist`)
130
+ helpMe.toStdout('watt-extra')
131
+ process.exit(1)
132
+ }
133
+
134
+ await commistInstance.parseAsync(args)
135
+ } catch (err) {
136
+ logger.error({ err }, 'Error running watt-extra')
137
+ process.exit(1)
138
+ }
139
+ }
140
+
141
+ run()