@pixpilot/scaffoldfy-configs 0.41.0 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @pixpilot/scaffoldfy-configs
2
2
 
3
+ ## 0.43.0
4
+
5
+ ### Minor Changes
6
+
7
+ - add GitHub App token release workflow
8
+
9
+ ## 0.42.0
10
+
11
+ ### Minor Changes
12
+
13
+ - add `--set <key=value>` option for preset prompt answers
14
+ - 0319f19: workspace-initializer: create multiple packages at once from a comma separated list of names
15
+
3
16
  ## 0.41.0
4
17
 
5
18
  ### Minor Changes
package/README.md CHANGED
@@ -24,6 +24,16 @@ Usage:
24
24
  npx @pixpilot/scaffoldfy@latest --config https://unpkg.com/@pixpilot/scaffoldfy-configs@latest/codeql-private-repo/scaffoldfy.json
25
25
  ```
26
26
 
27
+ ### github-action-release-app-token
28
+
29
+ Replaces the release workflow of a github-action-template clone with the GitHub App token variant (RELEASER_ID / RELEASER_PRIVATE_KEY) for pixpilot and ccpu repositories
30
+
31
+ Usage:
32
+
33
+ ```sh
34
+ npx @pixpilot/scaffoldfy@latest --config https://unpkg.com/@pixpilot/scaffoldfy-configs@latest/github-action-release-app-token/scaffoldfy.json
35
+ ```
36
+
27
37
  ### license-file
28
38
 
29
39
  Generate a LICENSE file with common open-source licenses
@@ -0,0 +1,50 @@
1
+ name: Release
2
+
3
+ on:
4
+ workflow_run:
5
+ workflows: [CI]
6
+ types:
7
+ - completed
8
+ branches:
9
+ - main
10
+
11
+ permissions:
12
+ contents: write
13
+
14
+ jobs:
15
+ release:
16
+ if: >-
17
+ ${{
18
+ github.event.workflow_run.conclusion == 'success' &&
19
+ github.event.workflow_run.event == 'push' &&
20
+ github.event.workflow_run.head_branch == 'main' &&
21
+ !contains(github.event.workflow_run.head_commit.message || '', '[skip ci]') &&
22
+ !contains(github.event.workflow_run.head_commit.message || '', '[ci skip]')
23
+ }}
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - name: Generate token
27
+ id: generate-token
28
+ uses: actions/create-github-app-token@v3
29
+ with:
30
+ client-id: ${{ secrets.RELEASER_ID }}
31
+ private-key: ${{ secrets.RELEASER_PRIVATE_KEY }}
32
+
33
+ - name: Checkout
34
+ uses: actions/checkout@v4
35
+ with:
36
+ fetch-depth: 0
37
+ persist-credentials: false
38
+ ref: ${{ github.event.workflow_run.head_sha }}
39
+ token: ${{ steps.generate-token.outputs.token }}
40
+
41
+ - name: Setup Project
42
+ uses: pixpilot/github-actions/setup-pnpm-project@v1
43
+
44
+ - name: Build dist
45
+ run: npm run bundle
46
+
47
+ - name: Semantic Release
48
+ uses: cycjimmy/semantic-release-action@v4
49
+ env:
50
+ GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
@@ -0,0 +1,31 @@
1
+ {
2
+ "$schema": "../node_modules/@pixpilot/scaffoldfy/schema/scaffoldfy.schema.json",
3
+ "name": "github-action-release-app-token",
4
+ "description": "Replaces the release workflow of a github-action-template clone with the GitHub App token variant (RELEASER_ID / RELEASER_PRIVATE_KEY) for pixpilot and ccpu repositories",
5
+ "dependencies": ["pixpilot-info"],
6
+ "variables": [
7
+ {
8
+ "id": "isGithubActionTemplate",
9
+ "value": {
10
+ "type": "exec",
11
+ "value": "node -e \"try{process.stdout.write(String(require(require('path').resolve('package.json')).name==='github-action-template'))}catch{process.stdout.write('false')}\""
12
+ }
13
+ }
14
+ ],
15
+ "tasks": [
16
+ {
17
+ "id": "github-action-release-app-token-workflow",
18
+ "name": "Use GitHub App token in release workflow",
19
+ "description": "Overwrites the release workflow to authenticate semantic-release with a GitHub App token",
20
+ "enabled": {
21
+ "type": "condition",
22
+ "value": "isGithubActionTemplate === true && pixpilot_project === true"
23
+ },
24
+ "type": "write",
25
+ "config": {
26
+ "templateFile": "release.yml",
27
+ "file": ".github/workflows/release.yml"
28
+ }
29
+ }
30
+ ]
31
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pixpilot/scaffoldfy-configs",
3
3
  "type": "module",
4
- "version": "0.41.0",
4
+ "version": "0.43.0",
5
5
  "author": "PixPilot <m.doaie@hotmail.com>",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -30,10 +30,10 @@
30
30
  "@types/node": "^22.18.11",
31
31
  "eslint": "^9.38.0",
32
32
  "@internal/eslint-config": "0.3.0",
33
- "@internal/prettier-config": "0.0.1",
33
+ "@internal/vitest-config": "0.1.0",
34
34
  "@internal/tsdown-config": "0.1.0",
35
- "@pixpilot/scaffoldfy": "0.55.1",
36
- "@internal/vitest-config": "0.1.0"
35
+ "@pixpilot/scaffoldfy": "0.56.0",
36
+ "@internal/prettier-config": "0.0.1"
37
37
  },
38
38
  "prettier": "@internal/prettier-config",
39
39
  "publishConfig": {
@@ -0,0 +1,101 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import process from 'node:process';
6
+ import { afterEach, describe, expect, it } from 'vitest';
7
+
8
+ const SCRIPT_PATH = path.join(
9
+ __dirname,
10
+ '..',
11
+ 'workspace-initializer',
12
+ 'scripts',
13
+ 'create-packages.cjs',
14
+ );
15
+ const temporaryDirectories: string[] = [];
16
+
17
+ afterEach(() => {
18
+ for (const directory of temporaryDirectories.splice(0)) {
19
+ fs.rmSync(directory, { force: true, recursive: true });
20
+ }
21
+ });
22
+
23
+ /**
24
+ * Put a fake `pnpm` on PATH that appends its arguments to a log file
25
+ */
26
+ function createFakePnpm(): { binDirectory: string; logPath: string } {
27
+ const testDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'create-packages-'));
28
+ temporaryDirectories.push(testDirectory);
29
+
30
+ const binDirectory = path.join(testDirectory, 'bin');
31
+ const logPath = path.join(testDirectory, 'calls.log');
32
+ const fakePnpmScript = path.join(binDirectory, 'fake-pnpm.cjs');
33
+ fs.mkdirSync(binDirectory, { recursive: true });
34
+ fs.writeFileSync(
35
+ fakePnpmScript,
36
+ `require('node:fs').appendFileSync(${JSON.stringify(logPath)}, process.argv.slice(2).join(' ') + '\\n');`,
37
+ );
38
+
39
+ if (process.platform === 'win32') {
40
+ fs.writeFileSync(
41
+ path.join(binDirectory, 'pnpm.cmd'),
42
+ `@"${process.execPath}" "${fakePnpmScript}" %*\r\n`,
43
+ );
44
+ } else {
45
+ const pnpmPath = path.join(binDirectory, 'pnpm');
46
+ fs.writeFileSync(
47
+ pnpmPath,
48
+ `#!/bin/sh\n"${process.execPath}" "${fakePnpmScript}" "$@"\n`,
49
+ );
50
+ fs.chmodSync(pnpmPath, 0o755);
51
+ }
52
+
53
+ return { binDirectory, logPath };
54
+ }
55
+
56
+ function runScript(packageNames: string, binDirectory: string) {
57
+ const pathKey =
58
+ Object.keys(process.env).find((key) => key.toUpperCase() === 'PATH') ?? 'PATH';
59
+ return spawnSync(process.execPath, [SCRIPT_PATH], {
60
+ encoding: 'utf8',
61
+ env: {
62
+ ...process.env,
63
+ [pathKey]: `${binDirectory}${path.delimiter}${process.env[pathKey] ?? ''}`,
64
+ PACKAGE_NAMES: packageNames,
65
+ },
66
+ });
67
+ }
68
+
69
+ describe('workspace-initializer create-packages script', () => {
70
+ it('runs the package generator once per comma separated name', () => {
71
+ const { binDirectory, logPath } = createFakePnpm();
72
+
73
+ const result = runScript(' pkg-a, pkg-b ,,@scope/pkg-c ', binDirectory);
74
+
75
+ expect(result.status).toBe(0);
76
+ expect(fs.readFileSync(logPath, 'utf8').trim().split(/\r?\n/u)).toEqual([
77
+ 'run gen:package --set packageBaseName=pkg-a',
78
+ 'run gen:package --set packageBaseName=pkg-b',
79
+ 'run gen:package --set packageBaseName=@scope/pkg-c',
80
+ ]);
81
+ });
82
+
83
+ it('skips when no names are given', () => {
84
+ const { binDirectory, logPath } = createFakePnpm();
85
+
86
+ const result = runScript(' , ', binDirectory);
87
+
88
+ expect(result.status).toBe(0);
89
+ expect(fs.existsSync(logPath)).toBe(false);
90
+ });
91
+
92
+ it('rejects names with shell characters', () => {
93
+ const { binDirectory, logPath } = createFakePnpm();
94
+
95
+ const result = runScript('pkg-a, bad;rm -rf', binDirectory);
96
+
97
+ expect(result.status).toBe(1);
98
+ expect(result.stderr).toContain('Invalid package name(s): bad;rm -rf');
99
+ expect(fs.existsSync(logPath)).toBe(false);
100
+ });
101
+ });
@@ -22,8 +22,18 @@
22
22
  {
23
23
  "id": "createFirstPackagePrompt",
24
24
  "type": "confirm",
25
- "message": "Create the first package now?",
25
+ "message": "Create packages now?",
26
26
  "default": false
27
+ },
28
+ {
29
+ "id": "packageNames",
30
+ "type": "input",
31
+ "message": "Enter package names (comma separated, e.g. my-package, my-other-package)",
32
+ "required": true,
33
+ "enabled": {
34
+ "type": "condition",
35
+ "value": "createFirstPackagePrompt === true"
36
+ }
27
37
  }
28
38
  ],
29
39
  "tasks": [
@@ -88,12 +98,19 @@
88
98
  },
89
99
  {
90
100
  "id": "create-first-package",
91
- "name": "Create first package",
92
- "description": "Generate the first package in the monorepo using the package generator",
93
- "type": "exec",
101
+ "name": "Create packages",
102
+ "description": "Generate each requested package in the monorepo using the package generator",
103
+ "type": "exec-file",
104
+ "enabled": {
105
+ "type": "condition",
106
+ "value": "createFirstPackagePrompt === true"
107
+ },
94
108
  "config": {
95
- "command": "pnpm run gen:package",
96
- "condition": "createFirstPackagePrompt"
109
+ "file": "./scripts/create-packages.cjs",
110
+ "runtime": "node",
111
+ "parameters": {
112
+ "PACKAGE_NAMES": "{{packageNames}}"
113
+ }
97
114
  }
98
115
  },
99
116
  {
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable node/prefer-global/process */
3
+ /**
4
+ * Runs the workspace package generator once per package name, pre-filling
5
+ * the package name prompt so only the remaining prompts are asked.
6
+ *
7
+ * Usage:
8
+ * PACKAGE_NAMES="pkg-a, pkg-b" node create-packages.cjs
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const { spawnSync } = require('node:child_process');
14
+
15
+ const SAFE_NAME = /^[\w.@/-]+$/u;
16
+
17
+ const names = (process.env.PACKAGE_NAMES || '')
18
+ .split(',')
19
+ .map((name) => name.trim())
20
+ .filter(Boolean);
21
+
22
+ if (names.length === 0) {
23
+ console.log('No package names provided, skipping package creation.');
24
+ process.exit(0);
25
+ }
26
+
27
+ const invalid = names.filter((name) => !SAFE_NAME.test(name));
28
+ if (invalid.length > 0) {
29
+ console.error(`Invalid package name(s): ${invalid.join(', ')}`);
30
+ process.exit(1);
31
+ }
32
+
33
+ for (const [index, name] of names.entries()) {
34
+ console.log(`\nCreating package ${index + 1}/${names.length}: ${name}\n`);
35
+
36
+ const result = spawnSync(
37
+ 'pnpm',
38
+ ['run', 'gen:package', '--set', `packageBaseName=${name}`],
39
+ { stdio: 'inherit', shell: true },
40
+ );
41
+
42
+ if (result.status !== 0) {
43
+ console.error(`Failed to create package: ${name}`);
44
+ process.exit(result.status ?? 1);
45
+ }
46
+ }