@pixpilot/scaffoldfy-configs 0.32.0 → 0.34.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,17 @@
1
1
  # @pixpilot/scaffoldfy-configs
2
2
 
3
+ ## 0.34.0
4
+
5
+ ### Minor Changes
6
+
7
+ - update ESLint configuration for test files
8
+
9
+ ## 0.33.0
10
+
11
+ ### Minor Changes
12
+
13
+ - update app templates and enhance setup script
14
+
3
15
  ## 0.32.0
4
16
 
5
17
  ### Minor Changes
package/eslint.config.mjs CHANGED
@@ -3,4 +3,13 @@ import baseConfig from '@internal/eslint-config/base';
3
3
  // Uncomment to use the internal ESLint config if available
4
4
  // /** @type {import('@internal/eslint-config').Config} */
5
5
  /** @type {import('typescript-eslint').Config} */
6
- export default await baseConfig();
6
+ export default [
7
+ ...(await baseConfig()),
8
+ {
9
+ files: ['test/**/*.test.ts'],
10
+ rules: {
11
+ 'dot-notation': 'off',
12
+ 'ts/dot-notation': 'off',
13
+ },
14
+ },
15
+ ];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pixpilot/scaffoldfy-configs",
3
3
  "type": "module",
4
- "version": "0.32.0",
4
+ "version": "0.34.0",
5
5
  "author": "PixPilot <m.doaie@hotmail.com>",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -26,11 +26,11 @@
26
26
  "devDependencies": {
27
27
  "@types/node": "^22.18.11",
28
28
  "eslint": "^9.38.0",
29
- "@internal/eslint-config": "0.3.0",
29
+ "@internal/prettier-config": "0.0.1",
30
30
  "@internal/tsdown-config": "0.1.0",
31
31
  "@internal/vitest-config": "0.1.0",
32
32
  "@pixpilot/scaffoldfy": "0.52.0",
33
- "@internal/prettier-config": "0.0.1"
33
+ "@internal/eslint-config": "0.3.0"
34
34
  },
35
35
  "prettier": "@internal/prettier-config",
36
36
  "publishConfig": {
@@ -0,0 +1,152 @@
1
+ import { execFileSync } 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-generator',
12
+ 'scripts',
13
+ 'setup-workspace.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
+ function createFakeGit(testDirectory: string): string {
24
+ const binDirectory = path.join(testDirectory, 'bin');
25
+ const fakeGitPath = path.join(binDirectory, 'fake-git.cjs');
26
+ const gitCommandPath = path.join(
27
+ binDirectory,
28
+ process.platform === 'win32' ? 'git.exe' : 'git',
29
+ );
30
+
31
+ fs.mkdirSync(binDirectory, { recursive: true });
32
+ fs.writeFileSync(
33
+ fakeGitPath,
34
+ [
35
+ "'use strict';",
36
+ "const fs = require('node:fs');",
37
+ "const Module = require('node:module');",
38
+ "const path = require('node:path');",
39
+ 'function applyGitOperation(command, args) {',
40
+ " if (command === 'clone') {",
41
+ ' const destination = args.at(-1);',
42
+ " if (destination === '.') {",
43
+ " fs.writeFileSync(path.join(process.cwd(), 'base-template.txt'), 'base');",
44
+ ' } else {',
45
+ ' fs.mkdirSync(destination, { recursive: true });',
46
+ ' }',
47
+ " } else if (command === 'sparse-checkout') {",
48
+ ' const sourceDirectory = args.at(-1);',
49
+ ' const templateDirectory = path.join(process.cwd(), sourceDirectory);',
50
+ ' fs.mkdirSync(templateDirectory, { recursive: true });',
51
+ " fs.writeFileSync(path.join(templateDirectory, 'template.txt'), sourceDirectory);",
52
+ '}',
53
+ '}',
54
+ "if (process.env.SCAFFOLDFY_FAKE_GIT_MODE === 'preload') {",
55
+ ' const resolveFilename = Module._resolveFilename;',
56
+ ' Module._resolveFilename = function resolveFakeGit(request, ...args) {',
57
+ ' const command = path.basename(request);',
58
+ " if (command === 'clone' || command === 'sparse-checkout') {",
59
+ ' applyGitOperation(command, process.argv.slice(2));',
60
+ " return path.join(__dirname, 'noop.cjs');",
61
+ ' }',
62
+ ' return resolveFilename.call(this, request, ...args);',
63
+ ' };',
64
+ '} else {',
65
+ ' const [command, ...args] = process.argv.slice(2);',
66
+ ' applyGitOperation(command, args);',
67
+ '}',
68
+ ].join('\n'),
69
+ );
70
+
71
+ if (process.platform === 'win32') {
72
+ fs.writeFileSync(path.join(binDirectory, 'noop.cjs'), "'use strict';\n");
73
+ fs.copyFileSync(process.execPath, gitCommandPath);
74
+ } else {
75
+ fs.writeFileSync(
76
+ gitCommandPath,
77
+ `#!${process.execPath}\nrequire('./fake-git.cjs');\n`,
78
+ );
79
+ fs.chmodSync(gitCommandPath, 0o755);
80
+ }
81
+
82
+ return binDirectory;
83
+ }
84
+
85
+ function runSetup(workspaceApps: string): string {
86
+ const testDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'scaffoldfy-workspace-'));
87
+ const workspaceDirectory = path.join(testDirectory, 'workspace');
88
+ temporaryDirectories.push(testDirectory);
89
+ fs.mkdirSync(workspaceDirectory);
90
+ const gitBinDirectory = createFakeGit(testDirectory);
91
+ const fakeGitPath = path.join(gitBinDirectory, 'fake-git.cjs');
92
+
93
+ execFileSync(process.execPath, [SCRIPT_PATH], {
94
+ cwd: workspaceDirectory,
95
+ stdio: 'pipe',
96
+ env: {
97
+ ...process.env,
98
+ PATH: `${gitBinDirectory}${path.delimiter}${process.env['PATH'] ?? ''}`,
99
+ ...(process.platform === 'win32' && {
100
+ NODE_OPTIONS: `--require=${fakeGitPath} ${process.env['NODE_OPTIONS'] ?? ''}`,
101
+ Path: `${gitBinDirectory}${path.delimiter}${process.env['Path'] ?? ''}`,
102
+ SCAFFOLDFY_FAKE_GIT_MODE: 'preload',
103
+ }),
104
+ WORKSPACE_APPS: workspaceApps,
105
+ },
106
+ });
107
+
108
+ return workspaceDirectory;
109
+ }
110
+
111
+ describe('workspace-generator setup script', () => {
112
+ it('should clone the base template when only Library is selected', () => {
113
+ const workspaceDirectory = runSetup('library');
114
+
115
+ expect(
116
+ fs.readFileSync(path.join(workspaceDirectory, 'base-template.txt'), 'utf8'),
117
+ ).toBe('base');
118
+ expect(fs.existsSync(path.join(workspaceDirectory, 'apps'))).toBe(false);
119
+ });
120
+
121
+ it('should add every selected app to the base workspace', () => {
122
+ const workspaceDirectory = runSetup(
123
+ 'library,nextjs-cloudflare,chrome-extension,expo',
124
+ );
125
+
126
+ expect(
127
+ fs.readFileSync(path.join(workspaceDirectory, 'base-template.txt'), 'utf8'),
128
+ ).toBe('base');
129
+ expect(
130
+ fs.readFileSync(
131
+ path.join(workspaceDirectory, 'apps', 'web', 'template.txt'),
132
+ 'utf8',
133
+ ),
134
+ ).toBe('apps/web');
135
+ expect(
136
+ fs.readFileSync(
137
+ path.join(workspaceDirectory, 'apps', 'chrome-extension', 'template.txt'),
138
+ 'utf8',
139
+ ),
140
+ ).toBe('apps/chrome-extension');
141
+ expect(
142
+ fs.readFileSync(
143
+ path.join(workspaceDirectory, 'apps', 'expo', 'template.txt'),
144
+ 'utf8',
145
+ ),
146
+ ).toBe('apps/expo');
147
+ });
148
+
149
+ it('should fail when no workspace app is selected', () => {
150
+ expect(() => runSetup('')).toThrow('Select at least one workspace app.');
151
+ });
152
+ });
@@ -124,13 +124,13 @@ describe('workspace-package-generator – add-dependencies script', () => {
124
124
  const initial = { name: 'test-pkg', dependencies: {} };
125
125
  const result = runAddDeps(initial, 'lodash axios');
126
126
 
127
- expect(result.dependencies).toMatchObject({ lodash: '*', axios: '*' });
127
+ expect(result['dependencies']).toMatchObject({ lodash: '*', axios: '*' });
128
128
  });
129
129
 
130
130
  it('should sort dependencies alphabetically', () => {
131
131
  const initial = { name: 'test-pkg', dependencies: {} };
132
132
  const result = runAddDeps(initial, 'zod axios chalk');
133
- const keys = Object.keys(result.dependencies as Record<string, string>);
133
+ const keys = Object.keys(result['dependencies'] as Record<string, string>);
134
134
 
135
135
  expect(keys).toEqual([...keys].sort());
136
136
  });
@@ -142,7 +142,7 @@ describe('workspace-package-generator – add-dependencies script', () => {
142
142
  devDependencies: { zod: '^3.0.0', axios: '^1.0.0', chalk: '^5.0.0' },
143
143
  };
144
144
  const result = runAddDeps(initial, 'vitest');
145
- const keys = Object.keys(result.devDependencies as Record<string, string>);
145
+ const keys = Object.keys(result['devDependencies'] as Record<string, string>);
146
146
 
147
147
  expect(keys).toEqual([...keys].sort());
148
148
  });
@@ -14,8 +14,8 @@
14
14
  "value": "library"
15
15
  },
16
16
  {
17
- "name": "Next.js",
18
- "value": "nextjs"
17
+ "name": "Next.js Cloudflare",
18
+ "value": "nextjs-cloudflare"
19
19
  },
20
20
  {
21
21
  "name": "Chrome Extension",
@@ -28,6 +28,17 @@
28
28
  ]
29
29
  }
30
30
  // {
31
+ // "id": "workspaceTools",
32
+ // "type": "checkbox",
33
+ // "message": "Select the packages to include:",
34
+ // "choices": [
35
+ // {
36
+ // "name": "storybook",
37
+ // "value": "storybook"
38
+ // }
39
+ // ]
40
+ // }
41
+ // {
31
42
  // "id": "nextjsVisualTest",
32
43
  // "type": "input",
33
44
  // "message": "Next.js visual-test value:",
@@ -36,24 +47,19 @@
36
47
  // "value": "workspaceApps.includes('nextjs')"
37
48
  // }
38
49
  // },
39
- // {
40
- // "id": "expoVisualTest",
41
- // "type": "input",
42
- // "message": "Expo visual-test value:",
43
- // "enabled": {
44
- // "type": "condition",
45
- // "value": "workspaceApps.includes('expo')"
46
- // }
47
- // }
48
50
  ],
49
51
  "tasks": [
50
52
  {
51
- "id": "clone-monorepo-template",
52
- "name": "Clone Monorepo Template",
53
- "description": "Clone the monorepo template",
54
- "type": "exec",
53
+ "id": "setup-workspace",
54
+ "name": "Set up workspace apps",
55
+ "description": "Clone the monorepo template and add each selected app",
56
+ "type": "exec-file",
55
57
  "config": {
56
- "command": "git clone https://github.com/pixpilot/pnpm-turbo-monorepo-template.git ."
58
+ "file": "./scripts/setup-workspace.cjs",
59
+ "runtime": "node",
60
+ "parameters": {
61
+ "WORKSPACE_APPS": "{{workspaceApps}}"
62
+ }
57
63
  }
58
64
  }
59
65
  ]
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { execFileSync } = require('node:child_process');
5
+ const fs = require('node:fs');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+ const process = require('node:process');
9
+
10
+ const BASE_TEMPLATE_REPOSITORY =
11
+ 'https://github.com/pixpilot/pnpm-turbo-monorepo-template.git';
12
+
13
+ const APP_TEMPLATES = {
14
+ 'nextjs-cloudflare': {
15
+ repository: 'https://github.com/ccpu/nextjs-cloudflare-monorepo-template.git',
16
+ sourceDirectory: 'apps/web',
17
+ destinationDirectory: 'apps/web',
18
+ },
19
+ 'chrome-extension': {
20
+ repository: 'https://github.com/ccpu/chrome-extension-monorepo-template.git',
21
+ sourceDirectory: 'apps/chrome-extension',
22
+ destinationDirectory: 'apps/chrome-extension',
23
+ },
24
+ expo: {
25
+ repository: 'https://github.com/ccpu/full-stack-starter.git',
26
+ sourceDirectory: 'apps/expo',
27
+ destinationDirectory: 'apps/expo',
28
+ },
29
+ };
30
+
31
+ function runGit(args, cwd) {
32
+ execFileSync('git', args, { cwd, stdio: 'inherit' });
33
+ }
34
+
35
+ function getSelectedApps() {
36
+ return new Set(
37
+ (process.env.WORKSPACE_APPS ?? '')
38
+ .split(',')
39
+ .map((app) => app.trim())
40
+ .filter(Boolean),
41
+ );
42
+ }
43
+
44
+ function ensureEmptyDirectory(directory) {
45
+ if (fs.readdirSync(directory).length > 0) {
46
+ throw new Error(`Workspace directory must be empty: ${directory}`);
47
+ }
48
+ }
49
+
50
+ function copyDirectoryContents(sourceDirectory, destinationDirectory) {
51
+ for (const entry of fs.readdirSync(sourceDirectory, { withFileTypes: true })) {
52
+ fs.cpSync(
53
+ path.join(sourceDirectory, entry.name),
54
+ path.join(destinationDirectory, entry.name),
55
+ { recursive: entry.isDirectory(), errorOnExist: true, force: false },
56
+ );
57
+ }
58
+ }
59
+
60
+ function addAppTemplate(template, workspaceDirectory) {
61
+ const destinationDirectory = path.join(
62
+ workspaceDirectory,
63
+ template.destinationDirectory,
64
+ );
65
+ fs.mkdirSync(destinationDirectory, { recursive: true });
66
+
67
+ if (fs.readdirSync(destinationDirectory).length > 0) {
68
+ throw new Error(`App destination already exists: ${destinationDirectory}`);
69
+ }
70
+
71
+ const temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'scaffoldfy-app-'));
72
+ const repositoryDirectory = path.join(temporaryDirectory, 'repository');
73
+
74
+ try {
75
+ runGit(
76
+ [
77
+ 'clone',
78
+ '--depth',
79
+ '1',
80
+ '--filter=blob:none',
81
+ '--sparse',
82
+ template.repository,
83
+ repositoryDirectory,
84
+ ],
85
+ workspaceDirectory,
86
+ );
87
+ runGit(['sparse-checkout', 'set', template.sourceDirectory], repositoryDirectory);
88
+
89
+ const sourceDirectory = path.join(repositoryDirectory, template.sourceDirectory);
90
+ if (!fs.existsSync(sourceDirectory)) {
91
+ throw new Error(
92
+ `Template directory "${template.sourceDirectory}" was not found in ${template.repository}`,
93
+ );
94
+ }
95
+
96
+ copyDirectoryContents(sourceDirectory, destinationDirectory);
97
+ } finally {
98
+ fs.rmSync(temporaryDirectory, { recursive: true, force: true });
99
+ }
100
+ }
101
+
102
+ function main() {
103
+ const workspaceDirectory = process.cwd();
104
+ const selectedApps = getSelectedApps();
105
+
106
+ if (selectedApps.size === 0) {
107
+ throw new Error('Select at least one workspace app.');
108
+ }
109
+
110
+ ensureEmptyDirectory(workspaceDirectory);
111
+ runGit(['clone', '--depth', '1', BASE_TEMPLATE_REPOSITORY, '.'], workspaceDirectory);
112
+
113
+ for (const app of selectedApps) {
114
+ const template = APP_TEMPLATES[app];
115
+ if (template) {
116
+ addAppTemplate(template, workspaceDirectory);
117
+ }
118
+ }
119
+ }
120
+
121
+ main();