@pixpilot/scaffoldfy-configs 0.47.0 → 0.48.1

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.48.1
4
+
5
+ ### Patch Changes
6
+
7
+ - adds id-token
8
+
9
+ ## 0.48.0
10
+
11
+ ### Minor Changes
12
+
13
+ - add support for pre-set answers in configuration
14
+
3
15
  ## 0.47.0
4
16
 
5
17
  ### Minor Changes
package/README.md CHANGED
@@ -134,16 +134,6 @@ Usage:
134
134
  npx @pixpilot/scaffoldfy@latest --config https://unpkg.com/@pixpilot/scaffoldfy-configs@latest/update-root-package-json/scaffoldfy.json
135
135
  ```
136
136
 
137
- ### workspace-generator
138
-
139
- Generate workspace with apps and packages
140
-
141
- Usage:
142
-
143
- ```sh
144
- npx @pixpilot/scaffoldfy@latest --config https://unpkg.com/@pixpilot/scaffoldfy-configs@latest/workspace-generator/scaffoldfy.jsonc
145
- ```
146
-
147
137
  ### workspace-initializer
148
138
 
149
139
  Initial setup for a pnpm + Turbo monorepo template, including project info, license, and initial package generation.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pixpilot/scaffoldfy-configs",
3
3
  "type": "module",
4
- "version": "0.47.0",
4
+ "version": "0.48.1",
5
5
  "author": "PixPilot <m.doaie@hotmail.com>",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -29,10 +29,10 @@
29
29
  "devDependencies": {
30
30
  "@types/node": "^22.18.11",
31
31
  "eslint": "^9.38.0",
32
- "@internal/vitest-config": "0.1.0",
33
32
  "@internal/eslint-config": "0.3.0",
34
- "@internal/tsdown-config": "0.1.0",
35
33
  "@internal/prettier-config": "0.0.1",
34
+ "@internal/tsdown-config": "0.1.0",
35
+ "@internal/vitest-config": "0.1.0",
36
36
  "@pixpilot/scaffoldfy": "0.58.0"
37
37
  },
38
38
  "prettier": "@internal/prettier-config",
@@ -21,9 +21,13 @@ jobs:
21
21
  permissions:
22
22
  contents: write
23
23
  pull-requests: write
24
- # Publishing targets GitHub Packages (publishConfig.registry in each package.json),
25
- # which authenticates with a token rather than npm OIDC trusted publishing.
24
+ # Packages publish to two registries (publishConfig.registry in each package.json):
25
+ # @pixpilot-private/* to GitHub Packages, authenticated with REGISTRY_TOKEN.
26
26
  packages: write
27
+ # @pixpilot/* to npmjs via npm trusted publishing (OIDC). npm exchanges the
28
+ # OIDC token per package at publish time and falls back to REGISTRY_TOKEN
29
+ # for GitHub Packages, where the exchange is unsupported.
30
+ id-token: write
27
31
  outputs:
28
32
  published: ${{ steps.release.outputs.published }}
29
33
  published-versions: ${{ steps.release.outputs.published-versions }}
@@ -60,10 +64,11 @@ jobs:
60
64
  with:
61
65
  GITHUB_TOKEN: ${{ steps.app_token.outputs.token }}
62
66
  # GitHub Packages auth. Must be a token with write:packages on the
63
- # package scope; npm OIDC trusted publishing is npmjs-only.
64
- # https://github.com/pixpilot-private
67
+ # package scope. npmjs packages authenticate via OIDC (id-token above),
68
+ # not this token. https://github.com/pixpilot-private
65
69
  REGISTRY_TOKEN: ${{ secrets.PIXPILOT_PRIVATE_REGISTRY_TOKEN }}
66
- # npm provenance attestation is not supported by npm.pkg.github.com.
70
+ # npm provenance is unsupported on npm.pkg.github.com and for packages
71
+ # published from a private repository.
67
72
  provenance: 'false'
68
73
 
69
74
  notify:
@@ -1,236 +0,0 @@
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
- const pnpmCommandPath = path.join(
31
- binDirectory,
32
- process.platform === 'win32' ? 'pnpm.exe' : 'pnpm',
33
- );
34
-
35
- fs.mkdirSync(binDirectory, { recursive: true });
36
- fs.writeFileSync(
37
- fakeGitPath,
38
- [
39
- "'use strict';",
40
- "const fs = require('node:fs');",
41
- "const Module = require('node:module');",
42
- "const path = require('node:path');",
43
- 'function applyGitOperation(command, args) {',
44
- " if (command === 'clone') {",
45
- ' const destination = args.at(-1);',
46
- " if (destination === '.') {",
47
- " fs.writeFileSync(path.join(process.cwd(), 'base-template.txt'), 'base');",
48
- " fs.writeFileSync(path.join(process.cwd(), 'package.json'), JSON.stringify({ scripts: { lint: 'base-lint' }, devDependencies: { 'base-dependency': '1.0.0' } }));",
49
- " fs.writeFileSync(path.join(process.cwd(), 'pnpm-workspace.yaml'), \"packages:\\n - 'packages/**'\\n - 'tooling/**'\\ncatalogs:\\n dev:\\n tsdown: ^1.0.0\\n typescript-eslint: ^1.0.0\\n\");",
50
- ' } else {',
51
- ' fs.mkdirSync(destination, { recursive: true });',
52
- ' }',
53
- " } else if (command === 'pnpm' || command === 'install') {",
54
- " fs.writeFileSync(path.join(process.cwd(), 'pnpm-lock.yaml'), 'lock');",
55
- " } else if (command === 'sparse-checkout') {",
56
- " const sourceDirectories = args.filter((arg) => !arg.startsWith('-') && arg !== 'set');",
57
- ' for (const sourceDirectory of sourceDirectories) {',
58
- " if (sourceDirectory === 'package.json') {",
59
- " fs.writeFileSync(path.join(process.cwd(), sourceDirectory), JSON.stringify({ scripts: { 'web:dev': 'source-web-dev', 'web:build': 'source-web-build', dev: 'source-dev', native: 'source-native', 'expo:clean:install': 'source-expo-clean-install', 'expo:clean:run': 'source-expo-clean-run', 'android:dev': 'source-android-dev', 'android:clean:dev': 'source-android-clean-dev', 'ios:dev': 'source-ios-dev', 'ios:clean:dev': 'source-ios-clean-dev' }, devDependencies: { 'template-dependency': '1.0.0' } }));",
60
- " } else if (sourceDirectory === 'pnpm-workspace.yaml') {",
61
- ' fs.writeFileSync(path.join(process.cwd(), sourceDirectory), "packages:\\n - apps/*\\n - packages/*\\n - tooling/*\\ncatalogs:\\n dev:\\n template-dependency: ^1.0.0\\n \\"typescript-eslint\\": ^2.0.0\\n");',
62
- " } else if (sourceDirectory === '.github/workflows') {",
63
- ' fs.mkdirSync(path.join(process.cwd(), sourceDirectory), { recursive: true });',
64
- " fs.writeFileSync(path.join(process.cwd(), sourceDirectory, 'ci.yml'), 'ci');",
65
- ' } else {',
66
- ' const templateDirectory = path.join(process.cwd(), sourceDirectory);',
67
- ' fs.mkdirSync(templateDirectory, { recursive: true });',
68
- " fs.writeFileSync(path.join(templateDirectory, 'template.txt'), sourceDirectory);",
69
- ' }',
70
- ' }',
71
- '}',
72
- '}',
73
- "if (process.env.SCAFFOLDFY_FAKE_GIT_MODE === 'preload') {",
74
- ' const resolveFilename = Module._resolveFilename;',
75
- ' Module._resolveFilename = function resolveFakeGit(request, ...args) {',
76
- ' const command = path.basename(request);',
77
- " if (command === 'clone' || command === 'install' || command === 'pnpm' || command === 'sparse-checkout') {",
78
- " applyGitOperation(command === 'install' ? 'pnpm' : command, process.argv.slice(2));",
79
- " return path.join(__dirname, 'noop.cjs');",
80
- ' }',
81
- ' return resolveFilename.call(this, request, ...args);',
82
- ' };',
83
- '} else {',
84
- ' const [command, ...args] = process.argv.slice(2);',
85
- ' applyGitOperation(command, args);',
86
- '}',
87
- ].join('\n'),
88
- );
89
-
90
- if (process.platform === 'win32') {
91
- fs.writeFileSync(path.join(binDirectory, 'noop.cjs'), "'use strict';\n");
92
- fs.copyFileSync(process.execPath, gitCommandPath);
93
- fs.copyFileSync(process.execPath, pnpmCommandPath);
94
- } else {
95
- fs.writeFileSync(
96
- gitCommandPath,
97
- `#!${process.execPath}\nrequire('./fake-git.cjs');\n`,
98
- );
99
- fs.chmodSync(gitCommandPath, 0o755);
100
- fs.writeFileSync(
101
- pnpmCommandPath,
102
- `#!${process.execPath}\nrequire('./fake-git.cjs');\n`,
103
- );
104
- fs.chmodSync(pnpmCommandPath, 0o755);
105
- }
106
-
107
- return binDirectory;
108
- }
109
-
110
- function runSetup(workspaceApps: string): string {
111
- const testDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'scaffoldfy-workspace-'));
112
- const workspaceDirectory = path.join(testDirectory, 'workspace');
113
- temporaryDirectories.push(testDirectory);
114
- fs.mkdirSync(workspaceDirectory);
115
- const gitBinDirectory = createFakeGit(testDirectory);
116
- const fakeGitPath = path.join(gitBinDirectory, 'fake-git.cjs');
117
-
118
- execFileSync(process.execPath, [SCRIPT_PATH], {
119
- cwd: workspaceDirectory,
120
- stdio: 'pipe',
121
- env: {
122
- ...process.env,
123
- PATH: `${gitBinDirectory}${path.delimiter}${process.env['PATH'] ?? ''}`,
124
- ...(process.platform === 'win32' && {
125
- NODE_OPTIONS: `--require=${fakeGitPath} ${process.env['NODE_OPTIONS'] ?? ''}`,
126
- Path: `${gitBinDirectory}${path.delimiter}${process.env['Path'] ?? ''}`,
127
- SCAFFOLDFY_FAKE_GIT_MODE: 'preload',
128
- }),
129
- WORKSPACE_APPS: workspaceApps,
130
- },
131
- });
132
-
133
- return workspaceDirectory;
134
- }
135
-
136
- describe('workspace-generator setup script', () => {
137
- it('should clone the base template when only Library is selected', () => {
138
- const workspaceDirectory = runSetup('library');
139
-
140
- expect(
141
- fs.readFileSync(path.join(workspaceDirectory, 'base-template.txt'), 'utf8'),
142
- ).toBe('base');
143
- expect(fs.existsSync(path.join(workspaceDirectory, 'apps'))).toBe(false);
144
- });
145
-
146
- it('should add the selected app and its required workspace files', () => {
147
- const workspaceDirectory = runSetup('nextjs-cloudflare');
148
-
149
- expect(
150
- fs.readFileSync(path.join(workspaceDirectory, 'base-template.txt'), 'utf8'),
151
- ).toBe('base');
152
- expect(
153
- fs.readFileSync(
154
- path.join(workspaceDirectory, 'apps', 'web', 'template.txt'),
155
- 'utf8',
156
- ),
157
- ).toBe('apps/web');
158
- expect(
159
- fs.readFileSync(path.join(workspaceDirectory, 'packages', 'template.txt'), 'utf8'),
160
- ).toBe('packages');
161
- expect(
162
- fs.readFileSync(path.join(workspaceDirectory, 'tooling', 'template.txt'), 'utf8'),
163
- ).toBe('tooling');
164
- expect(
165
- fs.readFileSync(
166
- path.join(workspaceDirectory, '.github', 'workflows', 'ci.yml'),
167
- 'utf8',
168
- ),
169
- ).toBe('ci');
170
-
171
- const packageJson = JSON.parse(
172
- fs.readFileSync(path.join(workspaceDirectory, 'package.json'), 'utf8'),
173
- );
174
- expect(packageJson.scripts).toMatchObject({
175
- lint: 'base-lint',
176
- 'web:dev': 'source-web-dev',
177
- });
178
- expect(packageJson.devDependencies).toMatchObject({
179
- 'base-dependency': '1.0.0',
180
- 'template-dependency': '1.0.0',
181
- });
182
- expect(
183
- fs.readFileSync(path.join(workspaceDirectory, 'pnpm-workspace.yaml'), 'utf8'),
184
- ).toContain(' - apps/*');
185
- expect(
186
- fs.readFileSync(path.join(workspaceDirectory, 'pnpm-workspace.yaml'), 'utf8'),
187
- ).not.toContain('apps/**');
188
- expect(
189
- fs.readFileSync(path.join(workspaceDirectory, 'pnpm-workspace.yaml'), 'utf8'),
190
- ).toContain(' tsdown: ^1.0.0');
191
- expect(
192
- fs.readFileSync(path.join(workspaceDirectory, 'pnpm-workspace.yaml'), 'utf8'),
193
- ).toContain(' template-dependency: ^1.0.0');
194
- expect(fs.readFileSync(path.join(workspaceDirectory, 'pnpm-lock.yaml'), 'utf8')).toBe(
195
- 'lock',
196
- );
197
- });
198
-
199
- it('should add every selected app to the base workspace', () => {
200
- const workspaceDirectory = runSetup(
201
- 'library,nextjs-cloudflare,chrome-extension,expo',
202
- );
203
-
204
- expect(
205
- fs.readFileSync(
206
- path.join(workspaceDirectory, 'apps', 'web', 'template.txt'),
207
- 'utf8',
208
- ),
209
- ).toBe('apps/web');
210
- expect(
211
- fs.readFileSync(
212
- path.join(workspaceDirectory, 'apps', 'chrome-extension', 'template.txt'),
213
- 'utf8',
214
- ),
215
- ).toBe('apps/chrome-extension');
216
- expect(
217
- fs.readFileSync(
218
- path.join(workspaceDirectory, 'apps', 'expo', 'template.txt'),
219
- 'utf8',
220
- ),
221
- ).toBe('apps/expo');
222
-
223
- const packageJson = JSON.parse(
224
- fs.readFileSync(path.join(workspaceDirectory, 'package.json'), 'utf8'),
225
- );
226
- expect(packageJson.scripts).toMatchObject({
227
- dev: 'source-dev',
228
- native: 'source-native',
229
- 'web:dev': 'source-web-dev',
230
- });
231
- });
232
-
233
- it('should fail when no workspace app is selected', () => {
234
- expect(() => runSetup('')).toThrow('Select at least one workspace app.');
235
- });
236
- });
@@ -1,66 +0,0 @@
1
- {
2
- "$schema": "../node_modules/@pixpilot/scaffoldfy/schema/scaffoldfy.schema.json",
3
- // "extends": ["../workspace-initializer/scaffoldfy.json"],
4
- "name": "pnpm-turbo-monorepo-template-setup",
5
- "description": "Generate workspace with apps and packages",
6
- "prompts": [
7
- {
8
- "id": "workspaceApps",
9
- "type": "checkbox",
10
- "message": "Select the apps to include:",
11
- "choices": [
12
- {
13
- "name": "Library",
14
- "value": "library"
15
- },
16
- {
17
- "name": "Next.js Cloudflare",
18
- "value": "nextjs-cloudflare"
19
- },
20
- {
21
- "name": "Chrome Extension",
22
- "value": "chrome-extension"
23
- },
24
- {
25
- "name": "Expo",
26
- "value": "expo"
27
- }
28
- ]
29
- }
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
- // {
42
- // "id": "nextjsVisualTest",
43
- // "type": "input",
44
- // "message": "Next.js visual-test value:",
45
- // "enabled": {
46
- // "type": "condition",
47
- // "value": "workspaceApps.includes('nextjs')"
48
- // }
49
- // },
50
- ],
51
- "tasks": [
52
- {
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",
57
- "config": {
58
- "file": "./scripts/setup-workspace.cjs",
59
- "runtime": "node",
60
- "parameters": {
61
- "WORKSPACE_APPS": "{{workspaceApps}}"
62
- }
63
- }
64
- }
65
- ]
66
- }
@@ -1,411 +0,0 @@
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
- rootScripts: ['web:dev', 'web:build'],
19
- },
20
- 'chrome-extension': {
21
- repository: 'https://github.com/ccpu/chrome-extension-monorepo-template.git',
22
- sourceDirectory: 'apps/chrome-extension',
23
- destinationDirectory: 'apps/chrome-extension',
24
- rootScripts: ['dev'],
25
- },
26
- expo: {
27
- repository: 'https://github.com/ccpu/full-stack-starter.git',
28
- sourceDirectory: 'apps/expo',
29
- destinationDirectory: 'apps/expo',
30
- rootScripts: [
31
- 'native',
32
- 'expo:clean:install',
33
- 'expo:clean:run',
34
- 'android:dev',
35
- 'android:clean:dev',
36
- 'ios:dev',
37
- 'ios:clean:dev',
38
- ],
39
- },
40
- };
41
-
42
- const TEMPLATE_SOURCE_PATHS = [
43
- 'package.json',
44
- 'pnpm-workspace.yaml',
45
- 'packages',
46
- 'tooling',
47
- '.github/workflows',
48
- ];
49
- const JSON_INDENTATION = 2;
50
- const LINE_BREAK_PATTERN = /\r?\n/u;
51
- const TOP_LEVEL_SECTION_PATTERN = /^[^\s#][^:]*:/u;
52
- const INDENTED_LIST_ENTRY_PATTERN = /^ {2}- /u;
53
-
54
- function runGit(args, cwd) {
55
- execFileSync('git', args, { cwd, stdio: 'inherit' });
56
- }
57
-
58
- function refreshLockfile(cwd) {
59
- execFileSync('pnpm', ['install', '--lockfile-only', '--ignore-scripts'], {
60
- cwd,
61
- stdio: 'inherit',
62
- });
63
- }
64
-
65
- function getSelectedApps() {
66
- return new Set(
67
- (process.env.WORKSPACE_APPS ?? '')
68
- .split(',')
69
- .map((app) => app.trim())
70
- .filter(Boolean),
71
- );
72
- }
73
-
74
- function ensureEmptyDirectory(directory) {
75
- if (fs.readdirSync(directory).length > 0) {
76
- throw new Error(`Workspace directory must be empty: ${directory}`);
77
- }
78
- }
79
-
80
- function copyDirectoryContents(sourceDirectory, destinationDirectory) {
81
- for (const entry of fs.readdirSync(sourceDirectory, { withFileTypes: true })) {
82
- fs.cpSync(
83
- path.join(sourceDirectory, entry.name),
84
- path.join(destinationDirectory, entry.name),
85
- { recursive: entry.isDirectory(), errorOnExist: true, force: false },
86
- );
87
- }
88
- }
89
-
90
- function overlayDirectoryContents(sourceDirectory, destinationDirectory) {
91
- if (!fs.existsSync(sourceDirectory)) {
92
- return;
93
- }
94
-
95
- fs.mkdirSync(destinationDirectory, { recursive: true });
96
-
97
- for (const entry of fs.readdirSync(sourceDirectory, { withFileTypes: true })) {
98
- fs.cpSync(
99
- path.join(sourceDirectory, entry.name),
100
- path.join(destinationDirectory, entry.name),
101
- { recursive: entry.isDirectory(), force: true },
102
- );
103
- }
104
- }
105
-
106
- function mergeDependencySections(basePackage, templatePackage) {
107
- return [
108
- 'dependencies',
109
- 'devDependencies',
110
- 'optionalDependencies',
111
- 'peerDependencies',
112
- ].reduce(
113
- (mergedPackage, section) =>
114
- templatePackage[section]
115
- ? {
116
- ...mergedPackage,
117
- [section]: {
118
- ...templatePackage[section],
119
- ...mergedPackage[section],
120
- },
121
- }
122
- : mergedPackage,
123
- basePackage,
124
- );
125
- }
126
-
127
- function mergeRootPackageManifest(template, repositoryDirectory, workspaceDirectory) {
128
- const packageJsonPath = path.join(workspaceDirectory, 'package.json');
129
- const templatePackageJsonPath = path.join(repositoryDirectory, 'package.json');
130
- const basePackage = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
131
- const templatePackage = JSON.parse(fs.readFileSync(templatePackageJsonPath, 'utf8'));
132
- const packageWithDependencies = mergeDependencySections(basePackage, templatePackage);
133
- const templateScripts = Object.fromEntries(
134
- template.rootScripts.flatMap((scriptName) => {
135
- const script = templatePackage.scripts?.[scriptName];
136
-
137
- return script ? [[scriptName, script]] : [];
138
- }),
139
- );
140
-
141
- const mergedPackage = {
142
- ...packageWithDependencies,
143
- scripts: {
144
- ...templateScripts,
145
- ...packageWithDependencies.scripts,
146
- },
147
- };
148
-
149
- fs.writeFileSync(
150
- packageJsonPath,
151
- `${JSON.stringify(mergedPackage, null, JSON_INDENTATION)}\n`,
152
- );
153
- }
154
-
155
- function escapeRegularExpression(value) {
156
- return value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
157
- }
158
-
159
- function normalizeYamlKey(value) {
160
- return value.replace(/^['"]|['"]$/gu, '');
161
- }
162
-
163
- function getCatalogEntries(workspaceManifest) {
164
- const catalogEntries = new Map();
165
- let isInCatalogsSection = false;
166
- let catalogName;
167
-
168
- for (const line of workspaceManifest.split(LINE_BREAK_PATTERN)) {
169
- if (line === 'catalogs:') {
170
- isInCatalogsSection = true;
171
- } else if (isInCatalogsSection && TOP_LEVEL_SECTION_PATTERN.test(line)) {
172
- isInCatalogsSection = false;
173
- } else if (isInCatalogsSection) {
174
- const catalogMatch = line.match(/^ {2}(?<catalogName>[^:\r\n]+):[\t ]*$/u);
175
- const entryMatch = line.match(/^ {4}(?<key>['"][^'"]+['"]|[^:]+):/u);
176
-
177
- if (catalogMatch) {
178
- const { catalogName: nextCatalogName } = catalogMatch.groups;
179
- catalogName = nextCatalogName;
180
- catalogEntries.set(catalogName, []);
181
- } else if (catalogName && entryMatch) {
182
- const { key } = entryMatch.groups;
183
- catalogEntries.get(catalogName).push({ key, line });
184
- }
185
- }
186
- }
187
-
188
- return catalogEntries;
189
- }
190
-
191
- function getYamlListEntries(workspaceManifest, sectionName) {
192
- const entries = [];
193
- let isInSection = false;
194
-
195
- for (const line of workspaceManifest.split(LINE_BREAK_PATTERN)) {
196
- if (line === `${sectionName}:`) {
197
- isInSection = true;
198
- } else if (isInSection && TOP_LEVEL_SECTION_PATTERN.test(line)) {
199
- isInSection = false;
200
- } else if (isInSection && INDENTED_LIST_ENTRY_PATTERN.test(line)) {
201
- entries.push(line);
202
- }
203
- }
204
-
205
- return entries;
206
- }
207
-
208
- function mergeCatalogEntries(baseWorkspaceManifest, templateWorkspaceManifest) {
209
- let mergedWorkspaceManifest = baseWorkspaceManifest;
210
- const lineEnding = baseWorkspaceManifest.includes('\r\n') ? '\r\n' : '\n';
211
-
212
- for (const [catalogName, entries] of getCatalogEntries(templateWorkspaceManifest)) {
213
- const catalogHeader = new RegExp(
214
- `^ {2}${escapeRegularExpression(catalogName)}:[\\t ]*(?:\\r?\\n|$)`,
215
- 'mu',
216
- );
217
-
218
- if (!catalogHeader.test(mergedWorkspaceManifest)) {
219
- mergedWorkspaceManifest = mergedWorkspaceManifest.replace(
220
- /^catalogs:[\t ]*(?:\r?\n|$)/mu,
221
- (header) =>
222
- `${header} ${catalogName}:${lineEnding}${entries.map((entry) => entry.line).join(lineEnding)}${lineEnding}`,
223
- );
224
- } else {
225
- for (const entry of entries) {
226
- const entryPattern = new RegExp(
227
- `^ {4}['"]?${escapeRegularExpression(normalizeYamlKey(entry.key))}['"]?:[^\\r\\n]*`,
228
- 'mu',
229
- );
230
-
231
- if (entryPattern.test(mergedWorkspaceManifest)) {
232
- mergedWorkspaceManifest = mergedWorkspaceManifest.replace(
233
- entryPattern,
234
- entry.line,
235
- );
236
- } else {
237
- const catalogMatch = catalogHeader.exec(mergedWorkspaceManifest);
238
- const catalogContents = mergedWorkspaceManifest.slice(
239
- catalogMatch.index + catalogMatch[0].length,
240
- );
241
- const nextCatalogMatch = catalogContents.match(
242
- /^(?: {2})?[^\s#][^:]*:[\t ]*(?:\r?\n|$)/mu,
243
- );
244
- const insertionIndex = nextCatalogMatch
245
- ? catalogMatch.index + catalogMatch[0].length + nextCatalogMatch.index
246
- : mergedWorkspaceManifest.length;
247
-
248
- mergedWorkspaceManifest = `${mergedWorkspaceManifest.slice(0, insertionIndex)}${entry.line}${lineEnding}${mergedWorkspaceManifest.slice(insertionIndex)}`;
249
- }
250
- }
251
- }
252
- }
253
-
254
- return mergedWorkspaceManifest;
255
- }
256
-
257
- function mergeYamlListEntries(
258
- baseWorkspaceManifest,
259
- templateWorkspaceManifest,
260
- sectionName,
261
- ) {
262
- const templateEntries = getYamlListEntries(templateWorkspaceManifest, sectionName);
263
- if (templateEntries.length === 0) {
264
- return baseWorkspaceManifest;
265
- }
266
-
267
- const lineEnding = baseWorkspaceManifest.includes('\r\n') ? '\r\n' : '\n';
268
- const lines = baseWorkspaceManifest.split(LINE_BREAK_PATTERN);
269
- const sectionIndex = lines.indexOf(`${sectionName}:`);
270
-
271
- if (sectionIndex === -1) {
272
- return `${baseWorkspaceManifest.trimEnd()}${lineEnding}${sectionName}:${lineEnding}${templateEntries.join(lineEnding)}${lineEnding}`;
273
- }
274
-
275
- let sectionEndIndex = sectionIndex + 1;
276
- while (
277
- sectionEndIndex < lines.length &&
278
- !TOP_LEVEL_SECTION_PATTERN.test(lines[sectionEndIndex])
279
- ) {
280
- sectionEndIndex += 1;
281
- }
282
-
283
- const existingEntries = new Set(
284
- lines
285
- .slice(sectionIndex + 1, sectionEndIndex)
286
- .filter((line) => INDENTED_LIST_ENTRY_PATTERN.test(line)),
287
- );
288
- const missingEntries = templateEntries.filter((entry) => !existingEntries.has(entry));
289
-
290
- lines.splice(sectionEndIndex, 0, ...missingEntries);
291
- return lines.join(lineEnding);
292
- }
293
-
294
- function mergeWorkspaceManifest(templateWorkspaceManifestPath, workspaceManifestPath) {
295
- const baseWorkspaceManifest = fs.readFileSync(workspaceManifestPath, 'utf8');
296
- const templateWorkspaceManifest = fs.readFileSync(
297
- templateWorkspaceManifestPath,
298
- 'utf8',
299
- );
300
- const catalogMergedWorkspaceManifest = mergeCatalogEntries(
301
- baseWorkspaceManifest,
302
- templateWorkspaceManifest,
303
- );
304
- const mergedWorkspaceManifest = mergeYamlListEntries(
305
- catalogMergedWorkspaceManifest,
306
- templateWorkspaceManifest,
307
- 'packages',
308
- );
309
- const workspaceManifestWithTemplatePackages = mergeYamlListEntries(
310
- mergedWorkspaceManifest,
311
- templateWorkspaceManifest,
312
- 'onlyBuiltDependencies',
313
- );
314
-
315
- fs.writeFileSync(workspaceManifestPath, workspaceManifestWithTemplatePackages);
316
- }
317
-
318
- function addAppTemplate(template, workspaceDirectory) {
319
- const destinationDirectory = path.join(
320
- workspaceDirectory,
321
- template.destinationDirectory,
322
- );
323
- fs.mkdirSync(destinationDirectory, { recursive: true });
324
-
325
- if (fs.readdirSync(destinationDirectory).length > 0) {
326
- throw new Error(`App destination already exists: ${destinationDirectory}`);
327
- }
328
-
329
- const temporaryDirectory = fs.mkdtempSync(path.join(os.tmpdir(), 'scaffoldfy-app-'));
330
- const repositoryDirectory = path.join(temporaryDirectory, 'repository');
331
-
332
- try {
333
- runGit(
334
- [
335
- 'clone',
336
- '--depth',
337
- '1',
338
- '--filter=blob:none',
339
- '--sparse',
340
- template.repository,
341
- repositoryDirectory,
342
- ],
343
- workspaceDirectory,
344
- );
345
- runGit(
346
- [
347
- 'sparse-checkout',
348
- 'set',
349
- '--skip-checks',
350
- template.sourceDirectory,
351
- ...TEMPLATE_SOURCE_PATHS,
352
- ],
353
- repositoryDirectory,
354
- );
355
-
356
- const sourceDirectory = path.join(repositoryDirectory, template.sourceDirectory);
357
- if (!fs.existsSync(sourceDirectory)) {
358
- throw new Error(
359
- `Template directory "${template.sourceDirectory}" was not found in ${template.repository}`,
360
- );
361
- }
362
-
363
- copyDirectoryContents(sourceDirectory, destinationDirectory);
364
- overlayDirectoryContents(
365
- path.join(repositoryDirectory, 'packages'),
366
- path.join(workspaceDirectory, 'packages'),
367
- );
368
- overlayDirectoryContents(
369
- path.join(repositoryDirectory, 'tooling'),
370
- path.join(workspaceDirectory, 'tooling'),
371
- );
372
- overlayDirectoryContents(
373
- path.join(repositoryDirectory, '.github', 'workflows'),
374
- path.join(workspaceDirectory, '.github', 'workflows'),
375
- );
376
- mergeRootPackageManifest(template, repositoryDirectory, workspaceDirectory);
377
-
378
- const templateWorkspaceManifestPath = path.join(
379
- repositoryDirectory,
380
- 'pnpm-workspace.yaml',
381
- );
382
- const workspaceManifestPath = path.join(workspaceDirectory, 'pnpm-workspace.yaml');
383
-
384
- mergeWorkspaceManifest(templateWorkspaceManifestPath, workspaceManifestPath);
385
- } finally {
386
- fs.rmSync(temporaryDirectory, { recursive: true, force: true });
387
- }
388
- }
389
-
390
- function main() {
391
- const workspaceDirectory = process.cwd();
392
- const selectedApps = getSelectedApps();
393
- const selectedTemplates = [...selectedApps].filter((app) => APP_TEMPLATES[app]);
394
-
395
- if (selectedApps.size === 0) {
396
- throw new Error('Select at least one workspace app.');
397
- }
398
-
399
- ensureEmptyDirectory(workspaceDirectory);
400
- runGit(['clone', '--depth', '1', BASE_TEMPLATE_REPOSITORY, '.'], workspaceDirectory);
401
-
402
- for (const app of selectedTemplates) {
403
- addAppTemplate(APP_TEMPLATES[app], workspaceDirectory);
404
- }
405
-
406
- if (selectedTemplates.length > 0) {
407
- refreshLockfile(workspaceDirectory);
408
- }
409
- }
410
-
411
- main();