@quadient/skillshare 1.6.0 → 1.6.2
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 +16 -3
- package/dist/index.js +11 -6
- package/dist/pages-dev-proxy.js +25 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,6 +21,9 @@ bun run index.ts install --skill-version-id 42 --target ./installed
|
|
|
21
21
|
|
|
22
22
|
## Behavior
|
|
23
23
|
|
|
24
|
+
- Starting with 1.6.2, `login`, `git configure` and API commands without a stored session default to `https://skillshare.quadientcloudpreprod.eu`. `login local` and `git configure local` still select `http://localhost:5050`.
|
|
25
|
+
- Existing sessions keep their saved server. After preprod cutover, run `skillshare login` again to replace a UAT session with a preprod session; stored tokens are not reused across hosts.
|
|
26
|
+
- Uploaded/installed records retain their registry URL. Legacy records without a URL still belong to UAT and are not silently reassigned to preprod.
|
|
24
27
|
- Upload and publish commands show progress while scanning and reading local files, then keep a timed upload status visible until the registry call finishes.
|
|
25
28
|
- `update` reads the centralized install registry and refreshes recorded installs to the latest published registry versions.
|
|
26
29
|
- `update` can be narrowed to one skill or one target directory, and `--dry-run` prints planned changes without downloading or overwriting files.
|
|
@@ -36,7 +39,7 @@ flowchart TD
|
|
|
36
39
|
B -- Yes --> D{Which command?}
|
|
37
40
|
|
|
38
41
|
D -->|login local| E[login LOCAL_URL]
|
|
39
|
-
D -->|login| F[login
|
|
42
|
+
D -->|login| F[login DEFAULT_URL]
|
|
40
43
|
D -->|logout| G[logoutSession]
|
|
41
44
|
D -->|whoami list show upload install update| H[createClient using active baseUrl]
|
|
42
45
|
|
|
@@ -148,7 +151,7 @@ export SKILLSHARE_TOKEN="<personal-access-token>"
|
|
|
148
151
|
npx @quadient/skillshare whoami
|
|
149
152
|
```
|
|
150
153
|
|
|
151
|
-
Register the
|
|
154
|
+
Register the default preprod or local SkillShare host as a generic OAuth provider in Git Credential
|
|
152
155
|
Manager 2.0.931 or newer:
|
|
153
156
|
|
|
154
157
|
```bash
|
|
@@ -179,7 +182,7 @@ npx @quadient/skillshare pages show release-notes
|
|
|
179
182
|
```
|
|
180
183
|
|
|
181
184
|
The page is then available at
|
|
182
|
-
`https://skillshare.
|
|
185
|
+
`https://skillshare.quadientcloudpreprod.eu/p/release-notes/` after SSO sign-in.
|
|
183
186
|
|
|
184
187
|
## Package Distribution
|
|
185
188
|
|
|
@@ -276,3 +279,13 @@ This is full-page reload, not React HMR. A CSP that disallows same-origin script
|
|
|
276
279
|
or connections must be adjusted for local development or the page reloaded manually.
|
|
277
280
|
The `/__skillshare_dev/` path is reserved for local development controls. This command
|
|
278
281
|
does not implement the SkillShare catalog, actual SSO or cloud deployment.
|
|
282
|
+
|
|
283
|
+
### Worker user directory (1.6.1)
|
|
284
|
+
|
|
285
|
+
Authenticated requests in `pages dev` include `X-Skillshare-Users-Url` and
|
|
286
|
+
`X-Skillshare-Users-Token`. A Worker can fetch the URL with the token in the
|
|
287
|
+
`X-Skillshare-Users-Token` request header to read the four local users. Each entry
|
|
288
|
+
contains `id`, `displayName`, `email`, `isAdmin` and `isOwner`. The hosted backend
|
|
289
|
+
uses the same contract for active SkillShare users. These capabilities stay in
|
|
290
|
+
the Worker and are not injected on `api/bypass-sso/*` requests. See the
|
|
291
|
+
[Worker example](../docs/pages-backends.md#skillshare-user-directory-from-a-worker).
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,9 @@ import { mkdir, open, readFile, readdir, rm, stat, writeFile } from 'node:fs/pro
|
|
|
8
8
|
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
9
9
|
import { homedir } from 'node:os';
|
|
10
10
|
import { cancel, confirm, intro, isCancel, multiselect, outro, progress, select, spinner, text } from '@clack/prompts';
|
|
11
|
-
const
|
|
11
|
+
const DEFAULT_URL = 'https://skillshare.quadientcloudpreprod.eu';
|
|
12
|
+
// Records without a registry URL were created against UAT before the preprod migration.
|
|
13
|
+
const LEGACY_UAT_URL = 'https://skillshare.inspireclouduat.net';
|
|
12
14
|
const LOCAL_URL = 'http://localhost:5050';
|
|
13
15
|
const APP_DIR = getAppDir();
|
|
14
16
|
const SESSION_PATH = join(APP_DIR, 'session.json');
|
|
@@ -91,6 +93,9 @@ Commands:
|
|
|
91
93
|
|
|
92
94
|
Options:
|
|
93
95
|
--token <token> Bearer token for API requests (overrides SKILLSHARE_TOKEN)
|
|
96
|
+
|
|
97
|
+
Default server: ${DEFAULT_URL}
|
|
98
|
+
A stored session keeps its server; run "skillshare login" to switch to the default.
|
|
94
99
|
`;
|
|
95
100
|
async function main() {
|
|
96
101
|
const args = process.argv.slice(2);
|
|
@@ -110,7 +115,7 @@ async function main() {
|
|
|
110
115
|
}
|
|
111
116
|
switch (command) {
|
|
112
117
|
case 'login': {
|
|
113
|
-
await login(options.positionals[0] === 'local' ? LOCAL_URL :
|
|
118
|
+
await login(options.positionals[0] === 'local' ? LOCAL_URL : DEFAULT_URL);
|
|
114
119
|
return;
|
|
115
120
|
}
|
|
116
121
|
case 'logout': {
|
|
@@ -119,7 +124,7 @@ async function main() {
|
|
|
119
124
|
}
|
|
120
125
|
case 'git': {
|
|
121
126
|
const action = options.positionals[0];
|
|
122
|
-
const baseUrl = options.positionals[1] === 'local' ? LOCAL_URL :
|
|
127
|
+
const baseUrl = options.positionals[1] === 'local' ? LOCAL_URL : DEFAULT_URL;
|
|
123
128
|
if (action === 'configure') {
|
|
124
129
|
await configureGitCredentialManager(baseUrl);
|
|
125
130
|
return;
|
|
@@ -1455,7 +1460,7 @@ async function resolveActiveBaseUrl() {
|
|
|
1455
1460
|
if (session?.baseUrl) {
|
|
1456
1461
|
return normalizeBaseUrl(session.baseUrl);
|
|
1457
1462
|
}
|
|
1458
|
-
return
|
|
1463
|
+
return DEFAULT_URL;
|
|
1459
1464
|
}
|
|
1460
1465
|
async function createClient(baseUrl, explicitToken) {
|
|
1461
1466
|
const resolvedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
@@ -1823,7 +1828,7 @@ async function readUploadedSkillRecords() {
|
|
|
1823
1828
|
const normalizedSkillId = Number.isInteger(skillId) && skillId > 0 ? skillId : undefined;
|
|
1824
1829
|
return [{
|
|
1825
1830
|
directory: typeof entry.directory === 'string' ? resolve(entry.directory) : '',
|
|
1826
|
-
baseUrl: typeof entry.baseUrl === 'string' ? normalizeBaseUrl(entry.baseUrl) :
|
|
1831
|
+
baseUrl: typeof entry.baseUrl === 'string' ? normalizeBaseUrl(entry.baseUrl) : LEGACY_UAT_URL,
|
|
1827
1832
|
updatedAt: typeof entry.updatedAt === 'string' ? entry.updatedAt : '',
|
|
1828
1833
|
skillId: normalizedSkillId,
|
|
1829
1834
|
skillName: typeof entry.skillName === 'string' ? entry.skillName : '',
|
|
@@ -1844,7 +1849,7 @@ async function readInstalledSkillRecords() {
|
|
|
1844
1849
|
}
|
|
1845
1850
|
return [{
|
|
1846
1851
|
targetRoot: typeof entry.targetRoot === 'string' ? resolve(entry.targetRoot) : '',
|
|
1847
|
-
baseUrl: typeof entry.baseUrl === 'string' ? normalizeBaseUrl(entry.baseUrl) :
|
|
1852
|
+
baseUrl: typeof entry.baseUrl === 'string' ? normalizeBaseUrl(entry.baseUrl) : LEGACY_UAT_URL,
|
|
1848
1853
|
updatedAt: typeof entry.updatedAt === 'string' ? entry.updatedAt : '',
|
|
1849
1854
|
skillId,
|
|
1850
1855
|
skillName: typeof entry.skillName === 'string' ? entry.skillName : '',
|
package/dist/pages-dev-proxy.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createServer, request as httpRequest, } from 'node:http';
|
|
2
|
+
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
3
|
const sessionCookie = 'skillshare.dev-user';
|
|
3
4
|
const internal = '/__skillshare_dev';
|
|
4
5
|
const hostCookie = (name) => name.startsWith('skillshare.auth') ||
|
|
@@ -25,7 +26,7 @@ function cleanHeaders(headers) {
|
|
|
25
26
|
delete result[name];
|
|
26
27
|
return result;
|
|
27
28
|
}
|
|
28
|
-
export function requestHeaders(request, user, baseUrl, slug) {
|
|
29
|
+
export function requestHeaders(request, user, baseUrl, slug, usersToken) {
|
|
29
30
|
const headers = cleanHeaders(request.headers);
|
|
30
31
|
for (const name of Object.keys(headers))
|
|
31
32
|
if (name.startsWith('x-skillshare-') ||
|
|
@@ -37,6 +38,10 @@ export function requestHeaders(request, user, baseUrl, slug) {
|
|
|
37
38
|
headers['accept-encoding'] = 'identity';
|
|
38
39
|
if (user) {
|
|
39
40
|
delete headers.authorization;
|
|
41
|
+
if (usersToken) {
|
|
42
|
+
headers['x-skillshare-users-url'] = new URL(`/api/pages/${encodeURIComponent(slug)}/users-directory`, baseUrl).href;
|
|
43
|
+
headers['x-skillshare-users-token'] = usersToken;
|
|
44
|
+
}
|
|
40
45
|
headers['x-skillshare-user'] = Buffer.from(JSON.stringify(user)).toString('base64');
|
|
41
46
|
const cookies = (request.headers.cookie ?? '')
|
|
42
47
|
.split(';')
|
|
@@ -74,6 +79,7 @@ export function responseCookies(cookies, prefix) {
|
|
|
74
79
|
}
|
|
75
80
|
export function createDevProxy(options) {
|
|
76
81
|
const prefix = `/p/${options.slug}`;
|
|
82
|
+
const usersToken = randomBytes(32).toString('hex');
|
|
77
83
|
const streams = new Set();
|
|
78
84
|
const upgrades = new Set();
|
|
79
85
|
const userFor = (request) => {
|
|
@@ -98,6 +104,22 @@ export function createDevProxy(options) {
|
|
|
98
104
|
return;
|
|
99
105
|
}
|
|
100
106
|
const url = new URL(request.url, `http://${request.headers.host}`);
|
|
107
|
+
if (url.pathname === `/api/pages/${encodeURIComponent(options.slug)}/users-directory`) {
|
|
108
|
+
response.setHeader('Cache-Control', 'no-store');
|
|
109
|
+
const token = request.headers['x-skillshare-users-token'];
|
|
110
|
+
if (typeof token !== 'string' || !/^[a-f0-9]{64}$/.test(token) ||
|
|
111
|
+
!timingSafeEqual(Buffer.from(token), Buffer.from(usersToken))) {
|
|
112
|
+
response.writeHead(401).end();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (request.method !== 'GET') {
|
|
116
|
+
response.writeHead(405).end();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
response.writeHead(200, { 'Content-Type': 'application/json' });
|
|
120
|
+
response.end(JSON.stringify(options.users.map(({ id, displayName, email, isAdmin, isOwner }) => ({ id, displayName, email, isAdmin, isOwner }))));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
101
123
|
if (url.pathname === '/' || url.pathname === prefix) {
|
|
102
124
|
response.writeHead(307, { Location: prefix + '/' + url.search }).end();
|
|
103
125
|
return;
|
|
@@ -188,7 +210,7 @@ label.append(select); bar.append(label); document.body.append(bar);
|
|
|
188
210
|
const user = bypass ? undefined : userFor(request);
|
|
189
211
|
const upstream = httpRequest(new URL(path + url.search, target), {
|
|
190
212
|
method: request.method,
|
|
191
|
-
headers: requestHeaders(request, user, url.origin + prefix + '/', options.slug),
|
|
213
|
+
headers: requestHeaders(request, user, url.origin + prefix + '/', options.slug, usersToken),
|
|
192
214
|
}, (incoming) => {
|
|
193
215
|
const headers = cleanHeaders(incoming.headers);
|
|
194
216
|
headers['cache-control'] = 'private, no-cache';
|
|
@@ -251,7 +273,7 @@ label.append(select); bar.append(label); document.body.append(bar);
|
|
|
251
273
|
return;
|
|
252
274
|
}
|
|
253
275
|
const bypass = path === '/api/bypass-sso' || path.startsWith('/api/bypass-sso/');
|
|
254
|
-
const headers = requestHeaders(request, bypass ? undefined : userFor(request), url.origin + prefix + '/', options.slug);
|
|
276
|
+
const headers = requestHeaders(request, bypass ? undefined : userFor(request), url.origin + prefix + '/', options.slug, usersToken);
|
|
255
277
|
headers.connection = 'Upgrade';
|
|
256
278
|
headers.upgrade = request.headers.upgrade;
|
|
257
279
|
const upstream = httpRequest(new URL(path + url.search, target), {
|