@crossdelta/platform-sdk 0.22.0 → 0.22.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.
@@ -0,0 +1,370 @@
1
+ /**
2
+ * Generate Scope Matrix Action
3
+ *
4
+ * Discovers Docker-enabled scopes in a monorepo and generates a GitHub Actions
5
+ * matrix for building only the scopes that have changed between commits.
6
+ *
7
+ * A "scope" is a directory containing a Dockerfile. Scopes are discovered by
8
+ * scanning configured root directories (default: apps, services).
9
+ *
10
+ * @example
11
+ * // In a workflow:
12
+ * - uses: ./.github/actions/generate-scope-matrix
13
+ * with:
14
+ * scope-roots: apps,services
15
+ *
16
+ * @outputs scopes - JSON array of scope objects [{name, shortName, dir, run}]
17
+ * @outputs scopes_count - Number of scopes in the matrix
18
+ */
19
+
20
+ const { appendFileSync } = require('node:fs')
21
+ const { spawnSync } = require('node:child_process')
22
+ const { existsSync, readdirSync, readFileSync, statSync } = require('node:fs')
23
+ const { basename, join, relative } = require('node:path')
24
+
25
+ /** Default directories to scan for scopes (Turborepo convention) */
26
+ const defaultRoots = ['apps', 'services']
27
+
28
+ /** Keys in GitHub event payload that contain changed file paths */
29
+ const changedKeys = ['added', 'modified', 'removed']
30
+
31
+ /**
32
+ * Builds environment variable keys for GitHub Actions inputs.
33
+ * GitHub converts input names to uppercase and replaces special chars with underscores.
34
+ * @param {string} name - Input name (e.g., 'scope-roots')
35
+ * @returns {string[]} Possible environment variable keys
36
+ */
37
+ const buildInputKeys = (name) => {
38
+ const trimmed = name.trim()
39
+ const upper = trimmed.toUpperCase()
40
+ const normalized = upper.replace(/[^A-Z0-9]+/g, '_')
41
+ return Array.from(new Set([`INPUT_${upper}`, `INPUT_${normalized}`]))
42
+ }
43
+
44
+ /**
45
+ * Retrieves a GitHub Actions input value from environment variables.
46
+ * @param {string} name - Input name as defined in action.yml
47
+ * @param {Object} options - Options
48
+ * @param {string} [options.defaultValue=''] - Default value if input is not set
49
+ * @returns {string} The input value
50
+ */
51
+ const getInput = (name, { defaultValue = '' } = {}) => {
52
+ const keys = buildInputKeys(name)
53
+ const raw = keys.map((key) => process.env[key]).find((value) => typeof value === 'string')
54
+ const value = typeof raw === 'string' ? raw.trim() : defaultValue
55
+ return value
56
+ }
57
+
58
+ /**
59
+ * Sets a GitHub Actions output value.
60
+ * Writes to GITHUB_OUTPUT file or logs to console if not in Actions environment.
61
+ * @param {string} name - Output name
62
+ * @param {string|object} value - Output value (objects are JSON stringified)
63
+ */
64
+ const setOutput = (name, value) => {
65
+ const outputFile = process.env.GITHUB_OUTPUT
66
+ const stringValue = typeof value === 'string' ? value : JSON.stringify(value)
67
+
68
+ if (outputFile) {
69
+ appendFileSync(outputFile, `${name}=${stringValue}\n`)
70
+ } else {
71
+ console.log(`${name}=${stringValue}`)
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Loads root directories to scan for scopes.
77
+ * Parses the 'scope-roots' input or uses defaults (apps, services).
78
+ * @returns {string[]} Array of root directory paths
79
+ */
80
+ const loadRoots = () => {
81
+ const envRoots = getInput('scope-roots')
82
+
83
+ if (!envRoots) return defaultRoots
84
+
85
+ const parsed = envRoots
86
+ .split(/[^a-zA-Z0-9._-]+/)
87
+ .map((entry) => entry.trim())
88
+ .filter(Boolean)
89
+
90
+ return parsed.length > 0 ? parsed : defaultRoots
91
+ }
92
+
93
+ /**
94
+ * Loads short names that should be forced into the matrix regardless of changes.
95
+ * Useful for deploying specific services manually.
96
+ * @returns {string[]} Array of short names to force-include
97
+ */
98
+ const loadForcedShortNames = () => {
99
+ const envValue = getInput('force-scope-short-names')
100
+
101
+ if (!envValue) return []
102
+
103
+ return Array.from(
104
+ new Set(
105
+ envValue
106
+ .split(/[^a-zA-Z0-9._-]+/)
107
+ .map((entry) => entry.trim())
108
+ .filter(Boolean),
109
+ ),
110
+ )
111
+ }
112
+
113
+ /**
114
+ * Resolves the scope name from package.json or falls back to directory name.
115
+ * @param {string} entryPath - Path to the scope directory
116
+ * @param {string} fallback - Fallback name if package.json is not found
117
+ * @returns {string} The scope name
118
+ */
119
+ const resolveScopeName = (entryPath, fallback) => {
120
+ const pkgPath = join(entryPath, 'package.json')
121
+
122
+ if (!existsSync(pkgPath)) return fallback
123
+
124
+ try {
125
+ const raw = readFileSync(pkgPath, 'utf8')
126
+ const pkg = JSON.parse(raw)
127
+ if (typeof pkg.name === 'string' && pkg.name.trim().length > 0) {
128
+ return pkg.name
129
+ }
130
+ } catch (error) {
131
+ console.error(`Failed to read package.json for ${entryPath}:`, error)
132
+ process.exit(1)
133
+ }
134
+
135
+ return fallback
136
+ }
137
+
138
+ /**
139
+ * Processes a single directory entry and adds it to scopes if it contains a Dockerfile.
140
+ * @param {string} root - Root directory path
141
+ * @param {string} entry - Directory entry name
142
+ * @param {Map} scopes - Map to store discovered scopes
143
+ */
144
+ const processScopeEntry = (root, entry, scopes) => {
145
+ const entryPath = join(root, entry)
146
+
147
+ const isDirectory = statSync(entryPath).isDirectory()
148
+ if (!isDirectory) return
149
+
150
+ const dockerfilePath = join(entryPath, 'Dockerfile')
151
+ const hasDockerfile = existsSync(dockerfilePath)
152
+ if (!hasDockerfile) return
153
+
154
+ const scopeName = resolveScopeName(entryPath, entry)
155
+ const isDuplicateScope = scopes.has(scopeName)
156
+ if (isDuplicateScope) return
157
+
158
+ const relativeDir = relative(process.cwd(), entryPath).replace(/\\/g, '/')
159
+ scopes.set(scopeName, {
160
+ name: scopeName,
161
+ dir: relativeDir,
162
+ shortName: basename(relativeDir),
163
+ })
164
+ }
165
+
166
+ /**
167
+ * Discovers all Docker-enabled scopes in the given root directories.
168
+ * @param {string[]} roots - Array of root directory paths to scan
169
+ * @returns {Map<string, {name: string, dir: string, shortName: string}>} Map of discovered scopes
170
+ */
171
+ const discoverScopes = (roots) => {
172
+ const scopes = new Map()
173
+
174
+ for (const root of roots) {
175
+ let entries = []
176
+ try {
177
+ entries = readdirSync(root)
178
+ } catch (error) {
179
+ if (error.code === 'ENOENT') {
180
+ continue
181
+ }
182
+ throw error
183
+ }
184
+
185
+ for (const entry of entries) {
186
+ processScopeEntry(root, entry, scopes)
187
+ }
188
+ }
189
+
190
+ return scopes
191
+ }
192
+
193
+ /**
194
+ * Loads changed files from the GitHub event payload.
195
+ * @param {string} eventPath - Path to the GitHub event JSON file
196
+ * @returns {string[]} Array of changed file paths
197
+ */
198
+ const loadEventChangedFiles = (eventPath) => {
199
+ if (!eventPath || !existsSync(eventPath)) {
200
+ return []
201
+ }
202
+
203
+ try {
204
+ const payloadRaw = readFileSync(eventPath, 'utf8')
205
+ const payload = JSON.parse(payloadRaw)
206
+ const commits = Array.isArray(payload.commits) ? payload.commits : []
207
+ const files = new Set()
208
+
209
+ commits.forEach((commit) => {
210
+ changedKeys.forEach((key) => {
211
+ if (!Array.isArray(commit[key])) {
212
+ return
213
+ }
214
+ commit[key].forEach((file) => {
215
+ files.add(file.replace(/\\/g, '/'))
216
+ })
217
+ })
218
+ })
219
+
220
+ return Array.from(files)
221
+ } catch (error) {
222
+ console.warn('Unable to read changed files from event payload:', error)
223
+ return []
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Gets the before and head commit refs from GitHub environment variables.
229
+ * @returns {{beforeRef: string|undefined, headRef: string|undefined}} Commit references
230
+ */
231
+ const getCommitRefs = () => {
232
+ const beforeRef = process.env.GITHUB_EVENT_BEFORE
233
+ const headRef = process.env.GITHUB_SHA
234
+
235
+ return { beforeRef, headRef }
236
+ }
237
+
238
+ /**
239
+ * Runs git diff to check if a directory has changes between two commits.
240
+ * @param {string} base - Base commit reference
241
+ * @param {string} target - Target commit reference
242
+ * @param {string} dir - Directory to check for changes
243
+ * @returns {{ok: boolean, changed?: boolean}} Result with ok status and changed flag
244
+ */
245
+ const runGitDiff = (base, target, dir) => {
246
+ const result = spawnSync('git', ['diff', '--name-only', base, target, '--', dir], { encoding: 'utf8' })
247
+
248
+ if (result.error || result.status !== 0) {
249
+ return { ok: false }
250
+ }
251
+
252
+ return { ok: true, changed: result.stdout.trim().length > 0 }
253
+ }
254
+
255
+ /**
256
+ * Resolves the parent commit reference for a given commit.
257
+ * @param {string} ref - Commit reference
258
+ * @returns {string|undefined} Parent commit reference or undefined if not found
259
+ */
260
+ const resolveParentRef = (ref) => {
261
+ if (!ref) {
262
+ return undefined
263
+ }
264
+
265
+ const result = spawnSync('git', ['rev-parse', '--verify', `${ref}^`], { encoding: 'utf8' })
266
+ if (result.error || result.status !== 0) {
267
+ return undefined
268
+ }
269
+
270
+ return result.stdout.trim()
271
+ }
272
+
273
+ /**
274
+ * Checks if a directory has changes using git diff or event payload.
275
+ * Uses event payload first, then falls back to git diff.
276
+ * @param {string} dir - Directory to check
277
+ * @param {string[]} changedFilesFromEvent - Changed files from event payload
278
+ * @param {string} beforeRef - Before commit reference
279
+ * @param {string} headRef - Head commit reference
280
+ * @returns {boolean} True if directory has changes
281
+ */
282
+ const hasGitDiffChanges = (dir, changedFilesFromEvent, beforeRef, headRef) => {
283
+ const normalizedDir = dir.replace(/\\/g, '/')
284
+
285
+ if (changedFilesFromEvent.length > 0) {
286
+ return changedFilesFromEvent.some((file) => file === normalizedDir || file.startsWith(`${normalizedDir}/`))
287
+ }
288
+
289
+ const diffCandidates = []
290
+ if (beforeRef && headRef && !/^0+$/.test(beforeRef)) {
291
+ diffCandidates.push({ base: beforeRef, target: headRef })
292
+ }
293
+
294
+ const parentRef = resolveParentRef(headRef)
295
+ if (parentRef) {
296
+ diffCandidates.push({ base: parentRef, target: headRef })
297
+ }
298
+
299
+ for (const { base, target } of diffCandidates) {
300
+ const diffResult = runGitDiff(base, target, dir)
301
+ if (diffResult.ok) {
302
+ return diffResult.changed ?? true
303
+ }
304
+ }
305
+
306
+ return true
307
+ }
308
+
309
+ /**
310
+ * Wrapper to check if a scope directory has changes.
311
+ * @param {string} scopeDir - Scope directory path
312
+ * @param {string[]} changedFilesFromEvent - Changed files from event
313
+ * @param {string} beforeRef - Before commit ref
314
+ * @param {string} headRef - Head commit ref
315
+ * @returns {boolean} True if scope has changes
316
+ */
317
+ const scopeHasChanges = (scopeDir, changedFilesFromEvent, beforeRef, headRef) =>
318
+ hasGitDiffChanges(scopeDir, changedFilesFromEvent, beforeRef, headRef)
319
+
320
+ /**
321
+ * Checks if the force-all input is enabled.
322
+ * @returns {boolean} True if all scopes should be force-included
323
+ */
324
+ const isForceAll = () => {
325
+ const value = getInput('force-all')
326
+ return value === 'true' || value === '1' || value === 'yes'
327
+ }
328
+
329
+ /**
330
+ * Builds the final matrix array with change detection and forced scopes.
331
+ * @param {Map} scopeMap - Discovered scopes
332
+ * @param {string[]} changedFilesFromEvent - Changed files from event
333
+ * @param {string} beforeRef - Before commit ref
334
+ * @param {string} headRef - Head commit ref
335
+ * @param {string[]} forcedShortNames - Short names to force into matrix
336
+ * @returns {Array<{name: string, shortName: string, dir: string, run: boolean}>} Matrix entries
337
+ */
338
+ const buildMatrix = (scopeMap, changedFilesFromEvent, beforeRef, headRef, forcedShortNames) => {
339
+ if (isForceAll()) {
340
+ return Array.from(scopeMap.values())
341
+ .map((scope) => ({ ...scope, run: true }))
342
+ .sort((a, b) => a.name.localeCompare(b.name))
343
+ }
344
+
345
+ const enriched = Array.from(scopeMap.values())
346
+ .map((scope) => ({
347
+ ...scope,
348
+ run: scopeHasChanges(scope.dir, changedFilesFromEvent, beforeRef, headRef),
349
+ }))
350
+ .sort((a, b) => a.name.localeCompare(b.name))
351
+
352
+ const forcedSet = new Set(forcedShortNames || [])
353
+ const base = enriched.filter((scope) => scope.run)
354
+ const forced = enriched.filter(
355
+ (scope) => forcedSet.has(scope.shortName) && !base.some((entry) => entry.shortName === scope.shortName),
356
+ )
357
+
358
+ return [...base, ...forced]
359
+ }
360
+
361
+ // Main execution
362
+ const roots = loadRoots()
363
+ const scopeMap = discoverScopes(roots)
364
+ const changedFilesFromEvent = loadEventChangedFiles(process.env.GITHUB_EVENT_PATH)
365
+ const { beforeRef, headRef } = getCommitRefs()
366
+ const forcedShortNames = loadForcedShortNames()
367
+ const matrix = buildMatrix(scopeMap, changedFilesFromEvent, beforeRef, headRef, forcedShortNames)
368
+
369
+ setOutput('scopes', JSON.stringify(matrix))
370
+ setOutput('scopes_count', String(matrix.length))
@@ -0,0 +1,167 @@
1
+ name: Prepare Build Context
2
+ description: Flatten turbo prune output for Docker build
3
+
4
+ inputs:
5
+ scope-short-name:
6
+ description: Short name of the scope (e.g., orders, storefront)
7
+ required: true
8
+ scope-dir:
9
+ description: Original scope directory (e.g., services/orders, apps/storefront)
10
+ required: true
11
+ npm-token:
12
+ description: NPM token for @orderboss private registry — used to regenerate bun.lock
13
+ required: false
14
+ default: ''
15
+
16
+ outputs:
17
+ context-dir:
18
+ description: Path to the prepared build context
19
+ value: ${{ steps.prepare.outputs.context-dir }}
20
+ dockerfile:
21
+ description: Path to the Dockerfile relative to context-dir
22
+ value: ${{ steps.prepare.outputs.dockerfile }}
23
+
24
+ runs:
25
+ using: composite
26
+ steps:
27
+ - name: Prepare build context
28
+ id: prepare
29
+ shell: bash
30
+ run: |
31
+ set -euo pipefail
32
+
33
+ CONTEXT_DIR="out/${{ inputs.scope-short-name }}/full"
34
+ SCOPE_DIR="${{ inputs.scope-dir }}"
35
+
36
+ # Only flatten services — apps keep monorepo structure so
37
+ # relative paths (../../packages/ui) resolve correctly
38
+ if [[ "$SCOPE_DIR" == services/* ]]; then
39
+ if [ -d "$CONTEXT_DIR/$SCOPE_DIR" ]; then
40
+ cp -r "$CONTEXT_DIR/$SCOPE_DIR"/. "$CONTEXT_DIR/"
41
+ rm -rf "$CONTEXT_DIR/$SCOPE_DIR"
42
+ fi
43
+ rm -rf "$CONTEXT_DIR/apps" "$CONTEXT_DIR/services"
44
+ DOCKERFILE="Dockerfile"
45
+ else
46
+ DOCKERFILE="$SCOPE_DIR/Dockerfile"
47
+ fi
48
+
49
+ cd "$CONTEXT_DIR"
50
+
51
+ # Ensure bun.lock is present — turbo prune may omit it for certain workspace configs.
52
+ # Copy from workspace root as a baseline; bun install below will regenerate it anyway.
53
+ if [ ! -f "bun.lock" ]; then
54
+ echo "bun.lock not in prune output — copying from workspace root"
55
+ cp "$GITHUB_WORKSPACE/bun.lock" bun.lock
56
+ fi
57
+
58
+ # Replace workspace:* with npm versions for published packages
59
+ # so bun install fetches pre-built packages from registry (with dist/)
60
+ # Private packages stay as workspace deps (Bun resolves their .ts source)
61
+ if [ -d "packages" ]; then
62
+ for pkg_dir in packages/*/; do
63
+ [ -f "$pkg_dir/package.json" ] || continue
64
+
65
+ pkg_name=$(jq -r '.name' "$pkg_dir/package.json")
66
+ is_private=$(jq -r '.private // false' "$pkg_dir/package.json")
67
+ pkg_version=$(jq -r '.version // empty' "$pkg_dir/package.json")
68
+
69
+ if [ "$is_private" = "true" ] || [ -z "$pkg_version" ]; then
70
+ echo " keeping workspace dep: $pkg_name (private)"
71
+ continue
72
+ fi
73
+
74
+ # Use npm version if local version isn't published yet (race with publish workflow)
75
+ npm_version=$(npm view "$pkg_name" version 2>/dev/null || echo "")
76
+ if [ -n "$npm_version" ] && ! npm view "$pkg_name@$pkg_version" version >/dev/null 2>&1; then
77
+ echo " $pkg_name@$pkg_version not on npm yet, using ^$npm_version"
78
+ pkg_version="$npm_version"
79
+ fi
80
+
81
+ echo " replacing workspace:* → ^$pkg_version for $pkg_name"
82
+
83
+ # Replace in dependencies and devDependencies (root + scope package.json)
84
+ for pkg_file in package.json "$SCOPE_DIR/package.json"; do
85
+ [ -f "$pkg_file" ] || continue
86
+ for field in dependencies devDependencies; do
87
+ if jq -e ".${field}[\"${pkg_name}\"]" "$pkg_file" > /dev/null 2>&1; then
88
+ jq ".${field}[\"${pkg_name}\"] = \"^${pkg_version}\"" "$pkg_file" > "$pkg_file.tmp"
89
+ mv "$pkg_file.tmp" "$pkg_file"
90
+ fi
91
+ done
92
+ done
93
+
94
+ # Also update private packages that depend on this published package
95
+ for other_pkg in packages/*/; do
96
+ [ -f "$other_pkg/package.json" ] || continue
97
+ for field in dependencies devDependencies; do
98
+ if jq -e ".${field}[\"${pkg_name}\"]" "$other_pkg/package.json" > /dev/null 2>&1; then
99
+ jq ".${field}[\"${pkg_name}\"] = \"^${pkg_version}\"" "$other_pkg/package.json" > "$other_pkg/package.json.tmp"
100
+ mv "$other_pkg/package.json.tmp" "$other_pkg/package.json"
101
+ fi
102
+ done
103
+ done
104
+
105
+ # Remove published package dir (will be installed from npm)
106
+ rm -rf "$pkg_dir"
107
+ done
108
+ fi
109
+
110
+ # Set workspace config for remaining private packages, or remove it
111
+ if [ -d "packages" ] && [ -n "$(ls -A packages/ 2>/dev/null)" ]; then
112
+ if [[ "$SCOPE_DIR" == services/* ]]; then
113
+ jq '.workspaces = ["packages/*"]' package.json > package.json.tmp
114
+ else
115
+ jq ".workspaces = [\"packages/*\", \"$SCOPE_DIR\"]" package.json > package.json.tmp
116
+ fi
117
+ mv package.json.tmp package.json
118
+ else
119
+ rm -rf packages
120
+ jq 'del(.workspaces)' package.json > package.json.tmp
121
+ mv package.json.tmp package.json
122
+ fi
123
+
124
+ # Ensure packages/ dir exists (Dockerfiles COPY it even when empty)
125
+ mkdir -p packages
126
+
127
+ # Strip prepare script — it runs husky + turbo (dev-only, not needed in Docker)
128
+ if jq -e '.scripts.prepare' package.json > /dev/null 2>&1; then
129
+ jq 'del(.scripts.prepare)' package.json > package.json.tmp
130
+ mv package.json.tmp package.json
131
+ fi
132
+
133
+ # Regenerate bun.lock for the flattened workspace.
134
+ # package.json files were modified above (workspace:* → npm versions), so the
135
+ # original bun.lock is now inconsistent. We re-run bun install in the pruned
136
+ # context — bun's global cache is warm from the earlier setup-bun-install step,
137
+ # so this is fast and produces no new network traffic for already-cached packages.
138
+ #
139
+ # bun may delete bun.lock if it detects a workspace mismatch and then fails.
140
+ # Always back up first; restore if install leaves no valid lockfile behind.
141
+ # Service Dockerfiles install without --frozen-lockfile for this reason.
142
+ if [ -f "bun.lock" ]; then
143
+ echo "Regenerating bun.lock for modified workspace..."
144
+ cp bun.lock bun.lock.bak
145
+ set +e
146
+ NPM_TOKEN="${{ inputs.npm-token }}" bun install --no-progress 2>&1 | tee /tmp/bun-install.log; INSTALL_EXIT=${PIPESTATUS[0]}
147
+ set -e
148
+ if [ ! -f bun.lock ] || [ ! -s bun.lock ]; then
149
+ echo "::warning::bun install (exit $INSTALL_EXIT) produced no valid bun.lock — restoring original so Docker COPY does not fail"
150
+ mv bun.lock.bak bun.lock
151
+ elif [ "$INSTALL_EXIT" -ne 0 ]; then
152
+ echo "::warning::bun install exited with $INSTALL_EXIT but bun.lock exists — using regenerated lockfile"
153
+ rm -f bun.lock.bak
154
+ else
155
+ rm -f bun.lock.bak
156
+ echo "bun.lock regenerated"
157
+ fi
158
+ # Remove all node_modules created by the install; Docker installs fresh
159
+ find . -type d -name node_modules -prune -exec rm -rf {} + 2>/dev/null || true
160
+ fi
161
+
162
+ # Create .dockerignore in context root so COPY . . never re-introduces
163
+ # node_modules (bun symlinks) even when no root .dockerignore is in scope.
164
+ printf '%s\n' '**/node_modules' '**/.git' '**/.turbo' '.env' '.env.local' '*.log' > ".dockerignore"
165
+
166
+ echo "context-dir=$CONTEXT_DIR" >> "$GITHUB_OUTPUT"
167
+ echo "dockerfile=$DOCKERFILE" >> "$GITHUB_OUTPUT"
@@ -0,0 +1,57 @@
1
+ name: Setup Bun and install dependencies
2
+ description: Installs Bun, optionally restores cache, and runs Bun install commands.
3
+ author: {{githubOwner}}
4
+
5
+ inputs:
6
+ working-directory:
7
+ description: Directory to run the install command in.
8
+ default: .
9
+ install-command:
10
+ description: Command to install dependencies.
11
+ default: bun install --frozen-lockfile
12
+ enable-cache:
13
+ description: Whether to run the cache step.
14
+ default: 'false'
15
+ cache-key:
16
+ description: Cache key to use when caching is enabled.
17
+ default: ''
18
+ cache-restore-keys:
19
+ description: Restore keys for the cache step.
20
+ default: ''
21
+ cache-paths:
22
+ description: Newline-delimited paths to include in the cache.
23
+ default: |
24
+ ~/.bun
25
+ node_modules
26
+ bun-version-file:
27
+ description: Path to a file containing the Bun version (passed to setup-bun).
28
+ default: 'package.json'
29
+ npm-token:
30
+ description: NPM token for private registry access.
31
+ required: false
32
+ default: ''
33
+
34
+ runs:
35
+ using: composite
36
+ steps:
37
+ - name: Install Bun
38
+ uses: oven-sh/setup-bun@v2
39
+ with:
40
+ bun-version-file: $\{{ inputs.bun-version-file }}
41
+
42
+ - name: Cache dependencies
43
+ if: $\{{ inputs.enable-cache == 'true' && inputs.cache-key != '' }}
44
+ uses: actions/cache@v5
45
+ with:
46
+ path: $\{{ inputs.cache-paths }}
47
+ key: $\{{ inputs.cache-key }}
48
+ restore-keys: $\{{ inputs.cache-restore-keys }}
49
+
50
+ - name: Install dependencies
51
+ shell: bash
52
+ env:
53
+ NPM_TOKEN: $\{{ inputs.npm-token }}
54
+ run: |
55
+ set -euo pipefail
56
+ cd "$\{{ inputs.working-directory }}"
57
+ $\{{ inputs.install-command }}
@@ -0,0 +1,18 @@
1
+ version: 2
2
+ registries:
3
+ npm-github:
4
+ type: npm-registry
5
+ url: https://npm.pkg.github.com
6
+ token: ${{secrets.NPM_TOKEN}}
7
+ replaces-base: true
8
+
9
+ updates:
10
+ - package-ecosystem: "bun"
11
+ directory: "/"
12
+ schedule:
13
+ interval: "daily"
14
+ open-pull-requests-limit: 10
15
+ labels:
16
+ - "dependencies"
17
+ registries:
18
+ - npm-github