| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,282 @@ | ||
| 'use strict' | ||
|
|
||
| const { join } = require('node:path') | ||
| const { test } = require('tap') | ||
| const { default: OpenAPISchemaValidator } = require('openapi-schema-validator') | ||
| const { | ||
| createComposer, | ||
| createOpenApiService, | ||
| testEntityRoutes | ||
| } = require('../helper') | ||
|
|
||
| const openApiValidator = new OpenAPISchemaValidator({ version: 3 }) | ||
|
|
||
| test('should compose openapi with prefixes', async (t) => { | ||
| const api1 = await createOpenApiService(t, ['users']) | ||
| const api2 = await createOpenApiService(t, ['posts']) | ||
|
|
||
| await api1.listen({ port: 0 }) | ||
| await api2.listen({ port: 0 }) | ||
|
|
||
| const composer = await createComposer(t, { | ||
| composer: { | ||
| services: [ | ||
| { | ||
| id: 'api1', | ||
| origin: 'http://127.0.0.1:' + api1.server.address().port, | ||
| openapi: { | ||
| url: '/documentation/json', | ||
| prefix: '/api1' | ||
| } | ||
| }, | ||
| { | ||
| id: 'api2', | ||
| origin: 'http://127.0.0.1:' + api2.server.address().port, | ||
| openapi: { | ||
| url: '/documentation/json', | ||
| prefix: '/api2' | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| }) | ||
|
|
||
| const { statusCode, body } = await composer.inject({ | ||
| method: 'GET', | ||
| url: '/documentation/json' | ||
| }) | ||
| t.equal(statusCode, 200) | ||
|
|
||
| const openApiSchema = JSON.parse(body) | ||
| openApiValidator.validate(openApiSchema) | ||
|
|
||
| await testEntityRoutes(t, composer, ['/api1/users', '/api2/posts']) | ||
| }) | ||
|
|
||
| test('should compose openapi without prefixes', async (t) => { | ||
| const api1 = await createOpenApiService(t, ['users']) | ||
| const api2 = await createOpenApiService(t, ['posts']) | ||
|
|
||
| await api1.listen({ port: 0 }) | ||
| await api2.listen({ port: 0 }) | ||
|
|
||
| const composer = await createComposer(t, { | ||
| composer: { | ||
| services: [ | ||
| { | ||
| id: 'api1', | ||
| origin: 'http://127.0.0.1:' + api1.server.address().port, | ||
| openapi: { | ||
| url: '/documentation/json' | ||
| } | ||
| }, | ||
| { | ||
| id: 'api2', | ||
| origin: 'http://127.0.0.1:' + api2.server.address().port, | ||
| openapi: { | ||
| url: '/documentation/json' | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| }) | ||
|
|
||
| const { statusCode, body } = await composer.inject({ | ||
| method: 'GET', | ||
| url: '/documentation/json' | ||
| }) | ||
| t.equal(statusCode, 200) | ||
|
|
||
| const openApiSchema = JSON.parse(body) | ||
| openApiValidator.validate(openApiSchema) | ||
|
|
||
| await testEntityRoutes(t, composer, ['/users', '/posts']) | ||
| }) | ||
|
|
||
| test('should read schemas from disk and compose openapi', async (t) => { | ||
| const api1 = await createOpenApiService(t, ['users']) | ||
| const api2 = await createOpenApiService(t, ['posts']) | ||
|
|
||
| await api1.listen({ port: 0 }) | ||
| await api2.listen({ port: 0 }) | ||
|
|
||
| const composer = await createComposer(t, { | ||
| composer: { | ||
| services: [ | ||
| { | ||
| id: 'api1', | ||
| origin: 'http://127.0.0.1:' + api1.server.address().port, | ||
| openapi: { | ||
| url: '/documentation/json' | ||
| } | ||
| }, | ||
| { | ||
| id: 'api2', | ||
| origin: 'http://127.0.0.1:' + api2.server.address().port, | ||
| openapi: { | ||
| url: '/documentation/json' | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| }) | ||
|
|
||
| const { statusCode, body } = await composer.inject({ | ||
| method: 'GET', | ||
| url: '/documentation/json' | ||
| }) | ||
| t.equal(statusCode, 200) | ||
|
|
||
| const openApiSchema = JSON.parse(body) | ||
| openApiValidator.validate(openApiSchema) | ||
|
|
||
| await testEntityRoutes(t, composer, ['/users', '/posts']) | ||
| }) | ||
|
|
||
| test('should not proxy request if it is not in a schema file', async (t) => { | ||
| const api1 = await createOpenApiService(t, ['users']) | ||
| const api2 = await createOpenApiService(t, ['posts']) | ||
|
|
||
| api1.get('/not-in-the-schema', async () => { | ||
| t.fail('should not proxy request') | ||
| }) | ||
|
|
||
| await api1.listen({ port: 0 }) | ||
| await api2.listen({ port: 0 }) | ||
|
|
||
| const composer = await createComposer(t, { | ||
| composer: { | ||
| services: [ | ||
| { | ||
| id: 'api1', | ||
| origin: 'http://127.0.0.1:' + api1.server.address().port, | ||
| openapi: { | ||
| file: join(__dirname, 'fixtures', 'schemas', 'users.json') | ||
| } | ||
| }, | ||
| { | ||
| id: 'api2', | ||
| origin: 'http://127.0.0.1:' + api2.server.address().port, | ||
| openapi: { | ||
| file: join(__dirname, 'fixtures', 'schemas', 'posts.json') | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| }) | ||
|
|
||
| const { statusCode, body } = await composer.inject({ | ||
| method: 'GET', | ||
| url: '/documentation/json' | ||
| }) | ||
| t.equal(statusCode, 200) | ||
|
|
||
| const openApiSchema = JSON.parse(body) | ||
| openApiValidator.validate(openApiSchema) | ||
|
|
||
| t.ok( | ||
| !openApiSchema.paths['/not-in-the-schema'], | ||
| 'should not have the path in the schema' | ||
| ) | ||
|
|
||
| await testEntityRoutes(t, composer, ['/users', '/posts']) | ||
|
|
||
| { | ||
| const { statusCode } = await composer.inject({ | ||
| method: 'GET', | ||
| url: '/not-in-the-schema' | ||
| }) | ||
| t.equal(statusCode, 404) | ||
| } | ||
| }) | ||
|
|
||
| test('should not compose api if there is no openapi config', async (t) => { | ||
| const api1 = await createOpenApiService(t, ['users']) | ||
| const api2 = await createOpenApiService(t, ['posts']) | ||
|
|
||
| await api1.listen({ port: 0 }) | ||
| await api2.listen({ port: 0 }) | ||
|
|
||
| const composer = await createComposer(t, { | ||
| composer: { | ||
| services: [ | ||
| { | ||
| id: 'api1', | ||
| origin: 'http://127.0.0.1:' + api1.server.address().port, | ||
| openapi: { | ||
| url: '/documentation/json', | ||
| prefix: '/api1' | ||
| } | ||
| }, | ||
| { | ||
| id: 'api2', | ||
| origin: 'http://127.0.0.1:' + api2.server.address().port | ||
| } | ||
| ] | ||
| } | ||
| }) | ||
|
|
||
| const { statusCode, body } = await composer.inject({ | ||
| method: 'GET', | ||
| url: '/documentation/json' | ||
| }) | ||
| t.equal(statusCode, 200) | ||
|
|
||
| const openApiSchema = JSON.parse(body) | ||
| openApiValidator.validate(openApiSchema) | ||
|
|
||
| await testEntityRoutes(t, composer, ['/api1/users']) | ||
|
|
||
| { | ||
| const { statusCode } = await composer.inject({ | ||
| method: 'GET', | ||
| url: '/api2/posts' | ||
| }) | ||
| t.equal(statusCode, 404) | ||
| } | ||
| }) | ||
|
|
||
| test('should allow custom title', async (t) => { | ||
| const api1 = await createOpenApiService(t, ['users']) | ||
| const api2 = await createOpenApiService(t, ['posts']) | ||
|
|
||
| await api1.listen({ port: 0 }) | ||
| await api2.listen({ port: 0 }) | ||
|
|
||
| const composer = await createComposer(t, { | ||
| composer: { | ||
| services: [ | ||
| { | ||
| id: 'api1', | ||
| origin: 'http://127.0.0.1:' + api1.server.address().port, | ||
| openapi: { | ||
| url: '/documentation/json', | ||
| prefix: '/api1' | ||
| } | ||
| }, | ||
| { | ||
| id: 'api2', | ||
| origin: 'http://127.0.0.1:' + api2.server.address().port, | ||
| openapi: { | ||
| url: '/documentation/json', | ||
| prefix: '/api2' | ||
| } | ||
| } | ||
| ], | ||
| openapi: { | ||
| title: 'My API', | ||
| version: '1.0.42' | ||
| } | ||
| } | ||
| }) | ||
|
|
||
| const { statusCode, body } = await composer.inject({ | ||
| method: 'GET', | ||
| url: '/documentation/json' | ||
| }) | ||
| t.equal(statusCode, 200) | ||
|
|
||
| const openApiSchema = JSON.parse(body) | ||
| t.equal(openApiSchema.info.title, 'My API') | ||
| t.equal(openApiSchema.info.version, '1.0.42') | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,240 @@ | ||
| 'use strict' | ||
|
|
||
| const { setTimeout } = require('node:timers/promises') | ||
| const { join } = require('node:path') | ||
| const { test } = require('tap') | ||
| const { default: OpenAPISchemaValidator } = require('openapi-schema-validator') | ||
| const { | ||
| createComposer, | ||
| createOpenApiService, | ||
| testEntityRoutes | ||
| } = require('../helper') | ||
|
|
||
| const openApiValidator = new OpenAPISchemaValidator({ version: 3 }) | ||
|
|
||
| test('should compose openapi with prefixes', async (t) => { | ||
| const api1 = await createOpenApiService(t, ['users']) | ||
| const api2 = await createOpenApiService(t, ['posts']) | ||
|
|
||
| await api1.listen({ port: 0 }) | ||
| await api2.listen({ port: 0 }) | ||
|
|
||
| const composer = await createComposer(t, { | ||
| composer: { | ||
| services: [ | ||
| { | ||
| id: 'api1', | ||
| origin: 'http://127.0.0.1:' + api1.server.address().port, | ||
| openapi: { | ||
| url: '/documentation/json', | ||
| prefix: '/api1' | ||
| } | ||
| }, | ||
| { | ||
| id: 'api2', | ||
| origin: 'http://127.0.0.1:' + api2.server.address().port, | ||
| openapi: { | ||
| url: '/documentation/json', | ||
| prefix: '/api2' | ||
| } | ||
| } | ||
| ], | ||
| refreshTimeout: 500 | ||
| } | ||
| }) | ||
|
|
||
| { | ||
| const { statusCode, body } = await composer.inject({ | ||
| method: 'GET', | ||
| url: '/documentation/json' | ||
| }) | ||
| t.equal(statusCode, 200) | ||
|
|
||
| const openApiSchema = JSON.parse(body) | ||
| openApiValidator.validate(openApiSchema) | ||
|
|
||
| await testEntityRoutes(t, composer, ['/api1/users', '/api2/posts']) | ||
| } | ||
|
|
||
| await api1.close() | ||
| await setTimeout(1000) | ||
|
|
||
| t.equal(composer.restarted, true) | ||
|
|
||
| { | ||
| const { statusCode, body } = await composer.inject({ | ||
| method: 'GET', | ||
| url: '/documentation/json' | ||
| }) | ||
| t.equal(statusCode, 200) | ||
|
|
||
| const openApiSchema = JSON.parse(body) | ||
| openApiValidator.validate(openApiSchema) | ||
|
|
||
| await testEntityRoutes(t, composer, ['/api2/posts']) | ||
|
|
||
| const { statusCode: statusCode2 } = await composer.inject({ | ||
| method: 'GET', | ||
| url: '/api1/users' | ||
| }) | ||
| t.equal(statusCode2, 404) | ||
| } | ||
| }) | ||
|
|
||
| test('should watch api only if it has a url', async (t) => { | ||
| const api1 = await createOpenApiService(t, ['users']) | ||
| const api2 = await createOpenApiService(t, ['posts']) | ||
|
|
||
| await api1.listen({ port: 0 }) | ||
| await api2.listen({ port: 0 }) | ||
|
|
||
| const composer = await createComposer(t, { | ||
| composer: { | ||
| services: [ | ||
| { | ||
| id: 'api1', | ||
| origin: 'http://127.0.0.1:' + api1.server.address().port, | ||
| openapi: { | ||
| url: '/documentation/json', | ||
| prefix: '/api1' | ||
| } | ||
| }, | ||
| { | ||
| id: 'api2', | ||
| origin: 'http://127.0.0.1:' + api2.server.address().port, | ||
| openapi: { | ||
| file: join(__dirname, 'fixtures', 'schemas', 'posts.json'), | ||
| prefix: '/api2' | ||
| } | ||
| } | ||
| ], | ||
| refreshTimeout: 500 | ||
| } | ||
| }) | ||
|
|
||
| { | ||
| const { statusCode, body } = await composer.inject({ | ||
| method: 'GET', | ||
| url: '/documentation/json' | ||
| }) | ||
| t.equal(statusCode, 200) | ||
|
|
||
| const openApiSchema = JSON.parse(body) | ||
| openApiValidator.validate(openApiSchema) | ||
|
|
||
| await testEntityRoutes(t, composer, ['/api1/users', '/api2/posts']) | ||
| } | ||
|
|
||
| await api2.close() | ||
| await setTimeout(1000) | ||
|
|
||
| t.equal(composer.restarted, false) | ||
|
|
||
| { | ||
| const { statusCode, body } = await composer.inject({ | ||
| method: 'GET', | ||
| url: '/documentation/json' | ||
| }) | ||
| t.equal(statusCode, 200) | ||
|
|
||
| const openApiSchema = JSON.parse(body) | ||
| openApiValidator.validate(openApiSchema) | ||
|
|
||
| await testEntityRoutes(t, composer, ['/api1/users']) | ||
|
|
||
| const { statusCode: statusCode2 } = await composer.inject({ | ||
| method: 'GET', | ||
| url: '/api2/posts' | ||
| }) | ||
| t.equal(statusCode2, 500) | ||
| } | ||
| }) | ||
|
|
||
| test('should compose schema after service restart', async (t) => { | ||
| const api1 = await createOpenApiService(t, ['users']) | ||
| const api2 = await createOpenApiService(t, ['posts']) | ||
|
|
||
| await api1.listen({ port: 0 }) | ||
| await api2.listen({ port: 0 }) | ||
|
|
||
| const api1Port = api1.server.address().port | ||
| const api2Port = api2.server.address().port | ||
|
|
||
| const composer = await createComposer(t, { | ||
| composer: { | ||
| services: [ | ||
| { | ||
| id: 'api1', | ||
| origin: 'http://127.0.0.1:' + api1Port, | ||
| openapi: { | ||
| url: '/documentation/json', | ||
| prefix: '/api1' | ||
| } | ||
| }, | ||
| { | ||
| id: 'api2', | ||
| origin: 'http://127.0.0.1:' + api2Port, | ||
| openapi: { | ||
| url: '/documentation/json', | ||
| prefix: '/api2' | ||
| } | ||
| } | ||
| ], | ||
| refreshTimeout: 500 | ||
| } | ||
| }) | ||
|
|
||
| { | ||
| const { statusCode, body } = await composer.inject({ | ||
| method: 'GET', | ||
| url: '/documentation/json' | ||
| }) | ||
| t.equal(statusCode, 200) | ||
|
|
||
| const openApiSchema = JSON.parse(body) | ||
| openApiValidator.validate(openApiSchema) | ||
|
|
||
| await testEntityRoutes(t, composer, ['/api1/users', '/api2/posts']) | ||
| } | ||
|
|
||
| await api1.close() | ||
| await setTimeout(1000) | ||
|
|
||
| t.equal(composer.restarted, true) | ||
|
|
||
| { | ||
| const { statusCode, body } = await composer.inject({ | ||
| method: 'GET', | ||
| url: '/documentation/json' | ||
| }) | ||
| t.equal(statusCode, 200) | ||
|
|
||
| const openApiSchema = JSON.parse(body) | ||
| openApiValidator.validate(openApiSchema) | ||
|
|
||
| await testEntityRoutes(t, composer, ['/api2/posts']) | ||
|
|
||
| const { statusCode: statusCode2 } = await composer.inject({ | ||
| method: 'GET', | ||
| url: '/api1/users' | ||
| }) | ||
| t.equal(statusCode2, 404) | ||
| } | ||
|
|
||
| const newApi1 = await createOpenApiService(t, ['users']) | ||
| await newApi1.listen({ port: api1Port }) | ||
| await setTimeout(1000) | ||
|
|
||
| { | ||
| const { statusCode, body } = await composer.inject({ | ||
| method: 'GET', | ||
| url: '/documentation/json' | ||
| }) | ||
| t.equal(statusCode, 200) | ||
|
|
||
| const openApiSchema = JSON.parse(body) | ||
| openApiValidator.validate(openApiSchema) | ||
|
|
||
| await testEntityRoutes(t, composer, ['/api1/users', '/api2/posts']) | ||
| } | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| 'use strict' | ||
|
|
||
| const { join } = require('path') | ||
| const { test } = require('tap') | ||
|
|
||
| const { createComposer } = require('./helper') | ||
|
|
||
| test('should resolve service ids to the origin', async (t) => { | ||
| const composer = await createComposer(t, | ||
| { | ||
| composer: { | ||
| services: [ | ||
| { | ||
| id: 'service1', | ||
| openapi: { | ||
| file: join(__dirname, 'openapi', 'fixtures', 'schemas', 'users.json') | ||
| } | ||
| }, | ||
| { | ||
| id: 'service2', | ||
| openapi: { | ||
| file: join(__dirname, 'openapi', 'fixtures', 'schemas', 'posts.json') | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| ) | ||
|
|
||
| const services = composer.platformatic.config.composer.services | ||
| t.equal(services.length, 2) | ||
|
|
||
| t.equal(services[0].id, 'service1') | ||
| t.equal(services[0].origin, 'http://service1.plt.local') | ||
|
|
||
| t.equal(services[1].id, 'service2') | ||
| t.equal(services[1].origin, 'http://service2.plt.local') | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| # Platformatic Composer API | ||
|
|
||
| This is a generated [Platformatic Composer](https://oss.platformatic.dev/docs/reference/composer/introduction) application. | ||
|
|
||
| ## Requirements | ||
|
|
||
| Platformatic supports macOS, Linux and Windows ([WSL](https://docs.microsoft.com/windows/wsl/) recommended). | ||
| You'll need to have [Node.js](https://nodejs.org/) >= v18.8.0 | ||
|
|
||
| ## Setup | ||
|
|
||
| 1. Install dependencies: | ||
|
|
||
| ```bash | ||
| npm install | ||
| ``` | ||
|
|
||
| ## Usage | ||
|
|
||
| Run the API with: | ||
|
|
||
| ```bash | ||
| npm start | ||
| ``` | ||
|
|
||
| ### Explore | ||
| - âš¡ The Platformatic Composer server is running at http://localhost:3042/ | ||
| - 📔 View the REST API's Swagger documentation at http://localhost:3042/documentation/ | ||
|
|
||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| import { getVersion, getDependencyVersion, isFileAccessible } from '../utils.mjs' | ||
| import { createPackageJson } from '../create-package-json.mjs' | ||
| import { createGitignore } from '../create-gitignore.mjs' | ||
| import { getPkgManager } from '../get-pkg-manager.mjs' | ||
| import parseArgs from 'minimist' | ||
| import { join } from 'path' | ||
| import inquirer from 'inquirer' | ||
| import { readFile, writeFile, mkdir } from 'fs/promises' | ||
| import pino from 'pino' | ||
| import pretty from 'pino-pretty' | ||
| import { execa } from 'execa' | ||
| import ora from 'ora' | ||
| import createComposer from './create-composer.mjs' | ||
| import askDir from '../ask-dir.mjs' | ||
| import { askDynamicWorkspaceCreateGHAction, askStaticWorkspaceGHAction } from '../ghaction.mjs' | ||
| import { getRunPackageManagerInstall, getPort } from '../cli-options.mjs' | ||
|
|
||
| export const createReadme = async (logger, dir = '.') => { | ||
| const readmeFileName = join(dir, 'README.md') | ||
| let isReadmeExists = await isFileAccessible(readmeFileName) | ||
| if (isReadmeExists) { | ||
| logger.debug(`${readmeFileName} found, asking to overwrite it.`) | ||
| const { shouldReplace } = await inquirer.prompt([{ | ||
| type: 'list', | ||
| name: 'shouldReplace', | ||
| message: 'Do you want to overwrite the existing README.md?', | ||
| default: true, | ||
| choices: [{ name: 'yes', value: true }, { name: 'no', value: false }] | ||
| }]) | ||
| isReadmeExists = !shouldReplace | ||
| } | ||
|
|
||
| if (isReadmeExists) { | ||
| logger.debug(`${readmeFileName} found, skipping creation of README.md file.`) | ||
| return | ||
| } | ||
|
|
||
| const readmeFile = new URL('README.md', import.meta.url) | ||
| const readme = await readFile(readmeFile, 'utf-8') | ||
| await writeFile(readmeFileName, readme) | ||
| logger.debug(`${readmeFileName} successfully created.`) | ||
| } | ||
|
|
||
| const createPlatformaticComposer = async (_args, opts) => { | ||
| const logger = opts.logger || pino(pretty({ | ||
| translateTime: 'SYS:HH:MM:ss', | ||
| ignore: 'hostname,pid' | ||
| })) | ||
|
|
||
| const args = parseArgs(_args, { | ||
| default: { | ||
| hostname: '127.0.0.1' | ||
| }, | ||
| alias: { | ||
| h: 'hostname', | ||
| p: 'port' | ||
| } | ||
| }) | ||
|
|
||
| const version = await getVersion() | ||
| const pkgManager = getPkgManager() | ||
|
|
||
| const projectDir = opts.dir || await askDir(logger, '.') | ||
|
|
||
| const toAsk = [getPort(args.port)] | ||
|
|
||
| if (!opts.skipPackageJson) { | ||
| toAsk.push(getRunPackageManagerInstall(pkgManager)) | ||
| } | ||
|
|
||
| const { runPackageManagerInstall, port } = await inquirer.prompt(toAsk) | ||
|
|
||
| // Create the project directory | ||
| await mkdir(projectDir, { recursive: true }) | ||
|
|
||
| const params = { | ||
| hostname: args.hostname, | ||
| port | ||
| } | ||
|
|
||
| const env = await createComposer(params, logger, projectDir, version) | ||
|
|
||
| const fastifyVersion = await getDependencyVersion('fastify') | ||
|
|
||
| // Create the package.json, notes that we don't have the option for TS (yet) so we don't generate | ||
| // the package.json with the TS build | ||
| if (!opts.skipPackageJson) { | ||
| await createPackageJson('composer', version, fastifyVersion, logger, projectDir, false) | ||
| } | ||
| if (!opts.skipGitignore) { | ||
| await createGitignore(logger, projectDir) | ||
| } | ||
| await createReadme(logger, projectDir) | ||
|
|
||
| if (runPackageManagerInstall) { | ||
| const spinner = ora('Installing dependencies...').start() | ||
| await execa(pkgManager, ['install'], { cwd: projectDir }) | ||
| spinner.succeed('...done!') | ||
| } | ||
|
|
||
| if (!opts.skipGitHubActions) { | ||
| await askDynamicWorkspaceCreateGHAction(logger, env, 'composer', false, projectDir) | ||
| await askStaticWorkspaceGHAction(logger, env, 'composer', false, projectDir) | ||
| } | ||
| } | ||
|
|
||
| export default createPlatformaticComposer |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| import { readFile, writeFile, appendFile } from 'fs/promises' | ||
| import { findComposerConfigFile, isFileAccessible } from '../utils.mjs' | ||
| import { join } from 'path' | ||
| import * as desm from 'desm' | ||
|
|
||
| function generateConfig (version) { | ||
| const config = { | ||
| $schema: `https://platformatic.dev/schemas/v${version}/composer`, | ||
| server: { | ||
| hostname: '{PLT_SERVER_HOSTNAME}', | ||
| port: '{PORT}', | ||
| logger: { | ||
| level: '{PLT_SERVER_LOGGER_LEVEL}' | ||
| } | ||
| }, | ||
| composer: { | ||
| services: [{ | ||
| id: 'example', | ||
| origin: '{PLT_EXAMPLE_ORIGIN}', | ||
| openapi: { | ||
| url: '/documentation/json' | ||
| } | ||
| }], | ||
| refreshTimeout: 1000 | ||
| }, | ||
| watch: true | ||
| } | ||
|
|
||
| return config | ||
| } | ||
|
|
||
| function generateEnv (hostname, port) { | ||
| const env = `\ | ||
| PLT_SERVER_HOSTNAME=${hostname} | ||
| PORT=${port} | ||
| PLT_SERVER_LOGGER_LEVEL=info | ||
| PLT_EXAMPLE_ORIGIN= | ||
| ` | ||
|
|
||
| return env | ||
| } | ||
|
|
||
| async function createComposer ({ hostname, port }, logger, currentDir = process.cwd(), version) { | ||
| if (!version) { | ||
| const pkg = await readFile(desm.join(import.meta.url, '..', '..', 'package.json')) | ||
| version = JSON.parse(pkg).version | ||
| } | ||
| const accessibleConfigFilename = await findComposerConfigFile(currentDir) | ||
|
|
||
| if (accessibleConfigFilename === undefined) { | ||
| const config = generateConfig(version) | ||
| await writeFile(join(currentDir, 'platformatic.composer.json'), JSON.stringify(config, null, 2)) | ||
| logger.info('Configuration file platformatic.composer.json successfully created.') | ||
|
|
||
| const env = generateEnv(hostname, port) | ||
| const envFileExists = await isFileAccessible('.env', currentDir) | ||
| await appendFile(join(currentDir, '.env'), env) | ||
| await writeFile(join(currentDir, '.env.sample'), env) | ||
| /* c8 ignore next 5 */ | ||
| if (envFileExists) { | ||
| logger.info('Environment file .env found, appending new environment variables to existing .env file.') | ||
| } else { | ||
| logger.info('Environment file .env successfully created.') | ||
| } | ||
| } else { | ||
| logger.info(`Configuration file ${accessibleConfigFilename} found, skipping creation of configuration file.`) | ||
| } | ||
|
|
||
| return { | ||
| PLT_SERVER_LOGGER_LEVEL: 'info', | ||
| PORT: port, | ||
| PLT_SERVER_HOSTNAME: hostname | ||
| } | ||
| } | ||
|
|
||
| export default createComposer |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # Platformatic Runtime API | ||
|
|
||
| This is a generated [Platformatic Runtime](https://oss.platformatic.dev/docs/reference/runtime/introduction) application. | ||
|
|
||
| ## Requirements | ||
|
|
||
| Platformatic supports macOS, Linux and Windows ([WSL](https://docs.microsoft.com/windows/wsl/) recommended). | ||
| You'll need to have [Node.js](https://nodejs.org/) >= v18.8.0 | ||
|
|
||
| ## Setup | ||
|
|
||
| 1. Install dependencies: | ||
|
|
||
| ```bash | ||
| npm install | ||
| ``` | ||
|
|
||
| ## Usage | ||
|
|
||
| Run the API with: | ||
|
|
||
| ```bash | ||
| npm start | ||
| ``` | ||
|
|
||
| ## Adding a Service | ||
|
|
||
| Adding a new service to this project is as simple as running `create-platformatic` again, like so: | ||
|
|
||
| ``` | ||
| npx create-platformatic | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,155 @@ | ||
| import { getVersion, getDependencyVersion, isFileAccessible } from '../utils.mjs' | ||
| import { createPackageJson } from '../create-package-json.mjs' | ||
| import { createGitignore } from '../create-gitignore.mjs' | ||
| import { getPkgManager } from '../get-pkg-manager.mjs' | ||
| import { join } from 'path' | ||
| import inquirer from 'inquirer' | ||
| import { readFile, writeFile, mkdir } from 'fs/promises' | ||
| import pino from 'pino' | ||
| import pretty from 'pino-pretty' | ||
| import { execa } from 'execa' | ||
| import ora from 'ora' | ||
| import createRuntime from './create-runtime.mjs' | ||
| import askDir from '../ask-dir.mjs' | ||
| import { askDynamicWorkspaceCreateGHAction, askStaticWorkspaceGHAction } from '../ghaction.mjs' | ||
| import { getRunPackageManagerInstall } from '../cli-options.mjs' | ||
| import generateName from 'boring-name-generator' | ||
| import { chooseKind } from '../index.mjs' | ||
|
|
||
| export const createReadme = async (logger, dir = '.') => { | ||
| const readmeFileName = join(dir, 'README.md') | ||
| let isReadmeExists = await isFileAccessible(readmeFileName) | ||
| if (isReadmeExists) { | ||
| logger.debug(`${readmeFileName} found, asking to overwrite it.`) | ||
| const { shouldReplace } = await inquirer.prompt([{ | ||
| type: 'list', | ||
| name: 'shouldReplace', | ||
| message: 'Do you want to overwrite the existing README.md?', | ||
| default: true, | ||
| choices: [{ name: 'yes', value: true }, { name: 'no', value: false }] | ||
| }]) | ||
| isReadmeExists = !shouldReplace | ||
| } | ||
|
|
||
| if (isReadmeExists) { | ||
| logger.debug(`${readmeFileName} found, skipping creation of README.md file.`) | ||
| return | ||
| } | ||
|
|
||
| const readmeFile = new URL('README.md', import.meta.url) | ||
| const readme = await readFile(readmeFile, 'utf-8') | ||
| await writeFile(readmeFileName, readme) | ||
| logger.debug(`${readmeFileName} successfully created.`) | ||
| } | ||
|
|
||
| export async function createPlatformaticRuntime (_args) { | ||
| const logger = pino(pretty({ | ||
| translateTime: 'SYS:HH:MM:ss', | ||
| ignore: 'hostname,pid' | ||
| })) | ||
|
|
||
| const version = await getVersion() | ||
| const pkgManager = getPkgManager() | ||
|
|
||
| const projectDir = await askDir(logger, '.') | ||
| const servicesDir = await askDir(logger, 'services', 'Where would you like to load your services from?') | ||
|
|
||
| const { runPackageManagerInstall } = await inquirer.prompt([ | ||
| getRunPackageManagerInstall(pkgManager) | ||
| ]) | ||
|
|
||
| // Create the project directory | ||
| await mkdir(projectDir, { recursive: true }) | ||
| await mkdir(servicesDir, { recursive: true }) | ||
|
|
||
| const fastifyVersion = await getDependencyVersion('fastify') | ||
|
|
||
| // Create the package.json, notes that we don't have the option for TS (yet) so we don't generate | ||
| // the package.json with the TS build | ||
| await createPackageJson('runtime', version, fastifyVersion, logger, projectDir, false) | ||
| await createGitignore(logger, projectDir) | ||
| await createReadme(logger, projectDir) | ||
|
|
||
| if (runPackageManagerInstall) { | ||
| const spinner = ora('Installing dependencies...').start() | ||
| await execa(pkgManager, ['install'], { cwd: projectDir }) | ||
| spinner.succeed('...done!') | ||
| } | ||
|
|
||
| logger.info('Let\'s create a first service!') | ||
|
|
||
| const names = [] | ||
| while (true) { | ||
| if (!await createRuntimeService({ servicesDir, names, logger })) { | ||
| continue | ||
| } | ||
|
|
||
| const { shouldBreak } = await inquirer.prompt([ | ||
| { | ||
| type: 'list', | ||
| name: 'shouldBreak', | ||
| message: 'Do you want to create another service?', | ||
| default: false, | ||
| choices: [{ name: 'yes', value: false }, { name: 'no', value: true }] | ||
| } | ||
| ]) | ||
|
|
||
| if (shouldBreak) { | ||
| break | ||
| } | ||
| } | ||
|
|
||
| let entrypoint = '' | ||
|
|
||
| if (names.length > 1) { | ||
| const results = await inquirer.prompt([ | ||
| { | ||
| type: 'list', | ||
| name: 'entrypoint', | ||
| message: 'Which service should be exposed?', | ||
| choices: names.map(name => ({ name, value: name })) | ||
| } | ||
| ]) | ||
| entrypoint = results.entrypoint | ||
| } else { | ||
| entrypoint = names[0] | ||
| } | ||
|
|
||
| const env = await createRuntime(logger, projectDir, version, servicesDir, entrypoint) | ||
|
|
||
| await askDynamicWorkspaceCreateGHAction(logger, env, 'service', false, projectDir) | ||
| await askStaticWorkspaceGHAction(logger, env, 'service', false, projectDir) | ||
| } | ||
|
|
||
| export async function createRuntimeService ({ servicesDir, names, logger }) { | ||
| logger ||= pino(pretty({ | ||
| translateTime: 'SYS:HH:MM:ss', | ||
| ignore: 'hostname,pid' | ||
| })) | ||
| const { name } = await inquirer.prompt({ | ||
| type: 'input', | ||
| name: 'name', | ||
| message: 'What is the name of the service?', | ||
| default: generateName().dashed | ||
| }) | ||
|
|
||
| if (names.includes(name)) { | ||
| logger.warn('This name is already used, please choose another one.') | ||
| return false | ||
| } | ||
| names.push(name) | ||
|
|
||
| const serviceDir = join(servicesDir, name) | ||
|
|
||
| await chooseKind([], { | ||
| skip: 'runtime', | ||
| dir: serviceDir, | ||
| logger, | ||
| skipGitHubActions: true, | ||
| skipPackageJson: true, | ||
| skipGitignore: true, | ||
| port: '0' | ||
| }) | ||
|
|
||
| return true | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import { readFile, writeFile } from 'fs/promises' | ||
| import { findRuntimeConfigFile } from '../utils.mjs' | ||
| import { join, relative, isAbsolute } from 'path' | ||
| import * as desm from 'desm' | ||
|
|
||
| function generateConfig (version, path, entrypoint) { | ||
| const config = { | ||
| $schema: `https://platformatic.dev/schemas/v${version}/runtime`, | ||
| entrypoint, | ||
| allowCycles: false, | ||
| hotReload: true, | ||
| autoload: { | ||
| path, | ||
| exclude: ['docs'] | ||
| } | ||
| } | ||
|
|
||
| return config | ||
| } | ||
|
|
||
| async function createRuntime (logger, currentDir = process.cwd(), version, servicesDir, entrypoint) { | ||
| if (!version) { | ||
| const pkg = await readFile(desm.join(import.meta.url, '..', '..', 'package.json')) | ||
| version = JSON.parse(pkg).version | ||
| } | ||
| const accessibleConfigFilename = await findRuntimeConfigFile(currentDir) | ||
|
|
||
| if (accessibleConfigFilename === undefined) { | ||
| const path = isAbsolute(servicesDir) ? relative(currentDir, servicesDir) : servicesDir | ||
| const config = generateConfig(version, path, entrypoint) | ||
| await writeFile(join(currentDir, 'platformatic.runtime.json'), JSON.stringify(config, null, 2)) | ||
| logger.info('Configuration file platformatic.runtime.json successfully created.') | ||
| } else { | ||
| logger.info(`Configuration file ${accessibleConfigFilename} found, skipping creation of configuration file.`) | ||
| } | ||
|
|
||
| return {} | ||
| } | ||
|
|
||
| export default createRuntime |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| import createComposer from '../../src/composer/create-composer.mjs' | ||
| import { test, beforeEach, afterEach } from 'tap' | ||
| import { tmpdir } from 'os' | ||
| import { mkdtempSync, rmSync, readFileSync, writeFileSync } from 'fs' | ||
| import { join } from 'path' | ||
| import dotenv from 'dotenv' | ||
|
|
||
| const base = tmpdir() | ||
| let tmpDir | ||
| let log = [] | ||
| beforeEach(() => { | ||
| tmpDir = mkdtempSync(join(base, 'test-create-platformatic-')) | ||
| }) | ||
|
|
||
| afterEach(() => { | ||
| log = [] | ||
| rmSync(tmpDir, { recursive: true, force: true }) | ||
| process.env = {} | ||
| }) | ||
|
|
||
| const fakeLogger = { | ||
| debug: msg => log.push(msg), | ||
| info: msg => log.push(msg) | ||
| } | ||
|
|
||
| test('creates composer', async ({ equal, same, ok }) => { | ||
| const params = { | ||
| hostname: 'myhost', | ||
| port: 6666, | ||
| typescript: false | ||
| } | ||
|
|
||
| await createComposer(params, fakeLogger, tmpDir) | ||
|
|
||
| const pathToComposerConfigFile = join(tmpDir, 'platformatic.composer.json') | ||
| const composerConfigFile = readFileSync(pathToComposerConfigFile, 'utf8') | ||
| const composerConfig = JSON.parse(composerConfigFile) | ||
| const { server, composer } = composerConfig | ||
|
|
||
| equal(server.hostname, '{PLT_SERVER_HOSTNAME}') | ||
| equal(server.port, '{PORT}') | ||
|
|
||
| const pathToDbEnvFile = join(tmpDir, '.env') | ||
| dotenv.config({ path: pathToDbEnvFile }) | ||
| equal(process.env.PLT_SERVER_HOSTNAME, 'myhost') | ||
| equal(process.env.PORT, '6666') | ||
| process.env = {} | ||
|
|
||
| const pathToDbEnvSampleFile = join(tmpDir, '.env.sample') | ||
| dotenv.config({ path: pathToDbEnvSampleFile }) | ||
| equal(process.env.PLT_SERVER_HOSTNAME, 'myhost') | ||
| equal(process.env.PORT, '6666') | ||
|
|
||
| same(composer, { | ||
| services: [{ | ||
| id: 'example', | ||
| origin: '{PLT_EXAMPLE_ORIGIN}', | ||
| openapi: { | ||
| url: '/documentation/json' | ||
| } | ||
| }], | ||
| refreshTimeout: 1000 | ||
| }) | ||
| }) | ||
|
|
||
| test('creates project with configuration already present', async ({ ok }) => { | ||
| const pathToComposerConfigFileOld = join(tmpDir, 'platformatic.composer.json') | ||
| writeFileSync(pathToComposerConfigFileOld, JSON.stringify({ test: 'test' })) | ||
| const params = { | ||
| hostname: 'myhost', | ||
| port: 6666 | ||
| } | ||
| await createComposer(params, fakeLogger, tmpDir) | ||
| ok(log.includes('Configuration file platformatic.composer.json found, skipping creation of configuration file.')) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| import createRuntime from '../../src/runtime/create-runtime.mjs' | ||
| import { test, beforeEach, afterEach } from 'tap' | ||
| import { tmpdir } from 'os' | ||
| import { mkdtempSync, rmSync, readFileSync, writeFileSync } from 'fs' | ||
| import { join } from 'path' | ||
|
|
||
| const base = tmpdir() | ||
| let tmpDir | ||
| let log = [] | ||
| beforeEach(() => { | ||
| tmpDir = mkdtempSync(join(base, 'test-create-platformatic-')) | ||
| }) | ||
|
|
||
| afterEach(() => { | ||
| log = [] | ||
| rmSync(tmpDir, { recursive: true, force: true }) | ||
| process.env = {} | ||
| }) | ||
|
|
||
| const fakeLogger = { | ||
| debug: msg => log.push(msg), | ||
| info: msg => log.push(msg) | ||
| } | ||
|
|
||
| test('creates runtime', async ({ equal, same, ok }) => { | ||
| await createRuntime(fakeLogger, tmpDir, undefined, 'services', 'foobar') | ||
|
|
||
| const pathToRuntimeConfigFile = join(tmpDir, 'platformatic.runtime.json') | ||
| const runtimeConfigFile = readFileSync(pathToRuntimeConfigFile, 'utf8') | ||
| const runtimeConfig = JSON.parse(runtimeConfigFile) | ||
|
|
||
| delete runtimeConfig.$schema | ||
|
|
||
| same(runtimeConfig, { | ||
| entrypoint: 'foobar', | ||
| allowCycles: false, | ||
| hotReload: true, | ||
| autoload: { | ||
| path: 'services', | ||
| exclude: ['docs'] | ||
| } | ||
| }) | ||
| }) | ||
|
|
||
| test('with a full path for autoload', async ({ equal, same, ok }) => { | ||
| await createRuntime(fakeLogger, tmpDir, undefined, join(tmpDir, 'services'), 'foobar') | ||
|
|
||
| const pathToRuntimeConfigFile = join(tmpDir, 'platformatic.runtime.json') | ||
| const runtimeConfigFile = readFileSync(pathToRuntimeConfigFile, 'utf8') | ||
| const runtimeConfig = JSON.parse(runtimeConfigFile) | ||
|
|
||
| delete runtimeConfig.$schema | ||
|
|
||
| same(runtimeConfig, { | ||
| entrypoint: 'foobar', | ||
| allowCycles: false, | ||
| hotReload: true, | ||
| autoload: { | ||
| path: 'services', | ||
| exclude: ['docs'] | ||
| } | ||
| }) | ||
| }) | ||
|
|
||
| test('creates project with configuration already present', async ({ ok }) => { | ||
| const pathToRuntimeConfigFileOld = join(tmpDir, 'platformatic.runtime.json') | ||
| writeFileSync(pathToRuntimeConfigFileOld, JSON.stringify({ test: 'test' })) | ||
| await createRuntime(fakeLogger, tmpDir, 'foobar') | ||
| ok(log.includes('Configuration file platformatic.runtime.json found, skipping creation of configuration file.')) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| 'use strict' | ||
|
|
||
| const { join } = require('path') | ||
| const { test } = require('tap') | ||
|
|
||
| const { deploy } = require('../index') | ||
| const { startMachine, startDeployService } = require('./helper') | ||
|
|
||
| test('should deploy platformatic composer project without github metadata', async (t) => { | ||
| t.plan(12) | ||
|
|
||
| const bundleId = 'test-bundle-id' | ||
| const token = 'test-upload-token' | ||
|
|
||
| const workspaceId = 'test-workspace-id' | ||
| const workspaceKey = 'test-workspace-key' | ||
|
|
||
| const entryPointUrl = await startMachine(t, () => { | ||
| t.pass('Action should make a prewarm request to the machine') | ||
| }) | ||
|
|
||
| const pathToProject = join(__dirname, 'fixtures', 'composer-basic') | ||
| const pathToConfig = './platformatic.composer.json' | ||
| const pathToEnvFile = './.env' | ||
|
|
||
| const label = 'github-pr:1' | ||
|
|
||
| const variables = { | ||
| ENV_VARIABLE_1: 'value1', | ||
| ENV_VARIABLE_2: 'value2' | ||
| } | ||
|
|
||
| const secrets = { | ||
| SECRET_VARIABLE_1: 'value3' | ||
| } | ||
|
|
||
| const metadata = { | ||
| appType: 'composer' | ||
| } | ||
|
|
||
| await startDeployService( | ||
| t, | ||
| { | ||
| createBundleCallback: (request, reply) => { | ||
| t.equal(request.headers['x-platformatic-workspace-id'], workspaceId) | ||
| t.equal(request.headers['x-platformatic-api-key'], workspaceKey) | ||
|
|
||
| const { bundle } = request.body | ||
|
|
||
| t.equal(bundle.appType, 'composer') | ||
| t.equal(bundle.configPath, pathToConfig) | ||
| t.ok(bundle.checksum) | ||
|
|
||
| t.ok(request.body.bundle.checksum) | ||
|
|
||
| reply.code(200).send({ id: bundleId, token, isBundleUploaded: false }) | ||
| }, | ||
| createDeploymentCallback: (request, reply) => { | ||
| t.equal(request.headers['x-platformatic-workspace-id'], workspaceId) | ||
| t.equal(request.headers['x-platformatic-api-key'], workspaceKey) | ||
| t.equal(request.headers.authorization, `Bearer ${token}`) | ||
| t.same( | ||
| request.body, | ||
| { label, metadata, variables, secrets } | ||
| ) | ||
| reply.code(200).send({ entryPointUrl }) | ||
| }, | ||
| uploadCallback: (request) => { | ||
| t.equal(request.headers.authorization, `Bearer ${token}`) | ||
| } | ||
| } | ||
| ) | ||
|
|
||
| const logger = { | ||
| info: () => {}, | ||
| warn: () => t.fail('Should not log a warning') | ||
| } | ||
|
|
||
| await deploy({ | ||
| deployServiceHost: 'http://localhost:3042', | ||
| workspaceId, | ||
| workspaceKey, | ||
| label, | ||
| pathToProject, | ||
| pathToConfig, | ||
| pathToEnvFile, | ||
| secrets, | ||
| variables, | ||
| logger | ||
| }) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| 'use strict' | ||
|
|
||
| const { join } = require('path') | ||
| const { test } = require('tap') | ||
|
|
||
| const { deploy } = require('../index') | ||
| const { startMachine, startDeployService } = require('./helper') | ||
|
|
||
| test('should deploy platformatic runtime project without github metadata', async (t) => { | ||
| t.plan(12) | ||
|
|
||
| const bundleId = 'test-bundle-id' | ||
| const token = 'test-upload-token' | ||
|
|
||
| const workspaceId = 'test-workspace-id' | ||
| const workspaceKey = 'test-workspace-key' | ||
|
|
||
| const entryPointUrl = await startMachine(t, () => { | ||
| t.pass('Action should make a prewarm request to the machine') | ||
| }) | ||
|
|
||
| const pathToProject = join(__dirname, 'fixtures', 'runtime-basic') | ||
| const pathToConfig = './platformatic.runtime.json' | ||
| const pathToEnvFile = './.env' | ||
|
|
||
| const label = 'github-pr:1' | ||
|
|
||
| const variables = { | ||
| ENV_VARIABLE_1: 'value1', | ||
| ENV_VARIABLE_2: 'value2' | ||
| } | ||
|
|
||
| const secrets = { | ||
| SECRET_VARIABLE_1: 'value3' | ||
| } | ||
|
|
||
| const metadata = { | ||
| appType: 'runtime', | ||
| services: [ | ||
| { | ||
| id: 'serviceApp', | ||
| entrypoint: true | ||
| }, | ||
| { | ||
| id: 'with-logger', | ||
| entrypoint: false | ||
| } | ||
| ] | ||
| } | ||
|
|
||
| await startDeployService( | ||
| t, | ||
| { | ||
| createBundleCallback: (request, reply) => { | ||
| t.equal(request.headers['x-platformatic-workspace-id'], workspaceId) | ||
| t.equal(request.headers['x-platformatic-api-key'], workspaceKey) | ||
|
|
||
| const { bundle } = request.body | ||
|
|
||
| t.equal(bundle.appType, 'runtime') | ||
| t.equal(bundle.configPath, pathToConfig) | ||
| t.ok(bundle.checksum) | ||
|
|
||
| t.ok(request.body.bundle.checksum) | ||
|
|
||
| reply.code(200).send({ id: bundleId, token, isBundleUploaded: false }) | ||
| }, | ||
| createDeploymentCallback: (request, reply) => { | ||
| t.equal(request.headers['x-platformatic-workspace-id'], workspaceId) | ||
| t.equal(request.headers['x-platformatic-api-key'], workspaceKey) | ||
| t.equal(request.headers.authorization, `Bearer ${token}`) | ||
| t.same( | ||
| request.body, | ||
| { label, metadata, variables, secrets } | ||
| ) | ||
| reply.code(200).send({ entryPointUrl }) | ||
| }, | ||
| uploadCallback: (request) => { | ||
| t.equal(request.headers.authorization, `Bearer ${token}`) | ||
| } | ||
| } | ||
| ) | ||
|
|
||
| const logger = { | ||
| info: () => {}, | ||
| warn: () => t.fail('Should not log a warning') | ||
| } | ||
|
|
||
| await deploy({ | ||
| deployServiceHost: 'http://localhost:3042', | ||
| workspaceId, | ||
| workspaceKey, | ||
| label, | ||
| pathToProject, | ||
| pathToConfig, | ||
| pathToEnvFile, | ||
| secrets, | ||
| variables, | ||
| logger | ||
| }) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| { | ||
| "openapi": "3.0.3", | ||
| "info": { | ||
| "version": "8.3.1", | ||
| "title": "@fastify/swagger" | ||
| }, | ||
| "components": { | ||
| "schemas": {} | ||
| }, | ||
| "paths": { | ||
| "/users": { | ||
| "get": { | ||
| "responses": { | ||
| "200": { | ||
| "description": "Default Response" | ||
| } | ||
| } | ||
| }, | ||
| "post": { | ||
| "responses": { | ||
| "200": { | ||
| "description": "Default Response" | ||
| } | ||
| } | ||
| }, | ||
| "put": { | ||
| "responses": { | ||
| "200": { | ||
| "description": "Default Response" | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "/users/{id}": { | ||
| "get": { | ||
| "responses": { | ||
| "200": { | ||
| "description": "Default Response" | ||
| } | ||
| } | ||
| }, | ||
| "post": { | ||
| "responses": { | ||
| "200": { | ||
| "description": "Default Response" | ||
| } | ||
| } | ||
| }, | ||
| "put": { | ||
| "responses": { | ||
| "200": { | ||
| "description": "Default Response" | ||
| } | ||
| } | ||
| }, | ||
| "delete": { | ||
| "responses": { | ||
| "200": { | ||
| "description": "Default Response" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| { | ||
| "$schema": "https://platformatic.dev/schemas/v0.23.2/composer", | ||
| "server": { | ||
| "hostname": "127.0.0.1", | ||
| "port": 0, | ||
| "logger": { | ||
| "level": "info" | ||
| } | ||
| }, | ||
| "composer": { | ||
| "services": [ | ||
| { | ||
| "id": "api1", | ||
| "origin": "http://127.0.0.1", | ||
| "openapi": { | ||
| "file": "./api1.json" | ||
| } | ||
| } | ||
| ], | ||
| "refreshTimeout": 1000 | ||
| }, | ||
| "watch": false | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "$schema": "https://platformatic.dev/schemas/v0.20.0/service", | ||
| "server": { | ||
| "hostname": "127.0.0.1", | ||
| "port": 0 | ||
| }, | ||
| "service": { | ||
| "openapi": true | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "$schema": "https://platformatic.dev/schemas/v0.20.0/service", | ||
| "server": { | ||
| "hostname": "127.0.0.1", | ||
| "port": 0 | ||
| }, | ||
| "service": { | ||
| "openapi": true | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| { | ||
| "$schema": "https://platformatic.dev/schemas/v0.20.0/runtime", | ||
| "entrypoint": "serviceApp", | ||
| "autoload": { | ||
| "path": "./packages", | ||
| "exclude": ["docs"], | ||
| "mappings": { | ||
| "serviceAppWithLogger": { | ||
| "id": "with-logger", | ||
| "config": "platformatic.service.json" | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| .nyc_output | ||
| coverage |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,201 @@ | ||
| Apache License | ||
| Version 2.0, January 2004 | ||
| http://www.apache.org/licenses/ | ||
|
|
||
| TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||
|
|
||
| 1. Definitions. | ||
|
|
||
| "License" shall mean the terms and conditions for use, reproduction, | ||
| and distribution as defined by Sections 1 through 9 of this document. | ||
|
|
||
| "Licensor" shall mean the copyright owner or entity authorized by | ||
| the copyright owner that is granting the License. | ||
|
|
||
| "Legal Entity" shall mean the union of the acting entity and all | ||
| other entities that control, are controlled by, or are under common | ||
| control with that entity. For the purposes of this definition, | ||
| "control" means (i) the power, direct or indirect, to cause the | ||
| direction or management of such entity, whether by contract or | ||
| otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||
| outstanding shares, or (iii) beneficial ownership of such entity. | ||
|
|
||
| "You" (or "Your") shall mean an individual or Legal Entity | ||
| exercising permissions granted by this License. | ||
|
|
||
| "Source" form shall mean the preferred form for making modifications, | ||
| including but not limited to software source code, documentation | ||
| source, and configuration files. | ||
|
|
||
| "Object" form shall mean any form resulting from mechanical | ||
| transformation or translation of a Source form, including but | ||
| not limited to compiled object code, generated documentation, | ||
| and conversions to other media types. | ||
|
|
||
| "Work" shall mean the work of authorship, whether in Source or | ||
| Object form, made available under the License, as indicated by a | ||
| copyright notice that is included in or attached to the work | ||
| (an example is provided in the Appendix below). | ||
|
|
||
| "Derivative Works" shall mean any work, whether in Source or Object | ||
| form, that is based on (or derived from) the Work and for which the | ||
| editorial revisions, annotations, elaborations, or other modifications | ||
| represent, as a whole, an original work of authorship. For the purposes | ||
| of this License, Derivative Works shall not include works that remain | ||
| separable from, or merely link (or bind by name) to the interfaces of, | ||
| the Work and Derivative Works thereof. | ||
|
|
||
| "Contribution" shall mean any work of authorship, including | ||
| the original version of the Work and any modifications or additions | ||
| to that Work or Derivative Works thereof, that is intentionally | ||
| submitted to Licensor for inclusion in the Work by the copyright owner | ||
| or by an individual or Legal Entity authorized to submit on behalf of | ||
| the copyright owner. For the purposes of this definition, "submitted" | ||
| means any form of electronic, verbal, or written communication sent | ||
| to the Licensor or its representatives, including but not limited to | ||
| communication on electronic mailing lists, source code control systems, | ||
| and issue tracking systems that are managed by, or on behalf of, the | ||
| Licensor for the purpose of discussing and improving the Work, but | ||
| excluding communication that is conspicuously marked or otherwise | ||
| designated in writing by the copyright owner as "Not a Contribution." | ||
|
|
||
| "Contributor" shall mean Licensor and any individual or Legal Entity | ||
| on behalf of whom a Contribution has been received by Licensor and | ||
| subsequently incorporated within the Work. | ||
|
|
||
| 2. Grant of Copyright License. Subject to the terms and conditions of | ||
| this License, each Contributor hereby grants to You a perpetual, | ||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||
| copyright license to reproduce, prepare Derivative Works of, | ||
| publicly display, publicly perform, sublicense, and distribute the | ||
| Work and such Derivative Works in Source or Object form. | ||
|
|
||
| 3. Grant of Patent License. Subject to the terms and conditions of | ||
| this License, each Contributor hereby grants to You a perpetual, | ||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||
| (except as stated in this section) patent license to make, have made, | ||
| use, offer to sell, sell, import, and otherwise transfer the Work, | ||
| where such license applies only to those patent claims licensable | ||
| by such Contributor that are necessarily infringed by their | ||
| Contribution(s) alone or by combination of their Contribution(s) | ||
| with the Work to which such Contribution(s) was submitted. If You | ||
| institute patent litigation against any entity (including a | ||
| cross-claim or counterclaim in a lawsuit) alleging that the Work | ||
| or a Contribution incorporated within the Work constitutes direct | ||
| or contributory patent infringement, then any patent licenses | ||
| granted to You under this License for that Work shall terminate | ||
| as of the date such litigation is filed. | ||
|
|
||
| 4. Redistribution. You may reproduce and distribute copies of the | ||
| Work or Derivative Works thereof in any medium, with or without | ||
| modifications, and in Source or Object form, provided that You | ||
| meet the following conditions: | ||
|
|
||
| (a) You must give any other recipients of the Work or | ||
| Derivative Works a copy of this License; and | ||
|
|
||
| (b) You must cause any modified files to carry prominent notices | ||
| stating that You changed the files; and | ||
|
|
||
| (c) You must retain, in the Source form of any Derivative Works | ||
| that You distribute, all copyright, patent, trademark, and | ||
| attribution notices from the Source form of the Work, | ||
| excluding those notices that do not pertain to any part of | ||
| the Derivative Works; and | ||
|
|
||
| (d) If the Work includes a "NOTICE" text file as part of its | ||
| distribution, then any Derivative Works that You distribute must | ||
| include a readable copy of the attribution notices contained | ||
| within such NOTICE file, excluding those notices that do not | ||
| pertain to any part of the Derivative Works, in at least one | ||
| of the following places: within a NOTICE text file distributed | ||
| as part of the Derivative Works; within the Source form or | ||
| documentation, if provided along with the Derivative Works; or, | ||
| within a display generated by the Derivative Works, if and | ||
| wherever such third-party notices normally appear. The contents | ||
| of the NOTICE file are for informational purposes only and | ||
| do not modify the License. You may add Your own attribution | ||
| notices within Derivative Works that You distribute, alongside | ||
| or as an addendum to the NOTICE text from the Work, provided | ||
| that such additional attribution notices cannot be construed | ||
| as modifying the License. | ||
|
|
||
| You may add Your own copyright statement to Your modifications and | ||
| may provide additional or different license terms and conditions | ||
| for use, reproduction, or distribution of Your modifications, or | ||
| for any such Derivative Works as a whole, provided Your use, | ||
| reproduction, and distribution of the Work otherwise complies with | ||
| the conditions stated in this License. | ||
|
|
||
| 5. Submission of Contributions. Unless You explicitly state otherwise, | ||
| any Contribution intentionally submitted for inclusion in the Work | ||
| by You to the Licensor shall be under the terms and conditions of | ||
| this License, without any additional terms or conditions. | ||
| Notwithstanding the above, nothing herein shall supersede or modify | ||
| the terms of any separate license agreement you may have executed | ||
| with Licensor regarding such Contributions. | ||
|
|
||
| 6. Trademarks. This License does not grant permission to use the trade | ||
| names, trademarks, service marks, or product names of the Licensor, | ||
| except as required for reasonable and customary use in describing the | ||
| origin of the Work and reproducing the content of the NOTICE file. | ||
|
|
||
| 7. Disclaimer of Warranty. Unless required by applicable law or | ||
| agreed to in writing, Licensor provides the Work (and each | ||
| Contributor provides its Contributions) on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||
| implied, including, without limitation, any warranties or conditions | ||
| of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | ||
| PARTICULAR PURPOSE. You are solely responsible for determining the | ||
| appropriateness of using or redistributing the Work and assume any | ||
| risks associated with Your exercise of permissions under this License. | ||
|
|
||
| 8. Limitation of Liability. In no event and under no legal theory, | ||
| whether in tort (including negligence), contract, or otherwise, | ||
| unless required by applicable law (such as deliberate and grossly | ||
| negligent acts) or agreed to in writing, shall any Contributor be | ||
| liable to You for damages, including any direct, indirect, special, | ||
| incidental, or consequential damages of any character arising as a | ||
| result of this License or out of the use or inability to use the | ||
| Work (including but not limited to damages for loss of goodwill, | ||
| work stoppage, computer failure or malfunction, or any and all | ||
| other commercial damages or losses), even if such Contributor | ||
| has been advised of the possibility of such damages. | ||
|
|
||
| 9. Accepting Warranty or Additional Liability. While redistributing | ||
| the Work or Derivative Works thereof, You may choose to offer, | ||
| and charge a fee for, acceptance of support, warranty, indemnity, | ||
| or other liability obligations and/or rights consistent with this | ||
| License. However, in accepting such obligations, You may act only | ||
| on Your own behalf and on Your sole responsibility, not on behalf | ||
| of any other Contributor, and only if You agree to indemnify, | ||
| defend, and hold each Contributor harmless for any liability | ||
| incurred by, or claims asserted against, such Contributor by reason | ||
| of your accepting any such warranty or additional liability. | ||
|
|
||
| END OF TERMS AND CONDITIONS | ||
|
|
||
| APPENDIX: How to apply the Apache License to your work. | ||
|
|
||
| To apply the Apache License to your work, attach the following | ||
| boilerplate notice, with the fields enclosed by brackets "[]" | ||
| replaced with your own identifying information. (Don't include | ||
| the brackets!) The text should be enclosed in the appropriate | ||
| comment syntax for the file format. We also recommend that a | ||
| file or class name and description of purpose be included on the | ||
| same "printed page" as the copyright notice for easier | ||
| identification within third-party archives. | ||
|
|
||
| Copyright [yyyy] [name of copyright owner] | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| Copyright 2022 Platformatic | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this file except in compliance with the License. | ||
| You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # @platformatic/runtime | ||
|
|
||
| Check out the full documentation for Platformatic Runtime on [our website](https://oss.platformatic.dev/docs/getting-started/quick-start-guide). | ||
|
|
||
| ## Install | ||
|
|
||
| ```sh | ||
| npm install @platformatic/runtime | ||
| ``` | ||
|
|
||
| ## License | ||
|
|
||
| Apache 2.0 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| { | ||
| "$schema": "https://platformatic.dev/schemas/v0.20.0/runtime", | ||
| "entrypoint": "invalid", | ||
| "autoload": { | ||
| "path": "../monorepo", | ||
| "exclude": ["docs", "composerApp"], | ||
| "mappings": { | ||
| "serviceAppWithLogger": { | ||
| "id": "with-logger", | ||
| "config": "platformatic.service.json" | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "$schema": "https://platformatic.dev/schemas/v0.20.0/runtime", | ||
| "entrypoint": "serviceApp", | ||
| "autoload": { | ||
| "exclude": ["docs", "composerApp"], | ||
| "mappings": { | ||
| "serviceAppWithMultiplePlugins": { | ||
| "id": "multi-plugin-service", | ||
| "config": "platformatic.service.json" | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "$schema": "https://platformatic.dev/schemas/v0.20.0/runtime", | ||
| "entrypoint": "serviceApp", | ||
| "autoload": { | ||
| "path": "../monorepo" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| { | ||
| "$schema": "https://platformatic.dev/schemas/v0.20.0/runtime", | ||
| "entrypoint": "composerApp", | ||
| "hotReload": false, | ||
| "autoload": { | ||
| "path": "../monorepo", | ||
| "exclude": ["docs"], | ||
| "mappings": { | ||
| "serviceAppWithLogger": { | ||
| "id": "with-logger", | ||
| "config": "platformatic.service.json" | ||
| }, | ||
| "serviceAppWithMultiplePlugins": { | ||
| "id": "multi-plugin-service", | ||
| "config": "platformatic.service.json" | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| { | ||
| "$schema": "https://platformatic.dev/schemas/v0.20.0/runtime", | ||
| "entrypoint": "with-logger", | ||
| "allowCycles": false, | ||
| "hotReload": false, | ||
| "autoload": { | ||
| "path": "../monorepo", | ||
| "exclude": [ | ||
| "composerApp", | ||
| "docs", | ||
| "serviceAppWithMultiplePlugins", | ||
| "serviceAppWithLogger" | ||
| ], | ||
| "mappings": { | ||
| "serviceApp": { | ||
| "id": "with-logger", | ||
| "config": "platformatic.service.json" | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
| "$schema": "https://platformatic.dev/schemas/v0.20.0/runtime", | ||
| "entrypoint": "serviceApp", | ||
| "allowCycles": false, | ||
| "hotReload": false, | ||
| "autoload": { | ||
| "path": "../monorepo", | ||
| "exclude": ["docs", "composerApp"], | ||
| "mappings": { | ||
| "serviceAppWithLogger": { | ||
| "id": "with-logger", | ||
| "config": "platformatic.service.json" | ||
| }, | ||
| "serviceAppWithMultiplePlugins": { | ||
| "id": "multi-plugin-service", | ||
| "config": "platformatic.service.json" | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
| "$schema": "https://platformatic.dev/schemas/v0.20.0/runtime", | ||
| "entrypoint": "serviceApp", | ||
| "allowCycles": true, | ||
| "hotReload": false, | ||
| "autoload": { | ||
| "path": "../monorepo", | ||
| "exclude": ["docs", "composerApp"], | ||
| "mappings": { | ||
| "serviceAppWithLogger": { | ||
| "id": "with-logger", | ||
| "config": "platformatic.service.json" | ||
| }, | ||
| "serviceAppWithMultiplePlugins": { | ||
| "id": "multi-plugin-service", | ||
| "config": "platformatic.service.json" | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "$schema": "https://platformatic.dev/schemas/v0.20.0/runtime", | ||
| "entrypoint": "doesNotExist", | ||
| "services": [] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "$schema": "https://platformatic.dev/schemas/v0.20.0/runtime", | ||
| "entrypoint": "" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "$schema": "https://platformatic.dev/schemas/v0.20.0/runtime", | ||
| "entrypoint": "serviceThrowsOnStart", | ||
| "services": [ | ||
| { | ||
| "id": "serviceThrowsOnStart", | ||
| "path": "../serviceAppThrowsOnStart", | ||
| "config": "platformatic.service.json" | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| { | ||
| "$schema": "https://platformatic.dev/schemas/v0.23.2/composer", | ||
| "server": { | ||
| "hostname": "127.0.0.1", | ||
| "port": 0, | ||
| "logger": { | ||
| "level": "info" | ||
| }, | ||
| "pluginTimeout": 0 | ||
| }, | ||
| "composer": { | ||
| "services": [ | ||
| { | ||
| "id": "with-logger", | ||
| "openapi": { | ||
| "url": "/documentation/json", | ||
| "prefix": "/with-logger" | ||
| } | ||
| }, | ||
| { | ||
| "id": "multi-plugin-service", | ||
| "origin": "{PLT_ORIGIN_BUT_NOT_PROVIDED}", | ||
| "openapi": { | ||
| "url": "/documentation/json", | ||
| "prefix": "/multi-plugin-service" | ||
| } | ||
| }, | ||
| { | ||
| "id": "serviceApp", | ||
| "openapi": { | ||
| "url": "/documentation/json", | ||
| "prefix": "/service-app" | ||
| } | ||
| } | ||
| ], | ||
| "refreshTimeout": 5000 | ||
| }, | ||
| "watch": false | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| This package should be excluded. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| async function getGreeting () { | ||
| const dep2 = await import('./dep2.mjs') | ||
|
|
||
| return dep2.default.greeting | ||
| } | ||
|
|
||
| module.exports = { getGreeting } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| import dep3 from './dep3.mjs' | ||
| export default dep3 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| import dep4 from './dep4.js' | ||
| export default dep4 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| module.exports = { greeting: 'hello' } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| { | ||
| "$schema": "https://platformatic.dev/schemas/v0.20.0/service", | ||
| "server": { | ||
| "hostname": "127.0.0.1", | ||
| "port": 0 | ||
| }, | ||
| "service": { | ||
| "openapi": true | ||
| }, | ||
| "plugins": { | ||
| "hotReload": true, | ||
| "paths": [ | ||
| "plugin.js" | ||
| ] | ||
| }, | ||
| "clients": [ | ||
| { | ||
| "path": "./with-logger", | ||
| "url": "{PLT_WITH_LOGGER_URL}" | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| 'use strict' | ||
|
|
||
| const { request } = require('undici') | ||
| const { getGreeting } = require('./deps/dep1') | ||
|
|
||
| /** @param {import('fastify').FastifyInstance} app */ | ||
| module.exports = async function (app) { | ||
| app.get('/', async () => { | ||
| return { hello: await getGreeting() + '123' } | ||
| }) | ||
|
|
||
| app.get('/upstream', async () => { | ||
| const res = await request('http://with-logger.plt.local') | ||
| const text = await res.body.text() | ||
| return text | ||
| }) | ||
|
|
||
| app.get('/crash', () => { | ||
| setImmediate(() => { | ||
| throw new Error('boom') | ||
| }) | ||
|
|
||
| return 'ok' | ||
| }) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "name": "with-logger", | ||
| "main": "./with-logger.cjs", | ||
| "types": "./with-logger.d.ts" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| 'use strict' | ||
|
|
||
| // const pltClient = require('@platformatic/client') | ||
| const { join } = require('path') | ||
|
|
||
| async function plugin () { | ||
|
|
||
| } | ||
|
|
||
| async function generateWithLoggerClientPlugin (app, opts) { | ||
| app.register(plugin, { | ||
| type: 'openapi', | ||
| name: 'withLogger', | ||
| path: join(__dirname, 'with-logger.openapi.json'), | ||
| url: opts.url | ||
| }) | ||
| } | ||
|
|
||
| generateWithLoggerClientPlugin[Symbol.for('plugin-meta')] = { | ||
| name: 'withLogger OpenAPI Client' | ||
| } | ||
| generateWithLoggerClientPlugin[Symbol.for('skip-override')] = true | ||
|
|
||
| module.exports = generateWithLoggerClientPlugin | ||
| module.exports.default = generateWithLoggerClientPlugin |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import { FastifyPluginAsync } from 'fastify' | ||
|
|
||
| interface GetRequest { | ||
| } | ||
|
|
||
| interface GetResponse { | ||
| } | ||
|
|
||
| interface WithLogger { | ||
| get(req: GetRequest): Promise<GetResponse>; | ||
| } | ||
|
|
||
| type WithLoggerPlugin = FastifyPluginAsync<NonNullable<withLogger.WithLoggerOptions>> | ||
|
|
||
| declare module 'fastify' { | ||
| interface ConfigureWithLogger { | ||
| async getHeaders(req: FastifyRequest, reply: FastifyReply): Promise<Record<string,string>>; | ||
| } | ||
| interface FastifyInstance { | ||
| 'withLogger': WithLogger; | ||
| configureWithLogger(opts: ConfigureWithLogger): unknown | ||
| } | ||
|
|
||
| interface FastifyRequest { | ||
| 'withLogger': WithLogger; | ||
| } | ||
| } | ||
|
|
||
| declare namespace withLogger { | ||
| export interface WithLoggerOptions { | ||
| url: string | ||
| } | ||
| export const withLogger: WithLoggerPlugin; | ||
| export { withLogger as default }; | ||
| } | ||
|
|
||
| declare function withLogger(...params: Parameters<WithLoggerPlugin>): ReturnType<WithLoggerPlugin>; | ||
| export = withLogger; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| { | ||
| "openapi": "3.0.3", | ||
| "info": { | ||
| "title": "Platformatic", | ||
| "description": "This is a service built on top of Platformatic", | ||
| "version": "1.0.0" | ||
| }, | ||
| "components": { | ||
| "schemas": {} | ||
| }, | ||
| "paths": { | ||
| "/": { | ||
| "get": { | ||
| "responses": { | ||
| "200": { | ||
| "description": "Default Response" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| { | ||
| "$schema": "https://platformatic.dev/schemas/v0.20.0/service", | ||
| "server": { | ||
| "hostname": "127.0.0.1", | ||
| "port": 0, | ||
| "logger": { | ||
| "name": "service-with-logger", | ||
| "level": "trace" | ||
| } | ||
| }, | ||
| "service": { | ||
| "openapi": true | ||
| }, | ||
| "plugins": { | ||
| "paths": [ | ||
| "plugin.js" | ||
| ] | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| 'use strict' | ||
|
|
||
| /** @param {import('fastify').FastifyInstance} app */ | ||
| module.exports = async function (app) { | ||
| app.get('/', async () => { | ||
| return { hello: 'world' } | ||
| }) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| { | ||
| "$schema": "https://platformatic.dev/schemas/v0.20.0/service", | ||
| "server": { | ||
| "hostname": "127.0.0.1", | ||
| "port": 0 | ||
| }, | ||
| "service": { | ||
| "openapi": true | ||
| }, | ||
| "plugins": { | ||
| "paths": [ | ||
| { | ||
| "path": "plugin.js", | ||
| "options": { | ||
| "name": "plugin1" | ||
| } | ||
| }, | ||
| { | ||
| "path": "plugin2.mjs", | ||
| "options": { | ||
| "name": "plugin2" | ||
| } | ||
| } | ||
| ] | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| 'use strict' | ||
|
|
||
| /** @param {import('fastify').FastifyInstance} app */ | ||
| module.exports = async function (app, options) { | ||
| app.get('/plugin1', async () => { | ||
| return { hello: options.name } | ||
| }) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export default async function (app, options) { | ||
| app.get('/plugin2', async () => { | ||
| return { hello: options.name } | ||
| }) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "$schema": "https://platformatic.dev/schemas/v0.20.0/service", | ||
| "server": { | ||
| "hostname": "127.0.0.1", | ||
| "port": 0 | ||
| }, | ||
| "plugins": { | ||
| "hotReload": true, | ||
| "paths": [ | ||
| "plugin.js" | ||
| ] | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| 'use strict' | ||
|
|
||
| /** @param {import('fastify').FastifyInstance} app */ | ||
| module.exports = async function (app) { | ||
| throw new Error('boom') | ||
| } |