@quadient/skillshare 1.4.0 → 1.5.0

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 CHANGED
@@ -209,3 +209,11 @@ bun run publish:tui -- --otp 123456
209
209
  ```
210
210
 
211
211
  The publish script builds `tui/`, validates the packed files with `npm pack --dry-run`, and only then runs `npm publish`.
212
+
213
+ ## Pages applications
214
+
215
+ `skillshare pages upload <directory> --slug <slug>` accepts static sites or
216
+ Wrangler JSON/JSONC projects. The CLI bundles Worker dependencies locally; install
217
+ them and build frontend assets before uploading. Inspect process status and logs
218
+ with `skillshare pages runtime <slug>`. Pages are served below `/p/<slug>/`, so use
219
+ that base path when building assets. Runtime data survives content replacement.
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { preparePageFiles } from './pages.js';
1
2
  import { createServer } from 'node:http';
2
3
  import { createHash, randomBytes } from 'node:crypto';
3
4
  import { spawn } from 'node:child_process';
@@ -280,15 +281,15 @@ async function main() {
280
281
  if (action === 'list') {
281
282
  const pages = await client.get('/api/pages');
282
283
  for (const page of pages) {
283
- console.log(`${page.slug}\t${page.fileCount}\t${page.ownerDisplayName}`);
284
+ console.log(`${page.slug}\t${page.kind}\t${page.fileCount}\t${page.ownerDisplayName}`);
284
285
  }
285
286
  return;
286
287
  }
287
- if (action === 'show') {
288
+ if (action === 'show' || action === 'runtime') {
288
289
  const slug = options.positionals[1];
289
290
  if (!slug)
290
- throw new Error('pages show requires <slug>');
291
- console.log(JSON.stringify(await client.get(`/api/pages/${encodeURIComponent(slug)}`), null, 2));
291
+ throw new Error(`pages ${action} requires <slug>`);
292
+ console.log(JSON.stringify(await client.get(`/api/pages/${encodeURIComponent(slug)}${action === 'runtime' ? '/runtime' : ''}`), null, 2));
292
293
  return;
293
294
  }
294
295
  if (action === 'upload') {
@@ -311,7 +312,7 @@ async function main() {
311
312
  }
312
313
  return;
313
314
  }
314
- throw new Error(`Unknown pages command '${action}'. Use list, show, or upload.`);
315
+ throw new Error(`Unknown pages command '${action}'. Use list, show, runtime, or upload.`);
315
316
  }
316
317
  }
317
318
  return;
@@ -796,17 +797,14 @@ async function loadLocalSkillContext(directory, baseUrl) {
796
797
  };
797
798
  }
798
799
  async function loadLocalPageFiles(directory) {
799
- const filePlans = await collectFilePlans(directory, directory, { includeSkillMetadata: true });
800
+ const filePlans = await collectFilePlans(directory, directory, { includeSkillMetadata: true, pageProject: true });
800
801
  if (filePlans.length === 0) {
801
802
  throw new Error('The page directory is empty.');
802
803
  }
803
- if (!filePlans.some((file) => file.relativePath === 'index.html')) {
804
- throw new Error('The page directory must contain index.html at its root.');
805
- }
806
- const totalBytes = filePlans.reduce((sum, file) => sum + file.size, 0);
804
+ const files = await preparePageFiles(directory, await collectFiles(filePlans));
807
805
  return {
808
- files: await collectFiles(filePlans),
809
- totalBytes,
806
+ files,
807
+ totalBytes: files.reduce((sum, file) => sum + file.content.length, 0),
810
808
  };
811
809
  }
812
810
  async function createSkillFromDirectory(client, localSkill, input) {
@@ -1881,6 +1879,8 @@ async function collectFilePlans(root, current = root, options = {}) {
1881
1879
  const files = [];
1882
1880
  for (const entry of entries) {
1883
1881
  const entryPath = join(current, entry.name);
1882
+ if (options.pageProject && (entry.isSymbolicLink() || ['.git', '.celld', '.wrangler', 'node_modules'].includes(entry.name)))
1883
+ continue;
1884
1884
  if (entry.isDirectory()) {
1885
1885
  files.push(...(await collectFilePlans(root, entryPath, options)));
1886
1886
  continue;
package/dist/pages.js ADDED
@@ -0,0 +1,51 @@
1
+ import { builtinModules } from 'node:module';
2
+ import { build } from 'esbuild';
3
+ import { parse, printParseErrorCode } from 'jsonc-parser';
4
+ import { resolve, relative } from 'node:path';
5
+ export async function preparePageFiles(directory, files) {
6
+ const configurations = files.filter(file => file.relativePath === 'wrangler.json' || file.relativePath === 'wrangler.jsonc');
7
+ if (configurations.length > 1)
8
+ throw new Error('Publish only one of wrangler.json and wrangler.jsonc.');
9
+ const configFile = configurations[0];
10
+ if (!configFile) {
11
+ if (!files.some(file => file.relativePath === 'index.html'))
12
+ throw new Error('The page directory must contain index.html or a Wrangler configuration at its root.');
13
+ return files;
14
+ }
15
+ const errors = [];
16
+ const config = parse(configFile.content.toString('utf8'), errors, { allowTrailingComma: true });
17
+ if (errors.length || !config || typeof config !== 'object' || Array.isArray(config))
18
+ throw new Error(`Invalid ${configFile.relativePath}: ${errors.map(error => printParseErrorCode(error.error)).join(', ')}`);
19
+ if (!config.main || config.no_bundle === true)
20
+ return files;
21
+ if (typeof config.main !== 'string' || !files.some(file => file.relativePath === config.main.replace(/^\.\//, '')))
22
+ throw new Error('Wrangler main must name a file inside the uploaded project.');
23
+ if (files.some(file => file.relativePath.startsWith('__skillshare/')))
24
+ throw new Error('The __skillshare directory is reserved for the compiled Worker.');
25
+ const outputDirectory = resolve(directory, '__skillshare');
26
+ const output = await build({
27
+ absWorkingDir: directory,
28
+ entryPoints: [config.main],
29
+ outdir: outputDirectory,
30
+ entryNames: 'worker',
31
+ bundle: true,
32
+ write: false,
33
+ format: 'esm',
34
+ platform: 'browser',
35
+ target: 'es2024',
36
+ conditions: ['workerd', 'worker', 'browser'],
37
+ external: ['cloudflare:*', 'node:*', ...builtinModules],
38
+ loader: { '.wasm': 'copy' },
39
+ });
40
+ config.main = '__skillshare/worker.js';
41
+ // Native celld still performs its own validation/bundling, including sibling WASM modules.
42
+ const prepared = files.map(file => file === configFile
43
+ ? { ...file, content: Buffer.from(JSON.stringify(config, null, 2)) } : file);
44
+ for (const file of output.outputFiles)
45
+ prepared.push({
46
+ relativePath: relative(directory, file.path).replaceAll('\\', '/'),
47
+ content: Buffer.from(file.contents),
48
+ contentType: file.path.endsWith('.wasm') ? 'application/wasm' : 'text/javascript; charset=utf-8',
49
+ });
50
+ return prepared;
51
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quadient/skillshare",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Skillshare CLI for installing and publishing Quadient skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,6 +29,8 @@
29
29
  "node": ">=20"
30
30
  },
31
31
  "dependencies": {
32
- "@clack/prompts": "^1.7.0"
32
+ "@clack/prompts": "^1.7.0",
33
+ "esbuild": "0.28.2",
34
+ "jsonc-parser": "3.3.1"
33
35
  }
34
36
  }