Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 9 additions & 31 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,13 @@ on:
- main
- 'releases/**'

env:
CARGO_NET_GIT_FETCH_WITH_CLI: 'true'
# GIT_SSH_COMMAND: 'ssh -o UserKnownHostsFile=/github/home/.ssh/known_hosts -o StrictHostKeyChecking=yes'

jobs:
ci:
name: CI - Node.js ${{ matrix.node-version }} & Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
matrix:
node-version:
- '20'
- '22'
- '24'
python-version:
Expand All @@ -38,40 +33,23 @@ jobs:
with:
ssh-private-key: |
${{ secrets.SSH_PRIVATE_KEY }}
${{ secrets.HTTP_HANDLER_ACCESS_TOKEN }}
${{ secrets.HTTP_REWRITER_ACCESS_TOKEN }}
- uses: actions/setup-node@v5
with:
node-version: ${{ matrix.node-version }}
- uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python }}
- uses: actions/cache@v4
with:
path: ~/.pnpm-store
key: node-modules-${{ hashFiles('package.json') }}
- uses: pnpm/action-setup@v4
with:
version: latest
- uses: actions/setup-node@v5
with:
node-version: ${{ matrix.node-version }}
cache: pnpm
registry-url: https://registry.npmjs.org
- name: Authenticate with private NPM package
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.PYTHON_NODE_NPM_TOKEN }}" > ~/.npmrc
- name: Install dependencies
run: pnpm install
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
# TODO: replace with `pnpm install` when using published dependency
- name: Build python-node manually
run: |
# Configure git to use SSH host aliases for private repos (needed by cargo)
git config --global url."ssh://git@github.com-http-handler/platformatic/http-handler".insteadOf "ssh://git@github.com/platformatic/http-handler"
git config --global url."ssh://git@github.com-http-handler/platformatic/http-handler.git".insteadOf "ssh://git@github.com/platformatic/http-handler.git"
git config --global url."ssh://git@github.com-http-rewriter/platformatic/http-rewriter".insteadOf "ssh://git@github.com/platformatic/http-rewriter"
git config --global url."ssh://git@github.com-http-rewriter/platformatic/http-rewriter.git".insteadOf "ssh://git@github.com/platformatic/http-rewriter.git"

cd node_modules/@platformatic/python-node
pnpm install --ignore-scripts
pnpm run build
pnpm run build:wasm
pnpm run build:fix
env:
NODE_AUTH_TOKEN: ${{ secrets.PYTHON_NODE_NPM_TOKEN }}
- name: Run Full Test Suite
shell: bash
run: pnpm test
54 changes: 54 additions & 0 deletions .github/workflows/publish_release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Publish release

on:
workflow_dispatch:
inputs:
version:
description: "The version number to tag and release"
required: true
type: string
prerelease:
description: "Release as pre-release"
required: false
type: boolean
default: false

jobs:
release-npm:
runs-on: ubuntu-latest
environment: main
permissions:
contents: write
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Use supported Node.js Version
uses: actions/setup-node@v4
with:
node-version: 22
- name: Restore cached dependencies
uses: actions/cache@v4
with:
path: ~/.pnpm-store
key: node-modules-${{ hashFiles('package.json') }}
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: latest
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Bump version and push commit
run: |
pnpm version ${{ inputs.version }} --no-git-tag-version
git config --global user.name "${{ github.actor }}"
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
git commit -a -m "Bumped v${{ inputs.version }}"
git push origin HEAD:${{ github.ref }}
- name: Publish new version
run: |
npm install npm -g
npm publish --access public --tag ${{ inputs.prerelease == true && 'next' || 'latest' }}
- name: Create release notes
run: |
npx @matteo.collina/release-notes -a ${{ secrets.GITHUB_TOKEN }} -t v${{ inputs.version }} -r ${{ github.repository }} ${{ github.event.inputs.prerelease == 'true' && '-p' || '' }} -c ${{ github.ref }}
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -140,5 +140,4 @@ plt-python
wordpress

package-lock.json
pnpm-lock.yaml
yarn.lock
61 changes: 21 additions & 40 deletions config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,32 +164,14 @@ export interface PlatformaticPythonConfiguration {
plugins?: {
[k: string]: unknown;
};
metrics?:
| boolean
| {
port?: number | string;
hostname?: string;
endpoint?: string;
server?: "own" | "parent" | "hide";
defaultMetrics?: {
enabled: boolean;
};
auth?: {
username: string;
password: string;
};
labels?: {
[k: string]: string;
};
};
telemetry?: {
enabled?: boolean | string;
/**
* The name of the service. Defaults to the folder name if not specified.
* The name of the application. Defaults to the folder name if not specified.
*/
serviceName: string;
applicationName: string;
/**
* The version of the service (optional)
* The version of the application (optional)
*/
version?: string;
/**
Expand Down Expand Up @@ -271,6 +253,7 @@ export interface PlatformaticPythonConfiguration {
| string;
$schema?: string;
module?: string;
application?: {};
service?: {
openapi?:
| {
Expand Down Expand Up @@ -324,21 +307,14 @@ export interface PlatformaticPythonConfiguration {
};
};
};
clients?: {
serviceId?: string;
name?: string;
type?: "openapi" | "graphql";
path?: string;
schema?: string;
url?: string;
fullResponse?: boolean;
fullRequest?: boolean;
validateResponse?: boolean;
}[];
runtime?: {
preload?: string | string[];
basePath?: string;
services?: {
[k: string]: unknown;
}[];
workers?: number | string;
workersRestartDelay?: number | string;
logger?: {
level: (
| ("fatal" | "error" | "warn" | "info" | "debug" | "trace" | "silent")
Expand Down Expand Up @@ -423,9 +399,10 @@ export interface PlatformaticPythonConfiguration {
};
startTimeout?: number;
restartOnError?: boolean | number;
exitOnUnhandledErrors?: boolean;
gracefulShutdown?: {
runtime: number | string;
service: number | string;
application: number | string;
};
health?: {
enabled?: boolean | string;
Expand All @@ -435,7 +412,7 @@ export interface PlatformaticPythonConfiguration {
maxELU?: number | string;
maxHeapUsed?: number | string;
maxHeapTotal?: number | string;
maxYoungGeneration?: number;
maxYoungGeneration?: number | string;
};
undici?: {
agentOptions?: {
Expand Down Expand Up @@ -512,6 +489,10 @@ export interface PlatformaticPythonConfiguration {
labels?: {
[k: string]: string;
};
/**
* The label name to use for the application identifier in metrics (e.g., applicationId, serviceId)
*/
applicationLabel?: string;
readiness?:
| boolean
| {
Expand All @@ -538,17 +519,16 @@ export interface PlatformaticPythonConfiguration {
body?: string;
};
};
additionalProperties?: never;
[k: string]: unknown;
plugins?: string[];
};
telemetry?: {
enabled?: boolean | string;
/**
* The name of the service. Defaults to the folder name if not specified.
* The name of the application. Defaults to the folder name if not specified.
*/
serviceName: string;
applicationName: string;
/**
* The version of the service (optional)
* The version of the application (optional)
*/
version?: string;
/**
Expand Down Expand Up @@ -624,7 +604,8 @@ export interface PlatformaticPythonConfiguration {
watchDisabled?: boolean;
[k: string]: unknown;
};
serviceTimeout?: number | string;
applicationTimeout?: number | string;
messagingTimeout?: number | string;
env?: {
[k: string]: string;
};
Expand Down
20 changes: 7 additions & 13 deletions lib/generator.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Generator as ServiceGenerator } from '@platformatic/service'
import { readFile } from 'node:fs/promises'
import { basename, resolve, join } from 'node:path'
import { join } from 'node:path'
import { packageJson } from '../lib/schema.js'

export class Generator extends ServiceGenerator {
constructor (opts = {}) {
Expand Down Expand Up @@ -31,7 +32,6 @@ export class Generator extends ServiceGenerator {
}

async _getConfigFileContents () {
const packageJson = await this._getStackablePackageJson()
const { server, watch } = await super._getConfigFileContents()

return {
Expand All @@ -52,8 +52,6 @@ export class Generator extends ServiceGenerator {
delete this.config.env.PLT_TYPESCRIPT
delete this.config.defaultEnv.PLT_TYPESCRIPT

const packageJson = await this._getStackablePackageJson()

this.config.dependencies = {
[packageJson.name]: `^${packageJson.version}`
}
Expand All @@ -64,15 +62,11 @@ export class Generator extends ServiceGenerator {
delete this.files['.gitignore']

if (!this.config.isUpdating) {
this.addFile({ path: 'public', file: 'main.py', contents: await readFile(join(import.meta.dirname, 'main.py'), 'utf-8') })
this.addFile({
path: 'public',
file: 'main.py',
contents: await readFile(join(import.meta.dirname, 'main.py'), 'utf-8')
})
}
}

async _getStackablePackageJson () {
if (!this._packageJson) {
this._packageJson = JSON.parse(await readFile(resolve(import.meta.dirname, '../package.json'), 'utf-8'))
}

return this._packageJson
}
}
40 changes: 13 additions & 27 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,19 @@
import { buildStackable } from '@platformatic/service'
import { Generator as _Generator } from './generator.js'
import { create as createService, platformaticService } from '@platformatic/service'
import { plugin } from './plugin.js'
import { packageJson, schema } from './schema.js'
import { schema } from './schema.js'

export async function stackable (fastify, opts) {
await fastify.register(plugin, opts)
export async function python (app, capability) {
await platformaticService(app, capability)
await app.register(plugin, capability)
}

stackable.Generator = _Generator
stackable.configType = 'python'
stackable.schema = schema
stackable.configManagerConfig = {
schemaOptions: {
useDefaults: true,
coerceTypes: true,
allErrors: true,
strict: false
}
export async function create (configOrRoot, sourceOrConfig, context) {
return createService(configOrRoot, sourceOrConfig, {
schema,
applicationFactory: python,
...context
})
}

export const Generator = _Generator

export default {
configType: 'python',
configManagerConfig: stackable.configManagerConfig,
/* c8 ignore next 3 */
async buildStackable (opts) {
return buildStackable(opts, stackable)
},
schema,
version: packageJson.version
}
export { Generator } from './generator.js'
export { packageJson, schema, schemaComponents, version } from './schema.js'
29 changes: 11 additions & 18 deletions lib/plugin.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { readFile } from 'node:fs/promises'
import { basename } from 'node:path'
import fp from 'fastify-plugin'

const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'TRACE']

const capitalizeHeaders = header => header.replace(/(^|-)([a-z])/g, (_, dash, letter) => dash + letter.toUpperCase());
const capitalizeHeaders = header => header.replace(/(^|-)([a-z])/g, (_, dash, letter) => dash + letter.toUpperCase())

export async function plugin (server, opts) {
// A full URL string is needed for PHP, but Node.js splits that across a bunch of places.
function urlForRequest (req) {
const proto = req.raw.protocol ?? 'http:'
const host = req.headers.host ?? 'localhost'
return new URL(req.url, `${proto}//${host}`)
}

export async function pythonPlugin (server, opts) {
// We import this dynically to provide better error reporting in case
// this module fails to load one of the native bindings
const { Python, Request } = await import('@platformatic/python-node')
Expand Down Expand Up @@ -95,17 +101,4 @@ export async function plugin (server, opts) {
})
}

// A full URL string is needed for Python, but Node.js splits that across a bunch of places.
function urlForRequest(req) {
const proto = req.raw.protocol ?? 'http:'
const host = req.headers.host ?? 'localhost'
return new URL(req.url, `${proto}//${host}`)
}

// Currently header values must be arrays. Need to make it support single values too.
function fixHeaders (headers) {
return Object.fromEntries(
Object.entries(headers)
.map(([key, value]) => [key, [value]])
)
}
export const plugin = fp(pythonPlugin)
Loading
Loading