pulse-js-framework 1.4.10 → 1.5.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/cli/analyze.js +8 -7
- package/cli/build.js +14 -13
- package/cli/dev.js +28 -7
- package/cli/format.js +13 -12
- package/cli/index.js +20 -1
- package/cli/lint.js +8 -7
- package/cli/release.js +493 -0
- package/compiler/parser.js +41 -20
- package/compiler/transformer/constants.js +54 -0
- package/compiler/transformer/export.js +33 -0
- package/compiler/transformer/expressions.js +273 -0
- package/compiler/transformer/imports.js +101 -0
- package/compiler/transformer/index.js +319 -0
- package/compiler/transformer/router.js +95 -0
- package/compiler/transformer/state.js +118 -0
- package/compiler/transformer/store.js +97 -0
- package/compiler/transformer/style.js +130 -0
- package/compiler/transformer/view.js +428 -0
- package/compiler/transformer.js +17 -1310
- package/core/errors.js +300 -0
- package/package.json +11 -3
- package/runtime/dom.js +61 -10
- package/runtime/lru-cache.js +141 -0
- package/runtime/native.js +6 -1
- package/runtime/pulse.js +46 -2
- package/runtime/router.js +4 -1
- package/runtime/store.js +35 -1
- package/runtime/utils.js +348 -0
- package/types/index.d.ts +19 -0
- package/types/lru-cache.d.ts +118 -0
- package/types/utils.d.ts +255 -0
package/cli/release.js
ADDED
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulse CLI - Release Command
|
|
3
|
+
*
|
|
4
|
+
* Handles version bumping and release automation:
|
|
5
|
+
* - Bump version (patch, minor, major)
|
|
6
|
+
* - Update CHANGELOG.md
|
|
7
|
+
* - Update docs changelog page
|
|
8
|
+
* - Update version in docs state
|
|
9
|
+
* - Create git commit and tag
|
|
10
|
+
* - Push to remote
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
14
|
+
import { join, dirname } from 'path';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
import { execSync } from 'child_process';
|
|
17
|
+
import { createInterface } from 'readline';
|
|
18
|
+
import { log } from './logger.js';
|
|
19
|
+
|
|
20
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const root = join(__dirname, '..');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Prompt user for input
|
|
25
|
+
*/
|
|
26
|
+
function prompt(question) {
|
|
27
|
+
const rl = createInterface({
|
|
28
|
+
input: process.stdin,
|
|
29
|
+
output: process.stdout
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return new Promise((resolve) => {
|
|
33
|
+
rl.question(question, (answer) => {
|
|
34
|
+
rl.close();
|
|
35
|
+
resolve(answer);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Prompt for multiline input
|
|
42
|
+
*/
|
|
43
|
+
async function promptMultiline(question) {
|
|
44
|
+
log.info(question);
|
|
45
|
+
log.info('(Enter each item on a new line, empty line to finish)');
|
|
46
|
+
|
|
47
|
+
const rl = createInterface({
|
|
48
|
+
input: process.stdin,
|
|
49
|
+
output: process.stdout
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const lines = [];
|
|
53
|
+
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
rl.on('line', (line) => {
|
|
56
|
+
if (line.trim() === '') {
|
|
57
|
+
rl.close();
|
|
58
|
+
resolve(lines);
|
|
59
|
+
} else {
|
|
60
|
+
lines.push(line.trim());
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Parse version string
|
|
68
|
+
*/
|
|
69
|
+
function parseVersion(version) {
|
|
70
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
71
|
+
if (!match) throw new Error(`Invalid version format: ${version}`);
|
|
72
|
+
return {
|
|
73
|
+
major: parseInt(match[1], 10),
|
|
74
|
+
minor: parseInt(match[2], 10),
|
|
75
|
+
patch: parseInt(match[3], 10)
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Bump version based on type
|
|
81
|
+
*/
|
|
82
|
+
function bumpVersion(version, type) {
|
|
83
|
+
const v = parseVersion(version);
|
|
84
|
+
switch (type) {
|
|
85
|
+
case 'major':
|
|
86
|
+
return `${v.major + 1}.0.0`;
|
|
87
|
+
case 'minor':
|
|
88
|
+
return `${v.major}.${v.minor + 1}.0`;
|
|
89
|
+
case 'patch':
|
|
90
|
+
return `${v.major}.${v.minor}.${v.patch + 1}`;
|
|
91
|
+
default:
|
|
92
|
+
throw new Error(`Unknown version type: ${type}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get current date in YYYY-MM-DD format
|
|
98
|
+
*/
|
|
99
|
+
function getCurrentDate() {
|
|
100
|
+
const now = new Date();
|
|
101
|
+
return now.toISOString().split('T')[0];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get current month and year
|
|
106
|
+
*/
|
|
107
|
+
function getCurrentMonthYear() {
|
|
108
|
+
const months = [
|
|
109
|
+
'January', 'February', 'March', 'April', 'May', 'June',
|
|
110
|
+
'July', 'August', 'September', 'October', 'November', 'December'
|
|
111
|
+
];
|
|
112
|
+
const now = new Date();
|
|
113
|
+
return `${months[now.getMonth()]} ${now.getFullYear()}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Update package.json version
|
|
118
|
+
*/
|
|
119
|
+
function updatePackageJson(newVersion) {
|
|
120
|
+
const pkgPath = join(root, 'package.json');
|
|
121
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
122
|
+
pkg.version = newVersion;
|
|
123
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
124
|
+
log.info(` Updated package.json to v${newVersion}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Update docs/src/state.js version
|
|
129
|
+
*/
|
|
130
|
+
function updateDocsState(newVersion) {
|
|
131
|
+
const statePath = join(root, 'docs/src/state.js');
|
|
132
|
+
if (!existsSync(statePath)) {
|
|
133
|
+
log.warn(' docs/src/state.js not found, skipping');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let content = readFileSync(statePath, 'utf-8');
|
|
138
|
+
content = content.replace(
|
|
139
|
+
/export const version = '[^']+'/,
|
|
140
|
+
`export const version = '${newVersion}'`
|
|
141
|
+
);
|
|
142
|
+
writeFileSync(statePath, content);
|
|
143
|
+
log.info(` Updated docs/src/state.js to v${newVersion}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Update CHANGELOG.md
|
|
148
|
+
*/
|
|
149
|
+
function updateChangelog(newVersion, title, changes) {
|
|
150
|
+
const changelogPath = join(root, 'CHANGELOG.md');
|
|
151
|
+
if (!existsSync(changelogPath)) {
|
|
152
|
+
log.warn(' CHANGELOG.md not found, skipping');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let content = readFileSync(changelogPath, 'utf-8');
|
|
157
|
+
|
|
158
|
+
// Build changelog entry
|
|
159
|
+
const date = getCurrentDate();
|
|
160
|
+
let entry = `## [${newVersion}] - ${date}\n\n`;
|
|
161
|
+
|
|
162
|
+
if (title) {
|
|
163
|
+
entry += `### ${title}\n\n`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (changes.added && changes.added.length > 0) {
|
|
167
|
+
entry += `### Added\n\n`;
|
|
168
|
+
for (const item of changes.added) {
|
|
169
|
+
entry += `- ${item}\n`;
|
|
170
|
+
}
|
|
171
|
+
entry += '\n';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (changes.changed && changes.changed.length > 0) {
|
|
175
|
+
entry += `### Changed\n\n`;
|
|
176
|
+
for (const item of changes.changed) {
|
|
177
|
+
entry += `- ${item}\n`;
|
|
178
|
+
}
|
|
179
|
+
entry += '\n';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (changes.fixed && changes.fixed.length > 0) {
|
|
183
|
+
entry += `### Fixed\n\n`;
|
|
184
|
+
for (const item of changes.fixed) {
|
|
185
|
+
entry += `- ${item}\n`;
|
|
186
|
+
}
|
|
187
|
+
entry += '\n';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (changes.removed && changes.removed.length > 0) {
|
|
191
|
+
entry += `### Removed\n\n`;
|
|
192
|
+
for (const item of changes.removed) {
|
|
193
|
+
entry += `- ${item}\n`;
|
|
194
|
+
}
|
|
195
|
+
entry += '\n';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Insert after the header section (after line 6)
|
|
199
|
+
const lines = content.split('\n');
|
|
200
|
+
const insertIndex = lines.findIndex(line => line.startsWith('## ['));
|
|
201
|
+
|
|
202
|
+
if (insertIndex !== -1) {
|
|
203
|
+
lines.splice(insertIndex, 0, entry);
|
|
204
|
+
} else {
|
|
205
|
+
// No existing version entries, add after header
|
|
206
|
+
lines.push(entry);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
writeFileSync(changelogPath, lines.join('\n'));
|
|
210
|
+
log.info(` Updated CHANGELOG.md with v${newVersion}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Update docs changelog page
|
|
215
|
+
*/
|
|
216
|
+
function updateDocsChangelog(newVersion, title, changes) {
|
|
217
|
+
const changelogPagePath = join(root, 'docs/src/pages/ChangelogPage.js');
|
|
218
|
+
if (!existsSync(changelogPagePath)) {
|
|
219
|
+
log.warn(' docs/src/pages/ChangelogPage.js not found, skipping');
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
let content = readFileSync(changelogPagePath, 'utf-8');
|
|
224
|
+
const monthYear = getCurrentMonthYear();
|
|
225
|
+
|
|
226
|
+
// Build HTML changelog section
|
|
227
|
+
let section = `
|
|
228
|
+
<section class="doc-section changelog-section">
|
|
229
|
+
<h2>v${newVersion} - ${title || 'Release'}</h2>
|
|
230
|
+
<p class="release-date">${monthYear}</p>
|
|
231
|
+
|
|
232
|
+
<div class="changelog-group">`;
|
|
233
|
+
|
|
234
|
+
// Combine all changes into feature list
|
|
235
|
+
const allChanges = [
|
|
236
|
+
...(changes.added || []).map(c => `<strong>Added:</strong> ${escapeHtml(c)}`),
|
|
237
|
+
...(changes.changed || []).map(c => `<strong>Changed:</strong> ${escapeHtml(c)}`),
|
|
238
|
+
...(changes.fixed || []).map(c => `<strong>Fixed:</strong> ${escapeHtml(c)}`),
|
|
239
|
+
...(changes.removed || []).map(c => `<strong>Removed:</strong> ${escapeHtml(c)}`)
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
if (allChanges.length > 0) {
|
|
243
|
+
section += `
|
|
244
|
+
<ul class="feature-list">`;
|
|
245
|
+
for (const change of allChanges) {
|
|
246
|
+
section += `
|
|
247
|
+
<li>${change}</li>`;
|
|
248
|
+
}
|
|
249
|
+
section += `
|
|
250
|
+
</ul>`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
section += `
|
|
254
|
+
</div>
|
|
255
|
+
</section>
|
|
256
|
+
`;
|
|
257
|
+
|
|
258
|
+
// Find where to insert (after the intro paragraph, before first section)
|
|
259
|
+
const insertMarker = '<section class="doc-section changelog-section">';
|
|
260
|
+
const insertIndex = content.indexOf(insertMarker);
|
|
261
|
+
|
|
262
|
+
if (insertIndex !== -1) {
|
|
263
|
+
content = content.slice(0, insertIndex) + section + content.slice(insertIndex);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
writeFileSync(changelogPagePath, content);
|
|
267
|
+
log.info(` Updated docs/src/pages/ChangelogPage.js with v${newVersion}`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Escape HTML entities
|
|
272
|
+
*/
|
|
273
|
+
function escapeHtml(str) {
|
|
274
|
+
return str
|
|
275
|
+
.replace(/&/g, '&')
|
|
276
|
+
.replace(/</g, '<')
|
|
277
|
+
.replace(/>/g, '>')
|
|
278
|
+
.replace(/"/g, '"');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Update CLAUDE.md if needed
|
|
283
|
+
*/
|
|
284
|
+
function updateClaudeMd(newVersion) {
|
|
285
|
+
// CLAUDE.md reads version from package.json, so no update needed
|
|
286
|
+
log.info(' CLAUDE.md reads version from package.json (no update needed)');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Update README.md if needed
|
|
291
|
+
*/
|
|
292
|
+
function updateReadme(newVersion) {
|
|
293
|
+
// README.md doesn't have a version number to update
|
|
294
|
+
// But we could update version-specific feature mentions if needed
|
|
295
|
+
log.info(' README.md has no version-specific content to update');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Execute git commands
|
|
300
|
+
*/
|
|
301
|
+
function gitCommitTagPush(newVersion, dryRun = false) {
|
|
302
|
+
const commands = [
|
|
303
|
+
'git add -A',
|
|
304
|
+
`git commit -m "$(cat <<'EOF'\nv${newVersion}\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n)"`,
|
|
305
|
+
`git tag -a v${newVersion} -m "Release v${newVersion}"`,
|
|
306
|
+
'git push',
|
|
307
|
+
'git push --tags'
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
for (const cmd of commands) {
|
|
311
|
+
if (dryRun) {
|
|
312
|
+
log.info(` [dry-run] ${cmd}`);
|
|
313
|
+
} else {
|
|
314
|
+
log.info(` Running: ${cmd.split('\n')[0]}...`);
|
|
315
|
+
try {
|
|
316
|
+
execSync(cmd, { cwd: root, stdio: 'inherit', shell: '/bin/bash' });
|
|
317
|
+
} catch (error) {
|
|
318
|
+
log.error(` Failed: ${error.message}`);
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Show usage
|
|
327
|
+
*/
|
|
328
|
+
function showUsage() {
|
|
329
|
+
log.info(`
|
|
330
|
+
Usage: pulse release <type> [options]
|
|
331
|
+
|
|
332
|
+
Types:
|
|
333
|
+
patch Bump patch version (1.0.0 -> 1.0.1)
|
|
334
|
+
minor Bump minor version (1.0.0 -> 1.1.0)
|
|
335
|
+
major Bump major version (1.0.0 -> 2.0.0)
|
|
336
|
+
|
|
337
|
+
Options:
|
|
338
|
+
--dry-run Show what would be done without making changes
|
|
339
|
+
--no-push Create commit and tag but don't push
|
|
340
|
+
--title <text> Release title (e.g., "Performance Improvements")
|
|
341
|
+
--skip-prompt Use empty changelog (for automated releases)
|
|
342
|
+
|
|
343
|
+
Examples:
|
|
344
|
+
pulse release patch
|
|
345
|
+
pulse release minor --title "New Features"
|
|
346
|
+
pulse release major --dry-run
|
|
347
|
+
`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Main release command
|
|
352
|
+
*/
|
|
353
|
+
export async function runRelease(args) {
|
|
354
|
+
const type = args[0];
|
|
355
|
+
|
|
356
|
+
if (!type || !['patch', 'minor', 'major'].includes(type)) {
|
|
357
|
+
showUsage();
|
|
358
|
+
process.exit(1);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Parse options
|
|
362
|
+
const dryRun = args.includes('--dry-run');
|
|
363
|
+
const noPush = args.includes('--no-push');
|
|
364
|
+
const skipPrompt = args.includes('--skip-prompt');
|
|
365
|
+
|
|
366
|
+
let title = '';
|
|
367
|
+
const titleIndex = args.indexOf('--title');
|
|
368
|
+
if (titleIndex !== -1 && args[titleIndex + 1]) {
|
|
369
|
+
title = args[titleIndex + 1];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Read current version
|
|
373
|
+
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8'));
|
|
374
|
+
const currentVersion = pkg.version;
|
|
375
|
+
const newVersion = bumpVersion(currentVersion, type);
|
|
376
|
+
|
|
377
|
+
log.info('');
|
|
378
|
+
log.info(`Pulse Release: v${currentVersion} -> v${newVersion}`);
|
|
379
|
+
log.info('='.repeat(50));
|
|
380
|
+
|
|
381
|
+
if (dryRun) {
|
|
382
|
+
log.warn('DRY RUN - No changes will be made');
|
|
383
|
+
log.info('');
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Check for uncommitted changes
|
|
387
|
+
try {
|
|
388
|
+
const status = execSync('git status --porcelain', { cwd: root, encoding: 'utf-8' });
|
|
389
|
+
if (status.trim()) {
|
|
390
|
+
log.warn('You have uncommitted changes:');
|
|
391
|
+
log.info(status);
|
|
392
|
+
const proceed = await prompt('Continue anyway? (y/N) ');
|
|
393
|
+
if (proceed.toLowerCase() !== 'y') {
|
|
394
|
+
log.info('Aborted.');
|
|
395
|
+
process.exit(0);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
} catch (error) {
|
|
399
|
+
log.error('Failed to check git status');
|
|
400
|
+
process.exit(1);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Collect changelog entries
|
|
404
|
+
let changes = { added: [], changed: [], fixed: [], removed: [] };
|
|
405
|
+
|
|
406
|
+
if (!skipPrompt) {
|
|
407
|
+
if (!title) {
|
|
408
|
+
title = await prompt('Release title (e.g., "Performance Improvements"): ');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
log.info('');
|
|
412
|
+
log.info('Enter changelog items (leave empty to skip category):');
|
|
413
|
+
log.info('');
|
|
414
|
+
|
|
415
|
+
changes.added = await promptMultiline('Added (new features):');
|
|
416
|
+
changes.changed = await promptMultiline('Changed (modifications):');
|
|
417
|
+
changes.fixed = await promptMultiline('Fixed (bug fixes):');
|
|
418
|
+
changes.removed = await promptMultiline('Removed (deprecated features):');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const hasChanges = Object.values(changes).some(arr => arr.length > 0);
|
|
422
|
+
|
|
423
|
+
if (!hasChanges && !skipPrompt) {
|
|
424
|
+
const proceed = await prompt('No changelog entries. Continue? (y/N) ');
|
|
425
|
+
if (proceed.toLowerCase() !== 'y') {
|
|
426
|
+
log.info('Aborted.');
|
|
427
|
+
process.exit(0);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
log.info('');
|
|
432
|
+
log.info('Updating files...');
|
|
433
|
+
|
|
434
|
+
if (!dryRun) {
|
|
435
|
+
// 1. Update package.json
|
|
436
|
+
updatePackageJson(newVersion);
|
|
437
|
+
|
|
438
|
+
// 2. Update docs state
|
|
439
|
+
updateDocsState(newVersion);
|
|
440
|
+
|
|
441
|
+
// 3. Update CHANGELOG.md
|
|
442
|
+
if (hasChanges) {
|
|
443
|
+
updateChangelog(newVersion, title, changes);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// 4. Update docs changelog page
|
|
447
|
+
if (hasChanges) {
|
|
448
|
+
updateDocsChangelog(newVersion, title, changes);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// 5. CLAUDE.md and README.md
|
|
452
|
+
updateClaudeMd(newVersion);
|
|
453
|
+
updateReadme(newVersion);
|
|
454
|
+
} else {
|
|
455
|
+
log.info(' [dry-run] Would update package.json');
|
|
456
|
+
log.info(' [dry-run] Would update docs/src/state.js');
|
|
457
|
+
if (hasChanges) {
|
|
458
|
+
log.info(' [dry-run] Would update CHANGELOG.md');
|
|
459
|
+
log.info(' [dry-run] Would update docs/src/pages/ChangelogPage.js');
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
log.info('');
|
|
464
|
+
log.info('Git operations...');
|
|
465
|
+
|
|
466
|
+
if (!dryRun) {
|
|
467
|
+
if (noPush) {
|
|
468
|
+
// Only commit and tag, no push
|
|
469
|
+
execSync('git add -A', { cwd: root, stdio: 'inherit' });
|
|
470
|
+
execSync(`git commit -m "v${newVersion}\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>"`, {
|
|
471
|
+
cwd: root,
|
|
472
|
+
stdio: 'inherit',
|
|
473
|
+
shell: '/bin/bash'
|
|
474
|
+
});
|
|
475
|
+
execSync(`git tag -a v${newVersion} -m "Release v${newVersion}"`, { cwd: root, stdio: 'inherit' });
|
|
476
|
+
log.info(' Created commit and tag (--no-push specified)');
|
|
477
|
+
} else {
|
|
478
|
+
gitCommitTagPush(newVersion, false);
|
|
479
|
+
}
|
|
480
|
+
} else {
|
|
481
|
+
gitCommitTagPush(newVersion, true);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
log.info('');
|
|
485
|
+
log.info(`Release v${newVersion} complete!`);
|
|
486
|
+
log.info('');
|
|
487
|
+
|
|
488
|
+
if (!dryRun && !noPush) {
|
|
489
|
+
log.info('Next steps:');
|
|
490
|
+
log.info(` 1. Create GitHub release: https://github.com/vincenthirtz/pulse-js-framework/releases/new?tag=v${newVersion}`);
|
|
491
|
+
log.info(' 2. Publish to npm: npm publish');
|
|
492
|
+
}
|
|
493
|
+
}
|
package/compiler/parser.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { TokenType, tokenize } from './lexer.js';
|
|
8
|
+
import { ParserError, SUGGESTIONS } from '../core/errors.js';
|
|
8
9
|
|
|
9
10
|
// AST Node types
|
|
10
11
|
export const NodeType = {
|
|
@@ -122,9 +123,10 @@ export class Parser {
|
|
|
122
123
|
expect(type, message = null) {
|
|
123
124
|
if (!this.is(type)) {
|
|
124
125
|
const token = this.current();
|
|
125
|
-
throw
|
|
126
|
-
message ||
|
|
127
|
-
|
|
126
|
+
throw this.createError(
|
|
127
|
+
message || `Expected ${type} but got ${token?.type}`,
|
|
128
|
+
token,
|
|
129
|
+
{ suggestion: SUGGESTIONS['unexpected-token']?.(type, token?.type) }
|
|
128
130
|
);
|
|
129
131
|
}
|
|
130
132
|
return this.advance();
|
|
@@ -132,14 +134,19 @@ export class Parser {
|
|
|
132
134
|
|
|
133
135
|
/**
|
|
134
136
|
* Create a parse error with detailed information
|
|
137
|
+
* @param {string} message - Error message
|
|
138
|
+
* @param {Object} [token] - Token where error occurred
|
|
139
|
+
* @param {Object} [options] - Additional options (suggestion, code)
|
|
140
|
+
* @returns {ParserError} The parser error
|
|
135
141
|
*/
|
|
136
|
-
createError(message, token = null) {
|
|
142
|
+
createError(message, token = null, options = {}) {
|
|
137
143
|
const t = token || this.current();
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
144
|
+
return new ParserError(message, {
|
|
145
|
+
line: t?.line || 1,
|
|
146
|
+
column: t?.column || 1,
|
|
147
|
+
token: t,
|
|
148
|
+
...options
|
|
149
|
+
});
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
/**
|
|
@@ -180,49 +187,63 @@ export class Parser {
|
|
|
180
187
|
// Props block
|
|
181
188
|
else if (this.is(TokenType.PROPS)) {
|
|
182
189
|
if (program.props) {
|
|
183
|
-
throw this.createError('Duplicate props block - only one props block allowed per file'
|
|
190
|
+
throw this.createError('Duplicate props block - only one props block allowed per file', null, {
|
|
191
|
+
suggestion: SUGGESTIONS['duplicate-declaration']?.('props')
|
|
192
|
+
});
|
|
184
193
|
}
|
|
185
194
|
program.props = this.parsePropsBlock();
|
|
186
195
|
}
|
|
187
196
|
// State block
|
|
188
197
|
else if (this.is(TokenType.STATE)) {
|
|
189
198
|
if (program.state) {
|
|
190
|
-
throw this.createError('Duplicate state block - only one state block allowed per file'
|
|
199
|
+
throw this.createError('Duplicate state block - only one state block allowed per file', null, {
|
|
200
|
+
suggestion: SUGGESTIONS['duplicate-declaration']?.('state')
|
|
201
|
+
});
|
|
191
202
|
}
|
|
192
203
|
program.state = this.parseStateBlock();
|
|
193
204
|
}
|
|
194
205
|
// View block
|
|
195
206
|
else if (this.is(TokenType.VIEW)) {
|
|
196
207
|
if (program.view) {
|
|
197
|
-
throw this.createError('Duplicate view block - only one view block allowed per file'
|
|
208
|
+
throw this.createError('Duplicate view block - only one view block allowed per file', null, {
|
|
209
|
+
suggestion: SUGGESTIONS['duplicate-declaration']?.('view')
|
|
210
|
+
});
|
|
198
211
|
}
|
|
199
212
|
program.view = this.parseViewBlock();
|
|
200
213
|
}
|
|
201
214
|
// Actions block
|
|
202
215
|
else if (this.is(TokenType.ACTIONS)) {
|
|
203
216
|
if (program.actions) {
|
|
204
|
-
throw this.createError('Duplicate actions block - only one actions block allowed per file'
|
|
217
|
+
throw this.createError('Duplicate actions block - only one actions block allowed per file', null, {
|
|
218
|
+
suggestion: SUGGESTIONS['duplicate-declaration']?.('actions')
|
|
219
|
+
});
|
|
205
220
|
}
|
|
206
221
|
program.actions = this.parseActionsBlock();
|
|
207
222
|
}
|
|
208
223
|
// Style block
|
|
209
224
|
else if (this.is(TokenType.STYLE)) {
|
|
210
225
|
if (program.style) {
|
|
211
|
-
throw this.createError('Duplicate style block - only one style block allowed per file'
|
|
226
|
+
throw this.createError('Duplicate style block - only one style block allowed per file', null, {
|
|
227
|
+
suggestion: SUGGESTIONS['duplicate-declaration']?.('style')
|
|
228
|
+
});
|
|
212
229
|
}
|
|
213
230
|
program.style = this.parseStyleBlock();
|
|
214
231
|
}
|
|
215
232
|
// Router block
|
|
216
233
|
else if (this.is(TokenType.ROUTER)) {
|
|
217
234
|
if (program.router) {
|
|
218
|
-
throw this.createError('Duplicate router block - only one router block allowed per file'
|
|
235
|
+
throw this.createError('Duplicate router block - only one router block allowed per file', null, {
|
|
236
|
+
suggestion: SUGGESTIONS['duplicate-declaration']?.('router')
|
|
237
|
+
});
|
|
219
238
|
}
|
|
220
239
|
program.router = this.parseRouterBlock();
|
|
221
240
|
}
|
|
222
241
|
// Store block
|
|
223
242
|
else if (this.is(TokenType.STORE)) {
|
|
224
243
|
if (program.store) {
|
|
225
|
-
throw this.createError('Duplicate store block - only one store block allowed per file'
|
|
244
|
+
throw this.createError('Duplicate store block - only one store block allowed per file', null, {
|
|
245
|
+
suggestion: SUGGESTIONS['duplicate-declaration']?.('store')
|
|
246
|
+
});
|
|
226
247
|
}
|
|
227
248
|
program.store = this.parseStoreBlock();
|
|
228
249
|
}
|
|
@@ -415,8 +436,8 @@ export class Parser {
|
|
|
415
436
|
|
|
416
437
|
if (this.is(TokenType.IDENT)) return this.parseIdentifierOrExpression();
|
|
417
438
|
|
|
418
|
-
throw
|
|
419
|
-
`Unexpected token ${this.current()?.type} in value
|
|
439
|
+
throw this.createError(
|
|
440
|
+
`Unexpected token ${this.current()?.type} in value`
|
|
420
441
|
);
|
|
421
442
|
}
|
|
422
443
|
|
|
@@ -1000,8 +1021,8 @@ export class Parser {
|
|
|
1000
1021
|
return this.parseIdentifierOrExpression();
|
|
1001
1022
|
}
|
|
1002
1023
|
|
|
1003
|
-
throw
|
|
1004
|
-
`Unexpected token ${this.current()?.type} in expression
|
|
1024
|
+
throw this.createError(
|
|
1025
|
+
`Unexpected token ${this.current()?.type} in expression`
|
|
1005
1026
|
);
|
|
1006
1027
|
}
|
|
1007
1028
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transformer Constants
|
|
3
|
+
* Shared constants for the Pulse transformer modules
|
|
4
|
+
* @module pulse-js-framework/compiler/transformer/constants
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Generate a unique scope ID for CSS scoping */
|
|
8
|
+
export const generateScopeId = () => 'p' + Math.random().toString(36).substring(2, 8);
|
|
9
|
+
|
|
10
|
+
/** Token types that should not have space after them */
|
|
11
|
+
export const NO_SPACE_AFTER = new Set([
|
|
12
|
+
'DOT', 'LPAREN', 'LBRACKET', 'LBRACE', 'NOT', 'SPREAD',
|
|
13
|
+
'.', '(', '[', '{', '!', '~', '...'
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
/** Token types that should not have space before them */
|
|
17
|
+
export const NO_SPACE_BEFORE = new Set([
|
|
18
|
+
'DOT', 'RPAREN', 'RBRACKET', 'RBRACE', 'SEMICOLON', 'COMMA',
|
|
19
|
+
'INCREMENT', 'DECREMENT', 'LPAREN', 'LBRACKET',
|
|
20
|
+
'.', ')', ']', '}', ';', ',', '++', '--', '(', '['
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
/** Punctuation that should not have space before */
|
|
24
|
+
export const PUNCT_NO_SPACE_BEFORE = [
|
|
25
|
+
'DOT', 'LPAREN', 'RPAREN', 'LBRACKET', 'RBRACKET', 'SEMICOLON', 'COMMA', 'COLON'
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/** Punctuation that should not have space after */
|
|
29
|
+
export const PUNCT_NO_SPACE_AFTER = [
|
|
30
|
+
'DOT', 'LPAREN', 'LBRACKET', 'NOT', 'COLON'
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
/** JavaScript statement keywords */
|
|
34
|
+
export const STATEMENT_KEYWORDS = new Set([
|
|
35
|
+
'let', 'const', 'var', 'return', 'if', 'else', 'for', 'while',
|
|
36
|
+
'switch', 'throw', 'try', 'catch', 'finally'
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
/** Built-in JavaScript functions and objects */
|
|
40
|
+
export const BUILTIN_FUNCTIONS = new Set([
|
|
41
|
+
'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
|
|
42
|
+
'alert', 'confirm', 'prompt', 'console', 'document', 'window',
|
|
43
|
+
'Math', 'JSON', 'Date', 'Array', 'Object', 'String', 'Number',
|
|
44
|
+
'Boolean', 'Promise', 'fetch'
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
/** Token types that start statements */
|
|
48
|
+
export const STATEMENT_TOKEN_TYPES = new Set(['IF', 'FOR', 'EACH']);
|
|
49
|
+
|
|
50
|
+
/** Token types that end statements */
|
|
51
|
+
export const STATEMENT_END_TYPES = new Set([
|
|
52
|
+
'RBRACE', 'RPAREN', 'RBRACKET', 'SEMICOLON', 'STRING',
|
|
53
|
+
'NUMBER', 'TRUE', 'FALSE', 'NULL', 'IDENT'
|
|
54
|
+
]);
|