@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.
- package/README.md +87 -0
- package/app.js +124 -0
- package/cli.js +141 -0
- package/clients/compliance/compliance-types.d.ts +887 -0
- package/clients/compliance/compliance.mjs +1049 -0
- package/clients/compliance/compliance.openapi.json +6127 -0
- package/clients/control-plane/control-plane-types.d.ts +2696 -0
- package/clients/control-plane/control-plane.mjs +3051 -0
- package/clients/control-plane/control-plane.openapi.json +13693 -0
- package/clients/cron/cron-types.d.ts +1479 -0
- package/clients/cron/cron.mjs +872 -0
- package/clients/cron/cron.openapi.json +9330 -0
- package/compliance/index.js +21 -0
- package/compliance/rules/dependencies.js +76 -0
- package/compliance/rules/utils.js +12 -0
- package/eslint.config.js +11 -0
- package/help/start.txt +12 -0
- package/help/watt-extra.txt +12 -0
- package/index.js +45 -0
- package/lib/banner.js +22 -0
- package/lib/errors.js +34 -0
- package/lib/utils.js +34 -0
- package/lib/wattpro.js +580 -0
- package/package.json +50 -0
- package/plugins/alerts.js +115 -0
- package/plugins/auth.js +89 -0
- package/plugins/compliancy.js +70 -0
- package/plugins/env.js +58 -0
- package/plugins/flamegraphs.js +100 -0
- package/plugins/init.js +70 -0
- package/plugins/metadata.js +84 -0
- package/plugins/scheduler.js +48 -0
- package/plugins/update.js +128 -0
- package/renovate.json +6 -0
- package/test/alerts.test.js +607 -0
- package/test/auth.test.js +128 -0
- package/test/auto-cache.test.js +401 -0
- package/test/cli.test.js +75 -0
- package/test/compliancy.test.js +87 -0
- package/test/fixtures/runtime-domains/alpha/package.json +5 -0
- package/test/fixtures/runtime-domains/alpha/platformatic.json +6 -0
- package/test/fixtures/runtime-domains/alpha/plugin.js +16 -0
- package/test/fixtures/runtime-domains/beta/package.json +5 -0
- package/test/fixtures/runtime-domains/beta/platformatic.json +6 -0
- package/test/fixtures/runtime-domains/beta/plugin.js +7 -0
- package/test/fixtures/runtime-domains/composer/package.json +5 -0
- package/test/fixtures/runtime-domains/composer/platformatic.json +19 -0
- package/test/fixtures/runtime-domains/package.json +1 -0
- package/test/fixtures/runtime-domains/platformatic.json +27 -0
- package/test/fixtures/runtime-health/package.json +20 -0
- package/test/fixtures/runtime-health/platformatic.json +16 -0
- package/test/fixtures/runtime-health/services/service-1/package.json +17 -0
- package/test/fixtures/runtime-health/services/service-1/platformatic.json +16 -0
- package/test/fixtures/runtime-health/services/service-1/plugins/example.js +6 -0
- package/test/fixtures/runtime-health/services/service-1/routes/root.cjs +8 -0
- package/test/fixtures/runtime-health/services/service-2/package.json +17 -0
- package/test/fixtures/runtime-health/services/service-2/platformatic.json +16 -0
- package/test/fixtures/runtime-health/services/service-2/plugins/example.js +6 -0
- package/test/fixtures/runtime-health/services/service-2/routes/root.cjs +8 -0
- package/test/fixtures/runtime-next/package.json +5 -0
- package/test/fixtures/runtime-next/platformatic.json +9 -0
- package/test/fixtures/runtime-next/web/next/next.config.js +2 -0
- package/test/fixtures/runtime-next/web/next/package.json +7 -0
- package/test/fixtures/runtime-next/web/next/platformatic.json +9 -0
- package/test/fixtures/runtime-next/web/next/src/app/direct/route.js +3 -0
- package/test/fixtures/runtime-next/web/next/src/app/layout.jsx +7 -0
- package/test/fixtures/runtime-next/web/next/src/app/page.jsx +3 -0
- package/test/fixtures/runtime-scheduler/main/package.json +5 -0
- package/test/fixtures/runtime-scheduler/main/platformatic.json +9 -0
- package/test/fixtures/runtime-scheduler/main/routes/root.cjs +11 -0
- package/test/fixtures/runtime-scheduler/package.json +1 -0
- package/test/fixtures/runtime-scheduler/platformatic.json +27 -0
- package/test/fixtures/runtime-service/main/package.json +5 -0
- package/test/fixtures/runtime-service/main/platformatic.json +12 -0
- package/test/fixtures/runtime-service/main/routes/root.cjs +11 -0
- package/test/fixtures/runtime-service/package.json +1 -0
- package/test/fixtures/runtime-service/platformatic.json +19 -0
- package/test/fixtures/service-1/package.json +7 -0
- package/test/fixtures/service-1/platformatic.json +18 -0
- package/test/fixtures/service-1/routes/root.cjs +48 -0
- package/test/fixtures/service-2/platformatic.json +21 -0
- package/test/fixtures/service-2/routes/root.cjs +5 -0
- package/test/fixtures/service-3/package.json +5 -0
- package/test/fixtures/service-3/platformatic.json +21 -0
- package/test/fixtures/service-3/routes/root.cjs +8 -0
- package/test/health.test.js +44 -0
- package/test/helper.js +274 -0
- package/test/init.test.js +243 -0
- package/test/patch-config.test.js +434 -0
- package/test/scheduler.test.js +71 -0
- package/test/send-to-icc-retry.test.js +138 -0
- package/test/shared-context.test.js +82 -0
- package/test/spawn.test.js +110 -0
- package/test/trigger-flamegraphs.test.js +226 -0
- 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()
|