@girardmedia/bootspring 2.2.0 → 2.2.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/bin/bootspring.js +127 -73
- package/claude-commands/agent.md +34 -0
- package/claude-commands/bs.md +31 -0
- package/claude-commands/build.md +25 -0
- package/claude-commands/skill.md +31 -0
- package/claude-commands/todo.md +25 -0
- package/dist/core/index.d.ts +5814 -0
- package/dist/core.js +5779 -0
- package/dist/index.js +93883 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp-server.js +2298 -0
- package/package.json +22 -55
- package/core/api-client.d.ts +0 -69
- package/core/api-client.js +0 -1482
- package/core/auth.d.ts +0 -98
- package/core/auth.js +0 -737
- package/core/build-orchestrator.js +0 -508
- package/core/build-state.js +0 -612
- package/core/config.d.ts +0 -106
- package/core/config.js +0 -1328
- package/core/context-loader.js +0 -580
- package/core/context.d.ts +0 -61
- package/core/context.js +0 -327
- package/core/entitlements.d.ts +0 -70
- package/core/entitlements.js +0 -322
- package/core/index.d.ts +0 -53
- package/core/index.js +0 -62
- package/core/mcp-config.js +0 -115
- package/core/policies.d.ts +0 -43
- package/core/policies.js +0 -113
- package/core/policy-matrix.js +0 -303
- package/core/project-activity.js +0 -175
- package/core/redaction.d.ts +0 -5
- package/core/redaction.js +0 -63
- package/core/self-update.js +0 -259
- package/core/session.js +0 -353
- package/core/task-extractor.js +0 -1098
- package/core/telemetry.d.ts +0 -55
- package/core/telemetry.js +0 -617
- package/core/tier-enforcement.js +0 -928
- package/core/utils.d.ts +0 -90
- package/core/utils.js +0 -455
- package/core/validation.js +0 -572
- package/mcp/server.d.ts +0 -57
- package/mcp/server.js +0 -264
package/core/project-activity.js
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Project collaboration activity + notification helpers.
|
|
3
|
-
*
|
|
4
|
-
* Uses telemetry JSONL as an append-only audit source.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const crypto = require('crypto');
|
|
8
|
-
|
|
9
|
-
const NOTIFICATION_EVENT = 'project_membership_notification';
|
|
10
|
-
const DEFAULT_ACTIVITY_LIMIT = 25;
|
|
11
|
-
const MAX_ACTIVITY_SCAN = 500;
|
|
12
|
-
const KNOWN_ACTIVITY_EVENTS = new Set([
|
|
13
|
-
NOTIFICATION_EVENT,
|
|
14
|
-
'project_invitation_sent',
|
|
15
|
-
'project_invitation_accepted',
|
|
16
|
-
'project_invitation_declined',
|
|
17
|
-
'project_member_added',
|
|
18
|
-
'project_member_removed',
|
|
19
|
-
'project_member_role_updated',
|
|
20
|
-
'project_owner_transferred'
|
|
21
|
-
]);
|
|
22
|
-
|
|
23
|
-
function getTelemetry() {
|
|
24
|
-
return require('./telemetry');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function sanitizeToken(value, fallback) {
|
|
28
|
-
const normalized = String(value || '')
|
|
29
|
-
.trim()
|
|
30
|
-
.toLowerCase()
|
|
31
|
-
.replace(/[^a-z0-9_-]+/g, '-')
|
|
32
|
-
.replace(/^-+|-+$/g, '');
|
|
33
|
-
return normalized || fallback;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function projectIdFromPayload(payload) {
|
|
37
|
-
return String(payload.projectId || payload.project || payload.projectSlug || '').trim();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function projectScopeFromProjectId(projectId) {
|
|
41
|
-
if (!projectId) return '';
|
|
42
|
-
return `ps_${crypto.createHash('sha256').update(projectId).digest('hex').slice(0, 16)}`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function projectScopeFromPayload(payload) {
|
|
46
|
-
return String(payload.projectScope || payload.project_scope || '').trim();
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function describeEvent(event, payload = {}) {
|
|
50
|
-
const email = String(payload.email || payload.targetEmail || payload.target || '').trim();
|
|
51
|
-
const role = String(payload.role || '').trim();
|
|
52
|
-
const target = String(payload.targetUserId || payload.userId || '').trim();
|
|
53
|
-
|
|
54
|
-
if (event === 'project_invitation_sent') {
|
|
55
|
-
return email ? `Invitation sent to ${email}${role ? ` (${role})` : ''}` : 'Invitation sent';
|
|
56
|
-
}
|
|
57
|
-
if (event === 'project_invitation_accepted') {
|
|
58
|
-
return email ? `${email} accepted invitation` : 'Invitation accepted';
|
|
59
|
-
}
|
|
60
|
-
if (event === 'project_invitation_declined') {
|
|
61
|
-
return email ? `${email} declined invitation` : 'Invitation declined';
|
|
62
|
-
}
|
|
63
|
-
if (event === 'project_member_added') {
|
|
64
|
-
return email ? `Member added: ${email}${role ? ` (${role})` : ''}` : 'Member added';
|
|
65
|
-
}
|
|
66
|
-
if (event === 'project_member_removed') {
|
|
67
|
-
return target ? `Member removed: ${target}` : 'Member removed';
|
|
68
|
-
}
|
|
69
|
-
if (event === 'project_member_role_updated') {
|
|
70
|
-
return target ? `Member role updated: ${target}${role ? ` -> ${role}` : ''}` : 'Member role updated';
|
|
71
|
-
}
|
|
72
|
-
if (event === 'project_owner_transferred') {
|
|
73
|
-
const owner = String(payload.newOwnerId || target || '').trim();
|
|
74
|
-
return owner ? `Ownership transferred to ${owner}` : 'Ownership transferred';
|
|
75
|
-
}
|
|
76
|
-
return String(payload.message || event).trim();
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function toActivityEntry(record) {
|
|
80
|
-
const payload = record.payload || {};
|
|
81
|
-
const projectId = projectIdFromPayload(payload);
|
|
82
|
-
return {
|
|
83
|
-
timestamp: String(record.timestamp || ''),
|
|
84
|
-
projectId,
|
|
85
|
-
projectScope: projectScopeFromPayload(payload),
|
|
86
|
-
event: String(record.event || ''),
|
|
87
|
-
action: sanitizeToken(payload.action || record.event, 'project_activity'),
|
|
88
|
-
actor: String(payload.actor || payload.actorId || 'system'),
|
|
89
|
-
target: String(payload.target || payload.email || payload.targetUserId || ''),
|
|
90
|
-
message: describeEvent(String(record.event || ''), payload),
|
|
91
|
-
metadata: payload
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function trackProjectActivity(event, payload = {}, options = {}) {
|
|
96
|
-
const normalizedEvent = sanitizeToken(event, 'project_activity');
|
|
97
|
-
const projectScope = projectScopeFromProjectId(projectIdFromPayload(payload));
|
|
98
|
-
const telemetry = getTelemetry();
|
|
99
|
-
telemetry.emitEvent(normalizedEvent, {
|
|
100
|
-
...payload,
|
|
101
|
-
...(projectScope ? { projectScope } : {}),
|
|
102
|
-
action: normalizedEvent,
|
|
103
|
-
occurredAt: options.now ? new Date(options.now).toISOString() : new Date().toISOString()
|
|
104
|
-
}, options);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function trackMembershipNotification(payload = {}, options = {}) {
|
|
108
|
-
const event = String(payload.event || '').trim();
|
|
109
|
-
const type = sanitizeToken(payload.type || event || 'membership_update', 'membership_update');
|
|
110
|
-
const message = String(payload.message || describeEvent(event || type, payload)).trim() || 'Membership updated';
|
|
111
|
-
const projectScope = projectScopeFromProjectId(projectIdFromPayload(payload));
|
|
112
|
-
|
|
113
|
-
const telemetry = getTelemetry();
|
|
114
|
-
telemetry.emitEvent(NOTIFICATION_EVENT, {
|
|
115
|
-
...payload,
|
|
116
|
-
...(projectScope ? { projectScope } : {}),
|
|
117
|
-
type,
|
|
118
|
-
message,
|
|
119
|
-
occurredAt: options.now ? new Date(options.now).toISOString() : new Date().toISOString()
|
|
120
|
-
}, options);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function getProjectActivityFeed(options = {}) {
|
|
124
|
-
const telemetry = getTelemetry();
|
|
125
|
-
const limit = Number(options.limit);
|
|
126
|
-
const effectiveLimit = Number.isFinite(limit) && limit > 0 ? Math.min(limit, MAX_ACTIVITY_SCAN) : DEFAULT_ACTIVITY_LIMIT;
|
|
127
|
-
|
|
128
|
-
const records = telemetry.listEvents({
|
|
129
|
-
projectRoot: options.projectRoot,
|
|
130
|
-
limit: MAX_ACTIVITY_SCAN
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
const projectId = String(options.projectId || '').trim();
|
|
134
|
-
const projectScope = projectScopeFromProjectId(projectId);
|
|
135
|
-
return records
|
|
136
|
-
.filter(record => KNOWN_ACTIVITY_EVENTS.has(String(record.event || '')))
|
|
137
|
-
.map(toActivityEntry)
|
|
138
|
-
.filter(entry => {
|
|
139
|
-
if (!projectId) return true;
|
|
140
|
-
if (entry.projectId === projectId) return true;
|
|
141
|
-
return !!projectScope && entry.projectScope === projectScope;
|
|
142
|
-
})
|
|
143
|
-
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
|
|
144
|
-
.slice(0, effectiveLimit);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function getMembershipNotifications(options = {}) {
|
|
148
|
-
const limit = Number(options.limit);
|
|
149
|
-
const effectiveLimit = Number.isFinite(limit) && limit > 0 ? Math.min(limit, MAX_ACTIVITY_SCAN) : DEFAULT_ACTIVITY_LIMIT;
|
|
150
|
-
|
|
151
|
-
return getProjectActivityFeed({
|
|
152
|
-
projectId: options.projectId,
|
|
153
|
-
limit: MAX_ACTIVITY_SCAN,
|
|
154
|
-
projectRoot: options.projectRoot
|
|
155
|
-
})
|
|
156
|
-
.filter(entry => entry.event === NOTIFICATION_EVENT || entry.event.startsWith('project_invitation_') || entry.event.startsWith('project_member_') || entry.event === 'project_owner_transferred')
|
|
157
|
-
.map(entry => ({
|
|
158
|
-
timestamp: entry.timestamp,
|
|
159
|
-
projectId: entry.projectId,
|
|
160
|
-
event: entry.event,
|
|
161
|
-
type: sanitizeToken(entry.metadata.type || entry.action, 'membership_update'),
|
|
162
|
-
message: String(entry.metadata.message || entry.message || 'Membership updated'),
|
|
163
|
-
actor: entry.actor,
|
|
164
|
-
target: entry.target,
|
|
165
|
-
metadata: entry.metadata
|
|
166
|
-
}))
|
|
167
|
-
.slice(0, effectiveLimit);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
module.exports = {
|
|
171
|
-
trackProjectActivity,
|
|
172
|
-
trackMembershipNotification,
|
|
173
|
-
getProjectActivityFeed,
|
|
174
|
-
getMembershipNotifications
|
|
175
|
-
};
|
package/core/redaction.d.ts
DELETED
package/core/redaction.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Centralized sensitive-data redaction helpers for runtime JS modules.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const REDACTED = '[REDACTED]';
|
|
6
|
-
|
|
7
|
-
const SENSITIVE_KEY_PATTERN = /(?:^|[_-])(api[_-]?key|token|refresh[_-]?token|authorization|x[_-]?api[_-]?key|project[_-]?id)$/i;
|
|
8
|
-
|
|
9
|
-
function redactPatternMatches(value) {
|
|
10
|
-
return value
|
|
11
|
-
.replace(/\b(?:bs|sk)_(?:live|test)_[A-Za-z0-9_-]{8,}\b/g, REDACTED)
|
|
12
|
-
.replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, REDACTED)
|
|
13
|
-
.replace(/\bBearer\s+[A-Za-z0-9._-]+\b/gi, `Bearer ${REDACTED}`)
|
|
14
|
-
.replace(/\bproj_[A-Za-z0-9_-]{6,}\b/g, `proj_${REDACTED}`)
|
|
15
|
-
.replace(/(["']?(?:authorization|x-api-key|apiKey|token|refreshToken|projectId)["']?\s*[:=]\s*["']?)([^"',\s}]+)/gi, `$1${REDACTED}`);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function redactSensitiveString(value) {
|
|
19
|
-
return redactPatternMatches(String(value || ''));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function redactSensitiveData(input, depth = 0) {
|
|
23
|
-
if (depth > 10) {
|
|
24
|
-
return input;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (typeof input === 'string') {
|
|
28
|
-
return redactSensitiveString(input);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (Array.isArray(input)) {
|
|
32
|
-
return input.map(item => redactSensitiveData(item, depth + 1));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (!input || typeof input !== 'object') {
|
|
36
|
-
return input;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const output = {};
|
|
40
|
-
for (const [key, value] of Object.entries(input)) {
|
|
41
|
-
if (SENSITIVE_KEY_PATTERN.test(key)) {
|
|
42
|
-
output[key] = REDACTED;
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
output[key] = redactSensitiveData(value, depth + 1);
|
|
46
|
-
}
|
|
47
|
-
return output;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function redactErrorMessage(error) {
|
|
51
|
-
if (!error) return '';
|
|
52
|
-
if (error instanceof Error) {
|
|
53
|
-
return redactSensitiveString(error.message || String(error));
|
|
54
|
-
}
|
|
55
|
-
return redactSensitiveString(String(error));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
module.exports = {
|
|
59
|
-
REDACTED,
|
|
60
|
-
redactSensitiveString,
|
|
61
|
-
redactSensitiveData,
|
|
62
|
-
redactErrorMessage
|
|
63
|
-
};
|
package/core/self-update.js
DELETED
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const os = require('os');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const { execFileSync, spawnSync } = require('child_process');
|
|
5
|
-
const { PACKAGE_NAME } = require('./mcp-config');
|
|
6
|
-
|
|
7
|
-
const pkg = require('../package.json');
|
|
8
|
-
const CURRENT_VERSION = pkg.version || '0.0.0';
|
|
9
|
-
const DEFAULT_INTERVAL_MS = Number.parseInt(
|
|
10
|
-
process.env.BOOTSPRING_AUTO_UPDATE_INTERVAL_MS || `${6 * 60 * 60 * 1000}`,
|
|
11
|
-
10
|
|
12
|
-
);
|
|
13
|
-
const STATE_PATH = path.join(os.homedir(), '.bootspring', 'update-state.json');
|
|
14
|
-
|
|
15
|
-
function getNpmCommand() {
|
|
16
|
-
if (process.env.BOOTSPRING_NPM_COMMAND) {
|
|
17
|
-
return process.env.BOOTSPRING_NPM_COMMAND;
|
|
18
|
-
}
|
|
19
|
-
return process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function compareVersions(a, b) {
|
|
23
|
-
const aParts = String(a || '0.0.0').split('.').map((part) => Number.parseInt(part, 10) || 0);
|
|
24
|
-
const bParts = String(b || '0.0.0').split('.').map((part) => Number.parseInt(part, 10) || 0);
|
|
25
|
-
|
|
26
|
-
for (let index = 0; index < 3; index += 1) {
|
|
27
|
-
if ((aParts[index] || 0) < (bParts[index] || 0)) {
|
|
28
|
-
return -1;
|
|
29
|
-
}
|
|
30
|
-
if ((aParts[index] || 0) > (bParts[index] || 0)) {
|
|
31
|
-
return 1;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return 0;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function readState() {
|
|
39
|
-
try {
|
|
40
|
-
return JSON.parse(fs.readFileSync(STATE_PATH, 'utf8'));
|
|
41
|
-
} catch {
|
|
42
|
-
return {};
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function writeState(nextState) {
|
|
47
|
-
try {
|
|
48
|
-
fs.mkdirSync(path.dirname(STATE_PATH), { recursive: true, mode: 0o700 });
|
|
49
|
-
fs.writeFileSync(STATE_PATH, JSON.stringify(nextState, null, 2));
|
|
50
|
-
} catch {
|
|
51
|
-
// Best-effort cache only.
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function getInstallContext() {
|
|
56
|
-
const packageRoot = path.resolve(__dirname, '..');
|
|
57
|
-
const scriptPath = path.resolve(process.argv[1] || path.join(packageRoot, 'bin', 'bootspring.js'));
|
|
58
|
-
const nodeModulesSegment = `${path.sep}node_modules${path.sep}`;
|
|
59
|
-
const forcedMode = process.env.BOOTSPRING_AUTO_UPDATE_INSTALL_MODE;
|
|
60
|
-
|
|
61
|
-
if (forcedMode === 'global' || forcedMode === 'local') {
|
|
62
|
-
const projectRoot = forcedMode === 'local'
|
|
63
|
-
? process.env.BOOTSPRING_AUTO_UPDATE_PROJECT_ROOT || process.cwd()
|
|
64
|
-
: null;
|
|
65
|
-
return { mode: forcedMode, packageRoot, projectRoot, scriptPath };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (packageRoot.includes(`${path.sep}_npx${path.sep}`) || scriptPath.includes(`${path.sep}_npx${path.sep}`)) {
|
|
69
|
-
return { mode: 'ephemeral', packageRoot, projectRoot: null, scriptPath };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (!packageRoot.includes(nodeModulesSegment)) {
|
|
73
|
-
return { mode: 'development', packageRoot, projectRoot: null, scriptPath };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (
|
|
77
|
-
scriptPath.includes(`${nodeModulesSegment}.bin${path.sep}`) ||
|
|
78
|
-
scriptPath.includes(`${nodeModulesSegment}@girardmedia${path.sep}bootspring${path.sep}bin${path.sep}`)
|
|
79
|
-
) {
|
|
80
|
-
const [projectRoot] = packageRoot.split(nodeModulesSegment);
|
|
81
|
-
return { mode: 'local', packageRoot, projectRoot: projectRoot || process.cwd(), scriptPath };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return { mode: 'global', packageRoot, projectRoot: null, scriptPath };
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function shouldSkipAutoUpdate(args = []) {
|
|
88
|
-
if (
|
|
89
|
-
process.env.BOOTSPRING_SKIP_AUTO_UPDATE === 'true' ||
|
|
90
|
-
process.env.BOOTSPRING_AUTO_UPDATE_APPLIED === 'true' ||
|
|
91
|
-
process.env.NODE_ENV === 'test' ||
|
|
92
|
-
process.env.CI
|
|
93
|
-
) {
|
|
94
|
-
return true;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const tokens = Array.isArray(args) ? args.filter(Boolean) : [];
|
|
98
|
-
if (tokens.length === 0) {
|
|
99
|
-
return true;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
tokens[0] === 'help' ||
|
|
104
|
-
tokens[0] === 'update' ||
|
|
105
|
-
tokens[0] === '--version' ||
|
|
106
|
-
tokens[0] === '-v' ||
|
|
107
|
-
tokens.includes('--help') ||
|
|
108
|
-
tokens.includes('-h')
|
|
109
|
-
) {
|
|
110
|
-
return true;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const context = getInstallContext();
|
|
114
|
-
if (context.mode === 'ephemeral') {
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (context.mode === 'development' && process.env.BOOTSPRING_ALLOW_DEV_AUTO_UPDATE !== 'true') {
|
|
119
|
-
return true;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function getLatestVersion() {
|
|
126
|
-
try {
|
|
127
|
-
const output = execFileSync(
|
|
128
|
-
getNpmCommand(),
|
|
129
|
-
['view', PACKAGE_NAME, 'version', '--json'],
|
|
130
|
-
{
|
|
131
|
-
encoding: 'utf8',
|
|
132
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
133
|
-
timeout: 10000,
|
|
134
|
-
env: {
|
|
135
|
-
...process.env,
|
|
136
|
-
npm_config_update_notifier: 'false'
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
).trim();
|
|
140
|
-
|
|
141
|
-
if (!output) {
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return JSON.parse(output);
|
|
146
|
-
} catch {
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function applyUpdate(context) {
|
|
152
|
-
const args = context.mode === 'local'
|
|
153
|
-
? ['install', `${PACKAGE_NAME}@latest`]
|
|
154
|
-
: ['install', '-g', `${PACKAGE_NAME}@latest`];
|
|
155
|
-
|
|
156
|
-
execFileSync(getNpmCommand(), args, {
|
|
157
|
-
cwd: context.projectRoot || process.cwd(),
|
|
158
|
-
encoding: 'utf8',
|
|
159
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
160
|
-
timeout: 120000,
|
|
161
|
-
env: {
|
|
162
|
-
...process.env,
|
|
163
|
-
npm_config_update_notifier: 'false'
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function relaunch(args = []) {
|
|
169
|
-
const result = spawnSync(process.execPath, [process.argv[1], ...args], {
|
|
170
|
-
stdio: 'inherit',
|
|
171
|
-
env: {
|
|
172
|
-
...process.env,
|
|
173
|
-
BOOTSPRING_AUTO_UPDATE_APPLIED: 'true'
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
if (result.error) {
|
|
178
|
-
throw result.error;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return typeof result.status === 'number' ? result.status : 1;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function checkForUpdates(options = {}) {
|
|
185
|
-
const latestVersion = getLatestVersion();
|
|
186
|
-
const currentVersion = options.currentVersion || CURRENT_VERSION;
|
|
187
|
-
return {
|
|
188
|
-
current: currentVersion,
|
|
189
|
-
latest: latestVersion,
|
|
190
|
-
updateAvailable: Boolean(latestVersion) && compareVersions(currentVersion, latestVersion) < 0
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function ensureLatestVersion(args = []) {
|
|
195
|
-
if (shouldSkipAutoUpdate(args)) {
|
|
196
|
-
return { updated: false, skipped: true };
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const context = getInstallContext();
|
|
200
|
-
const state = readState();
|
|
201
|
-
const lastCheckedAt = Date.parse(state.lastCheckedAt || '');
|
|
202
|
-
const now = Date.now();
|
|
203
|
-
const cacheFresh = Number.isFinite(lastCheckedAt) && now - lastCheckedAt < DEFAULT_INTERVAL_MS;
|
|
204
|
-
|
|
205
|
-
let latestVersion = state.latestVersion || null;
|
|
206
|
-
if (!cacheFresh || state.currentVersion !== CURRENT_VERSION) {
|
|
207
|
-
latestVersion = getLatestVersion();
|
|
208
|
-
writeState({
|
|
209
|
-
...state,
|
|
210
|
-
currentVersion: CURRENT_VERSION,
|
|
211
|
-
latestVersion,
|
|
212
|
-
lastCheckedAt: new Date(now).toISOString()
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (!latestVersion || compareVersions(CURRENT_VERSION, latestVersion) >= 0) {
|
|
217
|
-
return { updated: false, skipped: false, current: CURRENT_VERSION, latest: latestVersion };
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const installTarget = context.mode === 'local' ? context.projectRoot || process.cwd() : 'global install';
|
|
221
|
-
console.error(`[bootspring] Updating ${CURRENT_VERSION} -> ${latestVersion} before continuing (${installTarget}).`);
|
|
222
|
-
|
|
223
|
-
try {
|
|
224
|
-
applyUpdate(context);
|
|
225
|
-
writeState({
|
|
226
|
-
...readState(),
|
|
227
|
-
currentVersion: latestVersion,
|
|
228
|
-
latestVersion,
|
|
229
|
-
lastCheckedAt: new Date(now).toISOString(),
|
|
230
|
-
lastUpdatedAt: new Date().toISOString()
|
|
231
|
-
});
|
|
232
|
-
return {
|
|
233
|
-
updated: true,
|
|
234
|
-
current: CURRENT_VERSION,
|
|
235
|
-
latest: latestVersion,
|
|
236
|
-
exitCode: relaunch(args)
|
|
237
|
-
};
|
|
238
|
-
} catch (error) {
|
|
239
|
-
console.error(`[bootspring] Auto-update failed: ${error.message}`);
|
|
240
|
-
return {
|
|
241
|
-
updated: false,
|
|
242
|
-
skipped: false,
|
|
243
|
-
current: CURRENT_VERSION,
|
|
244
|
-
latest: latestVersion,
|
|
245
|
-
error
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
module.exports = {
|
|
251
|
-
PACKAGE_NAME,
|
|
252
|
-
CURRENT_VERSION,
|
|
253
|
-
compareVersions,
|
|
254
|
-
getInstallContext,
|
|
255
|
-
getLatestVersion,
|
|
256
|
-
checkForUpdates,
|
|
257
|
-
ensureLatestVersion,
|
|
258
|
-
applyUpdate
|
|
259
|
-
};
|