@crossdelta/platform-sdk 0.13.1 → 0.13.2

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 (62) hide show
  1. package/package.json +1 -1
  2. package/bin/cli.js +0 -312
  3. package/bin/docs/generators/README.md +0 -56
  4. package/bin/docs/generators/code-style.md +0 -96
  5. package/bin/docs/generators/hono-bun.md +0 -181
  6. package/bin/docs/generators/hono-node.md +0 -194
  7. package/bin/docs/generators/nest.md +0 -358
  8. package/bin/docs/generators/service.md +0 -564
  9. package/bin/docs/generators/testing.md +0 -97
  10. package/bin/integration.collection.json +0 -18
  11. package/bin/templates/hono-microservice/Dockerfile.hbs +0 -16
  12. package/bin/templates/hono-microservice/biome.json.hbs +0 -3
  13. package/bin/templates/hono-microservice/src/index.ts.hbs +0 -18
  14. package/bin/templates/hono-microservice/tsconfig.json.hbs +0 -14
  15. package/bin/templates/nest-microservice/Dockerfile.hbs +0 -37
  16. package/bin/templates/nest-microservice/biome.json.hbs +0 -3
  17. package/bin/templates/nest-microservice/src/app.context.ts.hbs +0 -17
  18. package/bin/templates/nest-microservice/src/events/events.module.ts.hbs +0 -8
  19. package/bin/templates/nest-microservice/src/events/events.service.ts.hbs +0 -22
  20. package/bin/templates/nest-microservice/src/main.ts.hbs +0 -34
  21. package/bin/templates/workspace/.github/README.md +0 -70
  22. package/bin/templates/workspace/.github/actions/check-image-tag-exists/action.yml +0 -27
  23. package/bin/templates/workspace/.github/actions/check-image-tag-exists/index.js +0 -179
  24. package/bin/templates/workspace/.github/actions/check-path-changes/action.yml +0 -21
  25. package/bin/templates/workspace/.github/actions/check-path-changes/index.js +0 -192
  26. package/bin/templates/workspace/.github/actions/detect-skipped-services/action.yml +0 -38
  27. package/bin/templates/workspace/.github/actions/generate-scope-matrix/action.yml +0 -17
  28. package/bin/templates/workspace/.github/actions/generate-scope-matrix/index.js +0 -355
  29. package/bin/templates/workspace/.github/actions/prepare-build-context/action.yml +0 -49
  30. package/bin/templates/workspace/.github/actions/resolve-scope-tags/action.yml +0 -31
  31. package/bin/templates/workspace/.github/actions/resolve-scope-tags/index.js +0 -398
  32. package/bin/templates/workspace/.github/actions/setup-bun-install/action.yml.hbs +0 -57
  33. package/bin/templates/workspace/.github/copilot-chat-configuration.json +0 -49
  34. package/bin/templates/workspace/.github/copilot-instructions.md.hbs +0 -72
  35. package/bin/templates/workspace/.github/dependabot.yml +0 -18
  36. package/bin/templates/workspace/.github/workflows/build-and-deploy.yml.hbs +0 -228
  37. package/bin/templates/workspace/.github/workflows/lint-and-tests.yml.hbs +0 -32
  38. package/bin/templates/workspace/.github/workflows/publish-packages.yml +0 -154
  39. package/bin/templates/workspace/apps/.gitkeep +0 -0
  40. package/bin/templates/workspace/biome.json.hbs +0 -62
  41. package/bin/templates/workspace/bunfig.toml.hbs +0 -5
  42. package/bin/templates/workspace/docs/.gitkeep +0 -0
  43. package/bin/templates/workspace/editorconfig.hbs +0 -9
  44. package/bin/templates/workspace/gitignore.hbs +0 -15
  45. package/bin/templates/workspace/infra/Pulumi.dev.yaml.hbs +0 -5
  46. package/bin/templates/workspace/infra/Pulumi.yaml.hbs +0 -6
  47. package/bin/templates/workspace/infra/index.ts.hbs +0 -56
  48. package/bin/templates/workspace/infra/package.json.hbs +0 -21
  49. package/bin/templates/workspace/infra/services/.gitkeep +0 -0
  50. package/bin/templates/workspace/infra/tsconfig.json.hbs +0 -15
  51. package/bin/templates/workspace/npmrc.hbs +0 -2
  52. package/bin/templates/workspace/package.json.hbs +0 -51
  53. package/bin/templates/workspace/packages/.gitkeep +0 -0
  54. package/bin/templates/workspace/packages/contracts/README.md.hbs +0 -166
  55. package/bin/templates/workspace/packages/contracts/package.json.hbs +0 -22
  56. package/bin/templates/workspace/packages/contracts/src/events/index.ts +0 -16
  57. package/bin/templates/workspace/packages/contracts/src/index.ts +0 -10
  58. package/bin/templates/workspace/packages/contracts/src/stream-policies.ts.hbs +0 -40
  59. package/bin/templates/workspace/packages/contracts/tsconfig.json.hbs +0 -7
  60. package/bin/templates/workspace/pnpm-workspace.yaml.hbs +0 -5
  61. package/bin/templates/workspace/turbo.json +0 -37
  62. package/bin/templates/workspace/turbo.json.hbs +0 -29
@@ -1,18 +0,0 @@
1
- // IMPORTANT: telemetry must be imported first to patch modules before they're loaded
2
- import '@crossdelta/telemetry'
3
-
4
- import { Hono } from 'hono'
5
-
6
- const port = Number(process.env.{{envKey}}_PORT) || 8080
7
- const app = new Hono()
8
-
9
- app.get('/health', (c) => {
10
- return c.json({ status: 'ok' })
11
- })
12
-
13
- Bun.serve({
14
- port,
15
- fetch: app.fetch,
16
- })
17
-
18
- console.log(`🚀 Service ready at http://localhost:${port}`)
@@ -1,14 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ES2022",
5
- "moduleResolution": "Bundler",
6
- "strict": true,
7
- "lib": ["ES2022"],
8
- "types": ["bun"],
9
- "jsx": "react-jsx",
10
- "jsxImportSource": "hono/jsx",
11
- "esModuleInterop": true,
12
- "skipLibCheck": true
13
- }
14
- }
@@ -1,37 +0,0 @@
1
- ARG NODE_VERSION={{nodeVersion}}
2
- ARG BUN_VERSION={{bunVersion}}
3
-
4
- # STAGE 1 — Build
5
- FROM oven/bun:${BUN_VERSION}-alpine AS builder
6
- WORKDIR /app
7
-
8
- COPY package.json tsconfig*.json nest-cli.json ./
9
- COPY src ./src
10
-
11
- RUN --mount=type=secret,id=NPM_TOKEN \
12
- export NPM_TOKEN="$(cat /run/secrets/NPM_TOKEN)" && \
13
- bun install
14
-
15
- RUN bun run build
16
-
17
- # STAGE 2 — Production dependencies
18
- FROM oven/bun:${BUN_VERSION}-alpine AS deps
19
- WORKDIR /app
20
-
21
- COPY package.json ./
22
-
23
- RUN --mount=type=secret,id=NPM_TOKEN \
24
- export NPM_TOKEN="$(cat /run/secrets/NPM_TOKEN)" && \
25
- bun install --production --omit=optional
26
-
27
- # STAGE 3 — Runtime
28
- FROM node:${NODE_VERSION}-alpine AS production
29
- WORKDIR /app
30
-
31
- COPY --from=builder /app/dist ./dist
32
- COPY --from=deps /app/node_modules ./node_modules
33
-
34
- USER node
35
- ENV NODE_ENV=production
36
-
37
- CMD ["node", "dist/main.js"]
@@ -1,3 +0,0 @@
1
- {
2
- "extends": ["../../biome.json"]
3
- }
@@ -1,17 +0,0 @@
1
- import type { INestApplication, Type } from '@nestjs/common'
2
-
3
- let app: INestApplication
4
-
5
- export const setAppContext = (nestApp: INestApplication): void => {
6
- app = nestApp
7
- }
8
-
9
- export const getAppContext = (): INestApplication => {
10
- if (!app) throw new Error('App context not initialized')
11
- return app
12
- }
13
-
14
- export const getService = <T>(serviceClass: Type<T>): T => {
15
- return getAppContext().get(serviceClass)
16
- }
17
-
@@ -1,8 +0,0 @@
1
- import { Module } from '@nestjs/common'
2
- import { EventsService } from './events.service'
3
-
4
- @Module({
5
- providers: [EventsService],
6
- exports: [EventsService],
7
- })
8
- export class EventsModule {}
@@ -1,22 +0,0 @@
1
- import { Injectable, Logger } from '@nestjs/common'
2
- import { consumeJetStreams } from '@crossdelta/cloudevents'
3
-
4
- @Injectable()
5
- export class EventsService {
6
- private readonly logger = new Logger(EventsService.name)
7
-
8
- async startConsumers(): Promise<void> {
9
- // Services NEVER create streams!
10
- // - Development: pf dev auto-creates ephemeral streams from contracts
11
- // - Production: Pulumi materializes persistent streams
12
-
13
- // Uncomment and configure when you have events:
14
- // consumeJetStreams({
15
- // streams: ['ORDERS'],
16
- // consumer: '{{serviceName}}',
17
- // discover: './src/events/**/*.event.ts',
18
- // })
19
-
20
- this.logger.log('Event consumers ready')
21
- }
22
- }
@@ -1,34 +0,0 @@
1
- // IMPORTANT: telemetry must be imported first to patch modules before they're loaded
2
- import '@crossdelta/telemetry'
3
-
4
- import { env } from 'node:process'
5
- import { ConsoleLogger } from '@nestjs/common'
6
- import { NestFactory } from '@nestjs/core'
7
- import { AppModule } from './app.module'
8
- import { setAppContext } from './app.context'
9
- import { EventsService } from './events/events.service'
10
-
11
- const port = Number(process.env.{{envKey}}_PORT) || {{defaultPort}}
12
- const serviceName = '{{displayName}}'
13
-
14
- const logger = new ConsoleLogger({
15
- json: env.JSON_LOGS === 'true',
16
- prefix: serviceName,
17
- })
18
-
19
- async function bootstrap() {
20
- const app = await NestFactory.create(AppModule, { logger })
21
-
22
- // Make app context available to event handlers
23
- setAppContext(app)
24
-
25
- // Start NATS event consumers
26
- const eventsService = app.get(EventsService)
27
- await eventsService.startConsumers()
28
-
29
- await app
30
- .listen(port)
31
- .then(() => logger.log(`${serviceName} is running on port ${port}`))
32
- }
33
-
34
- bootstrap()
@@ -1,70 +0,0 @@
1
- # CI/CD Workflows
2
-
3
- This project includes GitHub Actions workflows for continuous integration and deployment.
4
-
5
- ## Workflows
6
-
7
- ### Pull Request Checks (`lint-and-tests.yml`)
8
-
9
- Runs on every pull request to `main`:
10
- - Lints the codebase (`bun lint`)
11
- - Runs tests (`bun test`)
12
- - Uses dependency caching for faster builds
13
-
14
- ### Build and Deploy (`build-and-deploy.yml`)
15
-
16
- Runs on pushes to `main` and after package publishing:
17
- - Builds Docker images for changed scopes (apps/services)
18
- - Pushes images to GitHub Container Registry (GHCR)
19
- - Deploys infrastructure using Pulumi
20
-
21
- ## Required Secrets
22
-
23
- Configure these secrets in your GitHub repository settings:
24
-
25
- | Secret | Description |
26
- |--------|-------------|
27
- | `PULUMI_ACCESS_TOKEN` | Pulumi Cloud access token for infrastructure deployment |
28
- | `DIGITALOCEAN_TOKEN` | DigitalOcean API token for DOKS/spaces access |
29
-
30
- ## Required Variables
31
-
32
- Configure these variables in your GitHub repository settings:
33
-
34
- | Variable | Description | Example |
35
- |----------|-------------|---------|
36
- | `PULUMI_STACK_BASE` | Base name for Pulumi stacks | `myorg/myproject` |
37
-
38
- ## Automatic Permissions
39
-
40
- These are handled automatically via `permissions` in workflows:
41
- - `GITHUB_TOKEN` - GitHub-provided token for GHCR push and API access
42
-
43
- ## Custom Actions
44
-
45
- The workflows use these local actions:
46
-
47
- | Action | Purpose |
48
- |--------|---------|
49
- | `setup-bun-install` | Setup Bun runtime with caching |
50
- | `generate-scope-matrix` | Discover Docker-enabled scopes for matrix builds |
51
- | `check-image-tag-exists` | Skip builds if image tag already exists in GHCR |
52
- | `check-path-changes` | Detect file changes for conditional steps |
53
- | `prepare-build-context` | Flatten turbo prune output for Docker builds |
54
- | `resolve-scope-tags` | Map scope names to image tags for deployment |
55
- | `detect-skipped-services` | Find services marked with `skip: true` in infra config |
56
-
57
- ## Infrastructure Configuration
58
-
59
- Each service in `infra/services/*.ts` can be configured with:
60
-
61
- ```typescript
62
- const config: K8sServiceConfig = {
63
- name: 'my-service',
64
- containerPort: 4001,
65
- skip: false, // Set to true to skip deployment
66
- // ... other config
67
- }
68
- ```
69
-
70
- Services with `skip: true` will be excluded from deployment.
@@ -1,27 +0,0 @@
1
- name: Check image tag exists
2
- inputs:
3
- scope-short-name:
4
- description: Short directory name of the scope (e.g., storefront)
5
- required: true
6
- image-tag:
7
- description: Checksum-based image tag to look for
8
- required: true
9
- github-token:
10
- description: GitHub token with read:packages to query GHCR
11
- required: true
12
- repository-owner:
13
- description: GitHub organization or user that owns the container package
14
- required: false
15
- repository-prefix:
16
- description: Prefix used for GHCR packages (defaults to $GHCR_REPOSITORY_PREFIX or "platform")
17
- required: false
18
- max-pages:
19
- description: Maximum number of pagination pages to inspect (100 tags each)
20
- required: false
21
- default: '5'
22
- outputs:
23
- exists:
24
- description: 'true if the tag already exists in GHCR'
25
- runs:
26
- using: node20
27
- main: index.js
@@ -1,179 +0,0 @@
1
- /**
2
- * Check Image Tag Exists Action
3
- *
4
- * Checks if a specific Docker image tag already exists in GitHub Container Registry (GHCR).
5
- * Used to skip redundant builds when the image checksum hasn't changed.
6
- *
7
- * @example
8
- * - uses: ./.github/actions/check-image-tag-exists
9
- * with:
10
- * scope-short-name: storefront
11
- * image-tag: abc123def456
12
- * github-token: ${{ secrets.GITHUB_TOKEN }}
13
- *
14
- * @outputs exists - 'true' if the tag exists, 'false' otherwise
15
- */
16
-
17
- const { appendFileSync } = require('node:fs')
18
- const { exit } = require('node:process')
19
-
20
- /**
21
- * Builds environment variable keys for GitHub Actions inputs.
22
- * @param {string} name - Input name (e.g., 'scope-short-name')
23
- * @returns {string[]} Possible environment variable keys
24
- */
25
- const buildInputKeys = (name) => {
26
- const trimmed = name.trim()
27
- const upper = trimmed.toUpperCase()
28
- const normalized = upper.replace(/[^A-Z0-9]+/g, '_')
29
- return Array.from(new Set([`INPUT_${upper}`, `INPUT_${normalized}`]))
30
- }
31
-
32
- /**
33
- * Retrieves a GitHub Actions input value from environment variables.
34
- * @param {string} name - Input name as defined in action.yml
35
- * @param {Object} options - Options
36
- * @param {boolean} [options.required=false] - Whether the input is required
37
- * @param {string} [options.defaultValue=''] - Default value if input is not set
38
- * @returns {string} The input value
39
- */
40
- const getInput = (name, { required = false, defaultValue = '' } = {}) => {
41
- const keys = buildInputKeys(name)
42
- const raw = keys.map((key) => process.env[key]).find((value) => typeof value === 'string')
43
- const value = (typeof raw === 'string' ? raw : defaultValue).trim()
44
-
45
- if (required && !value) {
46
- console.error(`Input "${name}" is required`)
47
- exit(1)
48
- }
49
-
50
- return value
51
- }
52
-
53
- /**
54
- * Sets a GitHub Actions output value.
55
- * @param {string} name - Output name
56
- * @param {string} value - Output value
57
- */
58
- const setOutput = (name, value) => {
59
- const outputFile = process.env.GITHUB_OUTPUT
60
- if (outputFile) {
61
- appendFileSync(outputFile, `${name}=${value}\n`)
62
- } else {
63
- console.log(`${name}=${value}`)
64
- }
65
- }
66
-
67
- // Required inputs
68
- const scopeShortName = getInput('scope-short-name', { required: true })
69
- const imageTag = getInput('image-tag', { required: true })
70
- const githubToken = getInput('github-token', { required: true })
71
-
72
- // Optional inputs - defaults from environment for portability
73
- const repositoryOwner = getInput('repository-owner', { defaultValue: process.env.GITHUB_REPOSITORY_OWNER })
74
- const repositoryPrefixInput = getInput('repository-prefix')
75
- const repositoryPrefix =
76
- repositoryPrefixInput || process.env.GHCR_REPOSITORY_PREFIX || process.env.GITHUB_REPOSITORY?.split('/')[1] || 'platform'
77
- const maxPages = Number(getInput('max-pages', { defaultValue: '5' }))
78
-
79
- // GitHub API request headers
80
- const headers = {
81
- Authorization: `Bearer ${githubToken}`,
82
- Accept: 'application/vnd.github+json',
83
- 'X-GitHub-Api-Version': '2022-11-28',
84
- }
85
-
86
- // Construct the full package name (e.g., 'platform/storefront')
87
- const packageName = `${repositoryPrefix}/${scopeShortName}`
88
- const encodedPackage = encodeURIComponent(packageName)
89
-
90
- /**
91
- * Fetches a page of container versions from the GitHub Packages API.
92
- * @param {number} page - Page number (1-indexed)
93
- * @returns {Promise<Object>} Result with versions array, hasMore flag, and notFound flag
94
- */
95
- const fetchVersions = async (page) => {
96
- const url = `https://api.github.com/orgs/${repositoryOwner}/packages/container/${encodedPackage}/versions?per_page=100&page=${page}`
97
- const response = await fetch(url, { headers })
98
-
99
- // 404 means the package doesn't exist yet (first build)
100
- if (response.status === 404) {
101
- return { notFound: true, versions: [], hasMore: false }
102
- }
103
-
104
- if (!response.ok) {
105
- const body = await response.text()
106
- throw new Error(`GitHub API error (${response.status}): ${body}`)
107
- }
108
-
109
- const data = await response.json()
110
- const versions = Array.isArray(data) ? data : []
111
- return {
112
- versions,
113
- hasMore: versions.length === 100, // Full page means there might be more
114
- notFound: false,
115
- }
116
- }
117
-
118
- /**
119
- * Checks if the target tag exists in a list of container versions.
120
- * @param {Object[]} versions - Array of version objects from GitHub API
121
- * @param {string} targetTag - The tag to search for
122
- * @returns {boolean} True if tag is found
123
- */
124
- const tagExistsInVersions = (versions, targetTag) => {
125
- for (const version of versions) {
126
- const tags = version?.metadata?.container?.tags
127
- if (!Array.isArray(tags)) continue
128
- if (tags.includes(targetTag)) {
129
- return true
130
- }
131
- }
132
- return false
133
- }
134
-
135
- /**
136
- * Checks if the image tag exists in GHCR by paginating through all versions.
137
- * @returns {Promise<boolean>} True if tag exists
138
- */
139
- const check = async () => {
140
- const targetTag = imageTag.trim()
141
-
142
- // Paginate through versions until we find the tag or run out of pages
143
- for (let page = 1; page <= maxPages; page += 1) {
144
- const { versions, hasMore, notFound } = await fetchVersions(page)
145
-
146
- // Package doesn't exist yet - tag definitely doesn't exist
147
- if (notFound) {
148
- return false
149
- }
150
-
151
- // Check if target tag is in this page
152
- if (tagExistsInVersions(versions, targetTag)) {
153
- return true
154
- }
155
-
156
- // No more pages to check
157
- if (!hasMore) {
158
- break
159
- }
160
- }
161
-
162
- return false
163
- }
164
-
165
- /**
166
- * Main entry point - runs the check and outputs the result.
167
- */
168
- const run = async () => {
169
- try {
170
- const exists = await check()
171
- setOutput('exists', String(exists))
172
- } catch (error) {
173
- console.error('Failed to check image tag existence:', error)
174
- setOutput('exists', 'false')
175
- exit(1)
176
- }
177
- }
178
-
179
- run()
@@ -1,21 +0,0 @@
1
- name: Detect path changes
2
- description: Reports whether the specified paths changed between two git references.
3
-
4
- inputs:
5
- paths:
6
- description: Newline or comma-delimited list of paths to check.
7
- required: true
8
- base-ref:
9
- description: Base git ref/sha to diff from. Falls back to GITHUB_EVENT_BEFORE or parent commit.
10
- default: ''
11
- head-ref:
12
- description: Head git ref/sha to diff against. Falls back to GITHUB_SHA.
13
- default: ''
14
-
15
- outputs:
16
- changed:
17
- description: 'true' if any of the provided paths changed.
18
-
19
- runs:
20
- using: node20
21
- main: index.js
@@ -1,192 +0,0 @@
1
- /**
2
- * Check Path Changes Action
3
- *
4
- * Detects whether specified paths have changed between two git references.
5
- * Useful for conditional workflow steps based on file changes.
6
- *
7
- * @example
8
- * - uses: ./.github/actions/check-path-changes
9
- * with:
10
- * paths: infra,packages/cloudevents
11
- * base-ref: main
12
- *
13
- * @outputs changed - 'true' if any of the provided paths changed
14
- */
15
-
16
- const { appendFileSync } = require('node:fs')
17
- const { spawnSync } = require('node:child_process')
18
-
19
- /**
20
- * Builds environment variable keys for GitHub Actions inputs.
21
- * @param {string} name - Input name
22
- * @returns {string[]} Possible environment variable keys
23
- */
24
- const buildInputKeys = (name) => {
25
- const trimmed = name.trim()
26
- const upper = trimmed.toUpperCase()
27
- const normalized = upper.replace(/[^A-Z0-9]+/g, '_')
28
- return Array.from(new Set([`INPUT_${upper}`, `INPUT_${normalized}`]))
29
- }
30
-
31
- /**
32
- * Retrieves a GitHub Actions input value from environment variables.
33
- * @param {string} name - Input name
34
- * @param {Object} options - Options
35
- * @param {boolean} [options.required=false] - Whether the input is required
36
- * @param {string} [options.defaultValue=''] - Default value if not set
37
- * @returns {string} The input value
38
- */
39
- const getInput = (name, { required = false, defaultValue = '' } = {}) => {
40
- const keys = buildInputKeys(name)
41
- const raw = keys.map((key) => process.env[key]).find((value) => typeof value === 'string')
42
- const value = typeof raw === 'string' ? raw.trim() : defaultValue
43
-
44
- if (required && !value) {
45
- console.error(`Input "${name}" is required`)
46
- process.exit(1)
47
- }
48
-
49
- return value
50
- }
51
-
52
- /**
53
- * Sets a GitHub Actions output value.
54
- * @param {string} name - Output name
55
- * @param {string|object} value - Output value
56
- */
57
- const setOutput = (name, value) => {
58
- const outputFile = process.env.GITHUB_OUTPUT
59
- const stringValue = typeof value === 'string' ? value : JSON.stringify(value)
60
- if (outputFile) {
61
- appendFileSync(outputFile, `${name}=${stringValue}\n`)
62
- } else {
63
- console.log(`${name}=${stringValue}`)
64
- }
65
- }
66
-
67
- /**
68
- * Parses a comma/newline-separated list of paths.
69
- * @param {string} value - Input string
70
- * @returns {string[]} Array of paths
71
- */
72
- const parseList = (value) => {
73
- if (!value) return []
74
- return value
75
- .split(/[^a-zA-Z0-9._\-\/]+/)
76
- .map((entry) => entry.trim())
77
- .filter(Boolean)
78
- }
79
-
80
- /**
81
- * Runs a git command and returns stdout.
82
- * @param {string[]} args - Git command arguments
83
- * @returns {string} Command output
84
- */
85
- const runGit = (args) => {
86
- const result = spawnSync('git', args, { encoding: 'utf8' })
87
- if (result.error) {
88
- throw result.error
89
- }
90
- if (result.status !== 0) {
91
- throw new Error(`git ${args.join(' ')} failed: ${result.stderr || result.stdout}`)
92
- }
93
- return result.stdout.trim()
94
- }
95
-
96
- const resolveHeadRef = (headRefInput) => {
97
- if (headRefInput) {
98
- return headRefInput
99
- }
100
- if (process.env.GITHUB_SHA) {
101
- return process.env.GITHUB_SHA
102
- }
103
- return runGit(['rev-parse', 'HEAD'])
104
- }
105
-
106
- /**
107
- * Checks if a ref is a zero (null) ref.
108
- * @param {string} ref - Git reference
109
- * @returns {boolean} True if zero ref
110
- */
111
- const isZeroRef = (ref) => !ref || /^0+$/.test(ref)
112
-
113
- /**
114
- * Resolves the base ref for comparison.
115
- * @param {string} baseRefInput - User-provided base ref
116
- * @param {string} headRef - Resolved head ref
117
- * @returns {string} Base ref to use
118
- */
119
- const resolveBaseRef = (baseRefInput, headRef) => {
120
- if (baseRefInput && !isZeroRef(baseRefInput)) {
121
- return baseRefInput
122
- }
123
-
124
- const eventBefore = process.env.GITHUB_EVENT_BEFORE
125
- if (eventBefore && !isZeroRef(eventBefore)) {
126
- return eventBefore
127
- }
128
-
129
- try {
130
- const parent = runGit(['rev-parse', `${headRef}^`])
131
- if (parent) {
132
- return parent
133
- }
134
- } catch (error) {
135
- // ignore and fall through
136
- }
137
-
138
- return headRef
139
- }
140
-
141
- /**
142
- * Checks if a specific path has changed between two commits.
143
- * @param {string} baseRef - Base commit ref
144
- * @param {string} headRef - Head commit ref
145
- * @param {string} targetPath - Path to check
146
- * @returns {boolean} True if path has changes
147
- */
148
- const pathChanged = (baseRef, headRef, targetPath) => {
149
- const result = spawnSync('git', ['diff', '--name-only', baseRef, headRef, '--', targetPath], { encoding: 'utf8' })
150
- if (result.error) {
151
- throw result.error
152
- }
153
-
154
- if (result.status !== 0) {
155
- throw new Error(result.stderr || `git diff exited with status ${result.status}`)
156
- }
157
-
158
- return result.stdout.trim().length > 0
159
- }
160
-
161
- /**
162
- * Main entry point.
163
- */
164
- const main = () => {
165
- const pathsInput = getInput('paths', { required: true })
166
- const baseRefInput = getInput('base-ref')
167
- const headRefInput = getInput('head-ref')
168
-
169
- const paths = parseList(pathsInput)
170
- if (paths.length === 0) {
171
- console.error('At least one valid path must be provided via the paths input')
172
- process.exit(1)
173
- }
174
-
175
- const headRef = resolveHeadRef(headRefInput)
176
- const baseRef = resolveBaseRef(baseRefInput, headRef)
177
-
178
- let changed = false
179
- for (const targetPath of paths) {
180
- if (pathChanged(baseRef, headRef, targetPath)) {
181
- changed = true
182
- break
183
- }
184
- }
185
-
186
- setOutput('changed', changed ? 'true' : 'false')
187
- }
188
-
189
- main().catch((error) => {
190
- console.error('check-path-changes action failed:', error)
191
- process.exit(1)
192
- })
@@ -1,38 +0,0 @@
1
- name: Detect skipped services
2
- description: Detects services with skip:true and outputs them for workflow use
3
- inputs:
4
- services-dir:
5
- description: Directory containing service configuration files
6
- required: false
7
- default: infra/services
8
- outputs:
9
- skipped-services:
10
- description: Comma-separated list of skipped service names
11
- value: ${{ steps.detect.outputs.skipped_services }}
12
- runs:
13
- using: composite
14
- steps:
15
- - name: Detect skipped services
16
- id: detect
17
- shell: bash
18
- env:
19
- SERVICES_DIR: ${{ inputs.services-dir }}
20
- run: |
21
- set -euo pipefail
22
-
23
- SKIPPED=""
24
- for file in "$SERVICES_DIR"/*.ts; do
25
- if [ -f "$file" ] && grep -q "skip:\s*true" "$file" 2>/dev/null; then
26
- SERVICE_NAME=$(grep -oP "name:\s*['\"]?\K[a-zA-Z0-9_-]+" "$file" | head -1)
27
- if [ -n "$SERVICE_NAME" ]; then
28
- if [ -z "$SKIPPED" ]; then
29
- SKIPPED="$SERVICE_NAME"
30
- else
31
- SKIPPED="$SKIPPED,$SERVICE_NAME"
32
- fi
33
- fi
34
- fi
35
- done
36
-
37
- echo "skipped_services=$SKIPPED" >> "$GITHUB_OUTPUT"
38
- echo "Detected skipped services: $SKIPPED"