@pixpilot/scaffoldfy-configs 0.41.0 → 0.42.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,12 @@
1
1
  # @pixpilot/scaffoldfy-configs
2
2
 
3
+ ## 0.42.0
4
+
5
+ ### Minor Changes
6
+
7
+ - add `--set <key=value>` option for preset prompt answers
8
+ - 0319f19: workspace-initializer: create multiple packages at once from a comma separated list of names
9
+
3
10
  ## 0.41.0
4
11
 
5
12
  ### Minor Changes
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.42.0",
5
5
  "author": "PixPilot <m.doaie@hotmail.com>",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -29,11 +29,11 @@
29
29
  "devDependencies": {
30
30
  "@types/node": "^22.18.11",
31
31
  "eslint": "^9.38.0",
32
+ "@internal/tsdown-config": "0.1.0",
32
33
  "@internal/eslint-config": "0.3.0",
34
+ "@internal/vitest-config": "0.1.0",
33
35
  "@internal/prettier-config": "0.0.1",
34
- "@internal/tsdown-config": "0.1.0",
35
- "@pixpilot/scaffoldfy": "0.55.1",
36
- "@internal/vitest-config": "0.1.0"
36
+ "@pixpilot/scaffoldfy": "0.56.0"
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
+ }