create-forgeon 0.2.2 → 0.2.4

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.
@@ -1,6 +1,15 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { copyRecursive, writeJson } from '../utils/fs.mjs';
4
+ import {
5
+ ensureBuildSteps,
6
+ ensureDependency,
7
+ ensureLineAfter,
8
+ ensureLineBefore,
9
+ ensureLoadItem,
10
+ ensureValidatorSchema,
11
+ upsertEnvLines,
12
+ } from './shared/patch-utils.mjs';
4
13
 
5
14
  function copyFromPreset(packageRoot, targetRoot, relativePath) {
6
15
  const source = path.join(packageRoot, 'templates', 'module-presets', 'logger', relativePath);
@@ -11,108 +20,6 @@ function copyFromPreset(packageRoot, targetRoot, relativePath) {
11
20
  copyRecursive(source, destination);
12
21
  }
13
22
 
14
- function ensureDependency(packageJson, name, version) {
15
- if (!packageJson.dependencies) {
16
- packageJson.dependencies = {};
17
- }
18
- packageJson.dependencies[name] = version;
19
- }
20
-
21
- function ensureLineAfter(content, anchorLine, lineToInsert) {
22
- if (content.includes(lineToInsert)) {
23
- return content;
24
- }
25
-
26
- const index = content.indexOf(anchorLine);
27
- if (index < 0) {
28
- return `${content.trimEnd()}\n${lineToInsert}\n`;
29
- }
30
-
31
- const insertAt = index + anchorLine.length;
32
- return `${content.slice(0, insertAt)}\n${lineToInsert}${content.slice(insertAt)}`;
33
- }
34
-
35
- function ensureLineBefore(content, anchorLine, lineToInsert) {
36
- if (content.includes(lineToInsert)) {
37
- return content;
38
- }
39
-
40
- const index = content.indexOf(anchorLine);
41
- if (index < 0) {
42
- return `${content.trimEnd()}\n${lineToInsert}\n`;
43
- }
44
-
45
- return `${content.slice(0, index)}${lineToInsert}\n${content.slice(index)}`;
46
- }
47
-
48
- function upsertEnvLines(filePath, lines) {
49
- let content = '';
50
- if (fs.existsSync(filePath)) {
51
- content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
52
- }
53
-
54
- const keys = new Set(
55
- content
56
- .split('\n')
57
- .filter(Boolean)
58
- .map((line) => line.split('=')[0]),
59
- );
60
-
61
- const append = [];
62
- for (const line of lines) {
63
- const key = line.split('=')[0];
64
- if (!keys.has(key)) {
65
- append.push(line);
66
- }
67
- }
68
-
69
- const next =
70
- append.length > 0 ? `${content.trimEnd()}\n${append.join('\n')}\n` : `${content.trimEnd()}\n`;
71
- fs.writeFileSync(filePath, next.replace(/^\n/, ''), 'utf8');
72
- }
73
-
74
- function ensureLoadItem(content, itemName) {
75
- const pattern = /load:\s*\[([^\]]*)\]/m;
76
- const match = content.match(pattern);
77
- if (!match) {
78
- return content;
79
- }
80
-
81
- const rawList = match[1];
82
- const items = rawList
83
- .split(',')
84
- .map((item) => item.trim())
85
- .filter(Boolean);
86
-
87
- if (!items.includes(itemName)) {
88
- items.push(itemName);
89
- }
90
-
91
- const next = `load: [${items.join(', ')}]`;
92
- return content.replace(pattern, next);
93
- }
94
-
95
- function ensureValidatorSchema(content, schemaName) {
96
- const pattern = /validate:\s*createEnvValidator\(\[([^\]]*)\]\)/m;
97
- const match = content.match(pattern);
98
- if (!match) {
99
- return content;
100
- }
101
-
102
- const rawList = match[1];
103
- const items = rawList
104
- .split(',')
105
- .map((item) => item.trim())
106
- .filter(Boolean);
107
-
108
- if (!items.includes(schemaName)) {
109
- items.push(schemaName);
110
- }
111
-
112
- const next = `validate: createEnvValidator([${items.join(', ')}])`;
113
- return content.replace(pattern, next);
114
- }
115
-
116
23
  function patchApiPackage(targetRoot) {
117
24
  const packagePath = path.join(targetRoot, 'apps', 'api', 'package.json');
118
25
  if (!fs.existsSync(packagePath)) {
@@ -124,22 +31,7 @@ function patchApiPackage(targetRoot) {
124
31
  packageJson.scripts = {};
125
32
  }
126
33
 
127
- const loggerBuild = 'pnpm --filter @forgeon/logger build';
128
- const currentPredev = packageJson.scripts.predev;
129
- if (typeof currentPredev === 'string') {
130
- if (!currentPredev.includes(loggerBuild)) {
131
- if (currentPredev.includes('pnpm --filter @forgeon/core build')) {
132
- packageJson.scripts.predev = currentPredev.replace(
133
- 'pnpm --filter @forgeon/core build',
134
- `pnpm --filter @forgeon/core build && ${loggerBuild}`,
135
- );
136
- } else {
137
- packageJson.scripts.predev = `${loggerBuild} && ${currentPredev}`;
138
- }
139
- }
140
- } else {
141
- packageJson.scripts.predev = loggerBuild;
142
- }
34
+ ensureBuildSteps(packageJson, 'predev', ['pnpm --filter @forgeon/logger build']);
143
35
 
144
36
  ensureDependency(packageJson, '@forgeon/logger', 'workspace:*');
145
37
  writeJson(packagePath, packageJson);
@@ -0,0 +1,237 @@
1
+ import fs from 'node:fs';
2
+
3
+ export function ensureDependency(packageJson, name, version) {
4
+ if (!packageJson.dependencies) {
5
+ packageJson.dependencies = {};
6
+ }
7
+ packageJson.dependencies[name] = version;
8
+ }
9
+
10
+ export function ensureDevDependency(packageJson, name, version) {
11
+ if (!packageJson.devDependencies) {
12
+ packageJson.devDependencies = {};
13
+ }
14
+ packageJson.devDependencies[name] = version;
15
+ }
16
+
17
+ export function ensureScript(packageJson, name, command) {
18
+ if (!packageJson.scripts) {
19
+ packageJson.scripts = {};
20
+ }
21
+ packageJson.scripts[name] = command;
22
+ }
23
+
24
+ export function ensureBuildSteps(packageJson, scriptName, requiredCommands) {
25
+ if (!packageJson.scripts) {
26
+ packageJson.scripts = {};
27
+ }
28
+
29
+ const current = packageJson.scripts[scriptName];
30
+ const steps =
31
+ typeof current === 'string' && current.trim().length > 0
32
+ ? current
33
+ .split('&&')
34
+ .map((item) => item.trim())
35
+ .filter(Boolean)
36
+ : [];
37
+
38
+ for (const command of requiredCommands) {
39
+ if (!steps.includes(command)) {
40
+ steps.push(command);
41
+ }
42
+ }
43
+
44
+ if (steps.length > 0) {
45
+ packageJson.scripts[scriptName] = steps.join(' && ');
46
+ }
47
+ }
48
+
49
+ export function ensureLineAfter(content, anchorLine, lineToInsert) {
50
+ if (content.includes(lineToInsert)) {
51
+ return content;
52
+ }
53
+
54
+ const index = content.indexOf(anchorLine);
55
+ if (index < 0) {
56
+ return `${content.trimEnd()}\n${lineToInsert}\n`;
57
+ }
58
+
59
+ const insertAt = index + anchorLine.length;
60
+ return `${content.slice(0, insertAt)}\n${lineToInsert}${content.slice(insertAt)}`;
61
+ }
62
+
63
+ export function ensureLineBefore(content, anchorLine, lineToInsert) {
64
+ if (content.includes(lineToInsert)) {
65
+ return content;
66
+ }
67
+
68
+ const index = content.indexOf(anchorLine);
69
+ if (index < 0) {
70
+ return `${content.trimEnd()}\n${lineToInsert}\n`;
71
+ }
72
+
73
+ return `${content.slice(0, index)}${lineToInsert}\n${content.slice(index)}`;
74
+ }
75
+
76
+ export function ensureImportLine(content, importLine) {
77
+ if (content.includes(importLine)) {
78
+ return content;
79
+ }
80
+
81
+ const importMatches = [...content.matchAll(/^import\s.+;$/gm)];
82
+ if (importMatches.length === 0) {
83
+ return `${importLine}\n${content}`;
84
+ }
85
+
86
+ const lastImport = importMatches.at(-1);
87
+ const insertAt = lastImport.index + lastImport[0].length;
88
+ return `${content.slice(0, insertAt)}\n${importLine}${content.slice(insertAt)}`;
89
+ }
90
+
91
+ function findClassRange(content, className) {
92
+ const classPattern = new RegExp(`export\\s+class\\s+${className}\\b`);
93
+ const classMatch = classPattern.exec(content);
94
+ if (!classMatch) {
95
+ return null;
96
+ }
97
+
98
+ const openBrace = content.indexOf('{', classMatch.index);
99
+ if (openBrace < 0) {
100
+ return null;
101
+ }
102
+
103
+ let depth = 0;
104
+ for (let index = openBrace; index < content.length; index += 1) {
105
+ const char = content[index];
106
+ if (char === '{') {
107
+ depth += 1;
108
+ } else if (char === '}') {
109
+ depth -= 1;
110
+ if (depth === 0) {
111
+ return {
112
+ classStart: classMatch.index,
113
+ bodyStart: openBrace + 1,
114
+ classEnd: index,
115
+ };
116
+ }
117
+ }
118
+ }
119
+
120
+ return null;
121
+ }
122
+
123
+ export function ensureClassMember(content, className, memberCode, options = {}) {
124
+ const member = memberCode.trim();
125
+ if (member.length === 0) {
126
+ return content;
127
+ }
128
+
129
+ const range = findClassRange(content, className);
130
+ if (!range) {
131
+ return `${content.trimEnd()}\n${member}\n`;
132
+ }
133
+
134
+ const classBody = content.slice(range.bodyStart, range.classEnd);
135
+ if (classBody.includes(member)) {
136
+ return content;
137
+ }
138
+
139
+ let insertAt = range.classEnd;
140
+ const beforeNeedle = options.beforeNeedle;
141
+ if (typeof beforeNeedle === 'string' && beforeNeedle.length > 0) {
142
+ const needleIndex = classBody.indexOf(beforeNeedle);
143
+ if (needleIndex >= 0) {
144
+ insertAt = range.bodyStart + needleIndex;
145
+ }
146
+ }
147
+
148
+ return `${content.slice(0, insertAt)}\n\n${member}\n${content.slice(insertAt)}`;
149
+ }
150
+
151
+ export function upsertEnvLines(filePath, lines) {
152
+ let content = '';
153
+ if (fs.existsSync(filePath)) {
154
+ content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
155
+ }
156
+
157
+ const keys = new Set(
158
+ content
159
+ .split('\n')
160
+ .filter(Boolean)
161
+ .map((line) => line.split('=')[0]),
162
+ );
163
+
164
+ const append = [];
165
+ for (const line of lines) {
166
+ const key = line.split('=')[0];
167
+ if (!keys.has(key)) {
168
+ append.push(line);
169
+ }
170
+ }
171
+
172
+ const next =
173
+ append.length > 0 ? `${content.trimEnd()}\n${append.join('\n')}\n` : `${content.trimEnd()}\n`;
174
+ fs.writeFileSync(filePath, next.replace(/^\n/, ''), 'utf8');
175
+ }
176
+
177
+ export function ensureLoadItem(content, itemName) {
178
+ const pattern = /load:\s*\[([^\]]*)\]/m;
179
+ const match = content.match(pattern);
180
+ if (!match) {
181
+ return content;
182
+ }
183
+
184
+ const rawList = match[1];
185
+ const items = rawList
186
+ .split(',')
187
+ .map((item) => item.trim())
188
+ .filter(Boolean);
189
+
190
+ if (!items.includes(itemName)) {
191
+ items.push(itemName);
192
+ }
193
+
194
+ const next = `load: [${items.join(', ')}]`;
195
+ return content.replace(pattern, next);
196
+ }
197
+
198
+ export function ensureValidatorSchema(content, schemaName) {
199
+ const pattern = /validate:\s*createEnvValidator\(\[([^\]]*)\]\)/m;
200
+ const match = content.match(pattern);
201
+ if (!match) {
202
+ return content;
203
+ }
204
+
205
+ const rawList = match[1];
206
+ const items = rawList
207
+ .split(',')
208
+ .map((item) => item.trim())
209
+ .filter(Boolean);
210
+
211
+ if (!items.includes(schemaName)) {
212
+ items.push(schemaName);
213
+ }
214
+
215
+ const next = `validate: createEnvValidator([${items.join(', ')}])`;
216
+ return content.replace(pattern, next);
217
+ }
218
+
219
+ export function ensureNestCommonImport(content, importName) {
220
+ const pattern = /import\s*\{([^}]*)\}\s*from '@nestjs\/common';/m;
221
+ const match = content.match(pattern);
222
+ if (!match) {
223
+ return `import { ${importName} } from '@nestjs/common';\n${content}`;
224
+ }
225
+
226
+ const names = match[1]
227
+ .split(',')
228
+ .map((item) => item.trim())
229
+ .filter(Boolean);
230
+
231
+ if (!names.includes(importName)) {
232
+ names.push(importName);
233
+ }
234
+
235
+ const replacement = `import { ${names.join(', ')} } from '@nestjs/common';`;
236
+ return content.replace(pattern, replacement);
237
+ }
@@ -1,6 +1,15 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { copyRecursive, writeJson } from '../utils/fs.mjs';
4
+ import {
5
+ ensureBuildSteps,
6
+ ensureDependency,
7
+ ensureLineAfter,
8
+ ensureLineBefore,
9
+ ensureLoadItem,
10
+ ensureValidatorSchema,
11
+ upsertEnvLines,
12
+ } from './shared/patch-utils.mjs';
4
13
 
5
14
  function copyFromPreset(packageRoot, targetRoot, relativePath) {
6
15
  const source = path.join(packageRoot, 'templates', 'module-presets', 'swagger', relativePath);
@@ -11,126 +20,6 @@ function copyFromPreset(packageRoot, targetRoot, relativePath) {
11
20
  copyRecursive(source, destination);
12
21
  }
13
22
 
14
- function ensureDependency(packageJson, name, version) {
15
- if (!packageJson.dependencies) {
16
- packageJson.dependencies = {};
17
- }
18
- packageJson.dependencies[name] = version;
19
- }
20
-
21
- function ensureLineAfter(content, anchorLine, lineToInsert) {
22
- if (content.includes(lineToInsert)) {
23
- return content;
24
- }
25
-
26
- const index = content.indexOf(anchorLine);
27
- if (index < 0) {
28
- return `${content.trimEnd()}\n${lineToInsert}\n`;
29
- }
30
-
31
- const insertAt = index + anchorLine.length;
32
- return `${content.slice(0, insertAt)}\n${lineToInsert}${content.slice(insertAt)}`;
33
- }
34
-
35
- function ensureLineBefore(content, anchorLine, lineToInsert) {
36
- if (content.includes(lineToInsert)) {
37
- return content;
38
- }
39
-
40
- const index = content.indexOf(anchorLine);
41
- if (index < 0) {
42
- return `${content.trimEnd()}\n${lineToInsert}\n`;
43
- }
44
-
45
- return `${content.slice(0, index)}${lineToInsert}\n${content.slice(index)}`;
46
- }
47
-
48
- function upsertEnvLines(filePath, lines) {
49
- let content = '';
50
- if (fs.existsSync(filePath)) {
51
- content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
52
- }
53
-
54
- const keys = new Set(
55
- content
56
- .split('\n')
57
- .filter(Boolean)
58
- .map((line) => line.split('=')[0]),
59
- );
60
-
61
- const append = [];
62
- for (const line of lines) {
63
- const key = line.split('=')[0];
64
- if (!keys.has(key)) {
65
- append.push(line);
66
- }
67
- }
68
-
69
- const next =
70
- append.length > 0 ? `${content.trimEnd()}\n${append.join('\n')}\n` : `${content.trimEnd()}\n`;
71
- fs.writeFileSync(filePath, next.replace(/^\n/, ''), 'utf8');
72
- }
73
-
74
- function ensureBuildStep(packageJson, buildCommand) {
75
- if (!packageJson.scripts) {
76
- packageJson.scripts = {};
77
- }
78
-
79
- const current = packageJson.scripts.predev;
80
- if (typeof current !== 'string' || current.trim().length === 0) {
81
- packageJson.scripts.predev = buildCommand;
82
- return;
83
- }
84
-
85
- if (current.includes(buildCommand)) {
86
- return;
87
- }
88
-
89
- packageJson.scripts.predev = `${buildCommand} && ${current}`;
90
- }
91
-
92
- function ensureLoadItem(content, itemName) {
93
- const pattern = /load:\s*\[([^\]]*)\]/m;
94
- const match = content.match(pattern);
95
- if (!match) {
96
- return content;
97
- }
98
-
99
- const rawList = match[1];
100
- const items = rawList
101
- .split(',')
102
- .map((item) => item.trim())
103
- .filter(Boolean);
104
-
105
- if (!items.includes(itemName)) {
106
- items.push(itemName);
107
- }
108
-
109
- const next = `load: [${items.join(', ')}]`;
110
- return content.replace(pattern, next);
111
- }
112
-
113
- function ensureValidatorSchema(content, schemaName) {
114
- const pattern = /validate:\s*createEnvValidator\(\[([^\]]*)\]\)/m;
115
- const match = content.match(pattern);
116
- if (!match) {
117
- return content;
118
- }
119
-
120
- const rawList = match[1];
121
- const items = rawList
122
- .split(',')
123
- .map((item) => item.trim())
124
- .filter(Boolean);
125
-
126
- if (!items.includes(schemaName)) {
127
- items.push(schemaName);
128
- }
129
-
130
- const next = `validate: createEnvValidator([${items.join(', ')}])`;
131
- return content.replace(pattern, next);
132
- }
133
-
134
23
  function patchApiPackage(targetRoot) {
135
24
  const packagePath = path.join(targetRoot, 'apps', 'api', 'package.json');
136
25
  if (!fs.existsSync(packagePath)) {
@@ -139,7 +28,7 @@ function patchApiPackage(targetRoot) {
139
28
 
140
29
  const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
141
30
  ensureDependency(packageJson, '@forgeon/swagger', 'workspace:*');
142
- ensureBuildStep(packageJson, 'pnpm --filter @forgeon/swagger build');
31
+ ensureBuildSteps(packageJson, 'predev', ['pnpm --filter @forgeon/swagger build']);
143
32
  writeJson(packagePath, packageJson);
144
33
  }
145
34