@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
@@ -0,0 +1,21 @@
1
+ import dependenciesRule from './rules/dependencies.js'
2
+
3
+ const rules = {
4
+ npmDependencies: dependenciesRule
5
+ }
6
+
7
+ async function getCompliancyMetadata (config) {
8
+ const metadata = {}
9
+
10
+ await Promise.allSettled(
11
+ Object.keys(rules).map(async (ruleName) => {
12
+ metadata[ruleName] = await rules[ruleName](config)
13
+ })
14
+ )
15
+
16
+ return metadata
17
+ }
18
+
19
+ export {
20
+ getCompliancyMetadata
21
+ }
@@ -0,0 +1,76 @@
1
+ import { join } from 'node:path'
2
+ import { createRequire } from 'node:module'
3
+ import { readFile } from 'node:fs/promises'
4
+ import { setTimeout as sleep } from 'node:timers/promises'
5
+ import { fileExists } from './utils.js'
6
+
7
+ async function getApplicationDependencies (config) {
8
+ const { projectDir, runtime } = config
9
+
10
+ const runtimeDependencies = { runtime: {}, services: {} }
11
+
12
+ const runtimeConfig = runtime.getRuntimeConfig()
13
+ const applications = runtimeConfig.applications || []
14
+
15
+ await Promise.all(
16
+ applications.map(async (application) => {
17
+ const serviceDependencies = await getServiceDependencies(application.path)
18
+ runtimeDependencies.services[application.id] = serviceDependencies
19
+ })
20
+ )
21
+
22
+ runtimeDependencies.runtime = await getServiceDependencies(projectDir)
23
+ return runtimeDependencies
24
+ }
25
+
26
+ async function getServiceDependencies (projectDir) {
27
+ const packageJsonPath = join(projectDir, 'package.json')
28
+
29
+ const packageJson = await parsePackageJson(packageJsonPath)
30
+ if (packageJson === null) return { current: {}, dependencies: {} }
31
+
32
+ const dependencies = packageJson.dependencies || {}
33
+ const actualVersions = await getActualVersions(dependencies, projectDir)
34
+
35
+ return { current: actualVersions, dependencies }
36
+ }
37
+
38
+ async function getActualVersions (dependencies, cwd) {
39
+ const requireFromCwd = createRequire(join(cwd, 'dummy.js'))
40
+ const actualVersions = {}
41
+
42
+ await Promise.allSettled(
43
+ Object.keys(dependencies).map(async (packageName) => {
44
+ const packageJsonPath = requireFromCwd.resolve(`${packageName}/package.json`)
45
+ const packageJson = await parsePackageJson(packageJsonPath)
46
+ actualVersions[packageName] = packageJson ? packageJson.version : null
47
+ })
48
+ )
49
+
50
+ return actualVersions
51
+ }
52
+
53
+ async function parsePackageJson (packageJsonPath, attempt = 1) {
54
+ try {
55
+ const packageJsonExists = await fileExists(packageJsonPath)
56
+ if (!packageJsonExists) {
57
+ throw new Error(`package.json not found in ${packageJsonPath}`)
58
+ }
59
+
60
+ const packageJsonFile = await readFile(packageJsonPath, 'utf8')
61
+ const packageJson = JSON.parse(packageJsonFile)
62
+ return packageJson
63
+ } catch (error) {
64
+ console.error('Error parsing package.json:', error.message)
65
+ if (attempt < 3) {
66
+ console.error(`Retrying... attempt ${attempt}`)
67
+ await sleep(100)
68
+ return parsePackageJson(packageJsonPath, attempt + 1)
69
+ } else {
70
+ console.error('Max attempts reached, returning null')
71
+ return null
72
+ }
73
+ }
74
+ }
75
+
76
+ export default getApplicationDependencies
@@ -0,0 +1,12 @@
1
+ import { access } from 'node:fs/promises'
2
+
3
+ async function fileExists (path) {
4
+ try {
5
+ await access(path)
6
+ return true
7
+ } catch {
8
+ return false
9
+ }
10
+ }
11
+
12
+ export { fileExists }
@@ -0,0 +1,11 @@
1
+ import neostandard from 'neostandard'
2
+
3
+ export default neostandard({
4
+ ignores: [
5
+ ...neostandard.resolveIgnoresFromGitignore(),
6
+ 'clients/**/*',
7
+ 'test/fixtures/**/*',
8
+ 'test/tmp/**/*',
9
+ 'node_modules/**/*'
10
+ ]
11
+ })
package/help/start.txt ADDED
@@ -0,0 +1,12 @@
1
+ WattExtra - Start Command
2
+
3
+ Usage: watt-extra start [options]
4
+
5
+ Start the WattExtra runtime manager.
6
+
7
+ Options:
8
+ --log-level, -l Set log level (default: info)
9
+ --icc-url, -i Set the ICC URL (PLT_ICC_URL)
10
+ --app-name, -a Set the application name (PLT_APP_NAME)
11
+ --app-dir, -d Set the application directory (PLT_APP_DIR, default: current directory)
12
+ --help, -h Show this help message
@@ -0,0 +1,12 @@
1
+ WattExtra - The Platformatic Runtime Manager
2
+
3
+ Usage: watt-extra <command> [options]
4
+
5
+ Commands:
6
+ start Start WattExtra runtime manager
7
+ See 'watt-extra start --help' for options
8
+
9
+ version Show the version of WattExtra
10
+
11
+ General options:
12
+ --help, -h Show this help message
package/index.js ADDED
@@ -0,0 +1,45 @@
1
+ import buildApp from './app.js'
2
+ import pino from 'pino'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ const logger = pino({
6
+ level: process.env.PLT_LOG_LEVEL || 'info',
7
+ transport: {
8
+ target: 'pino-pretty',
9
+ options: {
10
+ colorize: true,
11
+ translateTime: true
12
+ }
13
+ }
14
+ })
15
+
16
+ // This starts the app and sends the info to ICC, so it's the main entry point
17
+ async function start () {
18
+ const app = await buildApp(logger)
19
+
20
+ app.log.info('Starting Runtime')
21
+ await app.startRuntime()
22
+
23
+ app.log.info('Setup health check')
24
+ await app.setupAlerts()
25
+ await app.setupFlamegraphs()
26
+
27
+ app.log.info('Sending info to ICC')
28
+ await app.sendToICCWithRetry()
29
+ return app
30
+ }
31
+
32
+ // Check if this file is being run directly
33
+ const isMain = process.argv[1] === fileURLToPath(import.meta.url)
34
+
35
+ if (isMain) {
36
+ start().catch(err => {
37
+ console.error(`Failed to start application: ${err.message}`)
38
+ process.exit(1)
39
+ })
40
+ }
41
+
42
+ export {
43
+ start,
44
+ logger
45
+ }
package/lib/banner.js ADDED
@@ -0,0 +1,22 @@
1
+ import chalk from 'chalk'
2
+
3
+ function getSimpleBanner (version) {
4
+ const versionStr = version || '0.0.1'
5
+ const versionLine = `WattExtra v${versionStr}`
6
+ const padding = ' '.repeat(33 - versionLine.length - 2)
7
+
8
+ const bannerText = `
9
+ +=================================+
10
+ | |
11
+ | ${versionLine}${padding}|
12
+ | Platformatic Runtime Manager |
13
+ | |
14
+ +=================================+
15
+ `
16
+
17
+ return chalk.green(bannerText)
18
+ }
19
+
20
+ export {
21
+ getSimpleBanner
22
+ }
package/lib/errors.js ADDED
@@ -0,0 +1,34 @@
1
+ import createError from '@fastify/error'
2
+
3
+ // Compliancy errors
4
+ const CompliancyMetadataError = createError('PLT_COMPLIANCE_METADATA_ERROR', 'Failed to send compliancy metadata')
5
+ const CompliancyStatusError = createError('PLT_COMPLIANCE_STATUS_ERROR', 'Failed to get compliance status')
6
+
7
+ // Application errors
8
+ const ApplicationNotFoundError = createError('PLT_APPLICATION_NOT_FOUND_ERROR', 'Application not found')
9
+ const ApplicationInfoError = createError('PLT_APPLICATION_INFO_ERROR', 'Failed to get application info')
10
+
11
+ // Metadata errors
12
+ const MetadataRuntimeError = createError('PLT_METADATA_RUNTIME_ERROR', 'Failed in getting and processing runtime metadata')
13
+ const MetadataError = createError('PLT_METADATA_ERROR', 'Failure in metadata processing')
14
+ const MetadataStateError = createError('PLT_METADATA_STATE_ERROR', 'Failed to save application state to Control Plane')
15
+ const MetadataAppIdError = createError('PLT_METADATA_APPID_ERROR', 'Cannot process metadata: no applicationId available')
16
+ const MetadataRuntimeNotStartedError = createError('PLT_METADATA_RUNTIME_NOT_STARTED_ERROR', 'Cannot process metadata: runtime not started')
17
+
18
+ // Status errors
19
+ const StatusSendError = createError('PLT_STATUS_SEND_ERROR', 'Failed to send application status')
20
+ const StatusAppIdError = createError('PLT_STATUS_APPID_ERROR', 'Cannot send application status: no applicationId available')
21
+
22
+ export {
23
+ CompliancyMetadataError,
24
+ CompliancyStatusError,
25
+ ApplicationNotFoundError,
26
+ ApplicationInfoError,
27
+ MetadataRuntimeError,
28
+ MetadataError,
29
+ MetadataStateError,
30
+ MetadataAppIdError,
31
+ MetadataRuntimeNotStartedError,
32
+ StatusSendError,
33
+ StatusAppIdError
34
+ }
package/lib/utils.js ADDED
@@ -0,0 +1,34 @@
1
+ import { access, rm, readdir } from 'node:fs/promises'
2
+ import { execa } from 'execa'
3
+
4
+ async function fileExists (path) {
5
+ try {
6
+ await access(path)
7
+ return true
8
+ } catch {
9
+ return false
10
+ }
11
+ }
12
+
13
+ async function dirExists (dir) {
14
+ try {
15
+ await access(dir)
16
+ const files = await readdir(dir)
17
+ return files.length > 0
18
+ } catch (err) {
19
+ if (err.code !== 'ENOENT') {
20
+ await rm(dir, { force: true, recursive: true })
21
+ }
22
+ return false
23
+ }
24
+ }
25
+
26
+ async function extractTarball (tarballPath, dir) {
27
+ const child = execa('tar', ['-xf', tarballPath, '-C', dir])
28
+ child.stdout.pipe(process.stdout)
29
+ child.stderr.pipe(process.stderr)
30
+
31
+ await child
32
+ }
33
+
34
+ export { fileExists, dirExists, extractTarball }