@gjsify/cli 0.64.3-next.260 → 0.64.3-next.262

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.
@@ -113,7 +113,8 @@ export declare class BuildAction {
113
113
  /**
114
114
  * Post-bundle step for a `--app browser` HTML entry: write the processed
115
115
  * `index.html` beside the JS bundle, with the entry `<script>`'s `src`
116
- * rewritten to point at the built bundle. Mirrors `applyShebang` — kept in
116
+ * rewritten to point at the built bundle and the page's linked icons copied
117
+ * to `assets/` beside it. Mirrors `applyShebang` — kept in
117
118
  * the CLI (not a Rolldown plugin) because the native rolldown engine
118
119
  * exposes no `emitFile`.
119
120
  */
@@ -6,10 +6,10 @@ import { resolveGlobalsList, writeRegisterInjectFile, detectAutoGlobals, detectN
6
6
  import { pnpPlugin } from '@gjsify/rolldown-plugin-pnp';
7
7
  import { basename, dirname, extname, join, resolve } from 'node:path';
8
8
  import { fileURLToPath, pathToFileURL } from 'node:url';
9
- import { chmod, mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
9
+ import { chmod, copyFile, mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
10
10
  import { normalizeBundlerOptions, mergeBundlerOptions } from '../utils/normalize-bundler-options.js';
11
11
  import { inputSourceDirs, isOutdirInsideSource, libraryOutputLeakError } from '../utils/library-output.js';
12
- import { detectHtmlEntry, parseHtmlEntry, emitBrowserHtml, htmlOutPathFor } from '../utils/html-entry.js';
12
+ import { collectLinkedAssets, detectHtmlEntry, emitBrowserHtml, htmlOutPathFor, parseHtmlEntry, } from '../utils/html-entry.js';
13
13
  import { assertGjsBundleLoadable, assertGjsBundleParses } from '../utils/gjs-bundle-guard.js';
14
14
  import { collectEntryPaths, describeJsxConfig, findJsxEntryPoint, jsxConfigMissingError } from '../utils/jsx-config.js';
15
15
  import { giSystemProbes } from '../utils/gi-runtime-paths.js';
@@ -489,7 +489,8 @@ export class BuildAction {
489
489
  /**
490
490
  * Post-bundle step for a `--app browser` HTML entry: write the processed
491
491
  * `index.html` beside the JS bundle, with the entry `<script>`'s `src`
492
- * rewritten to point at the built bundle. Mirrors `applyShebang` — kept in
492
+ * rewritten to point at the built bundle and the page's linked icons copied
493
+ * to `assets/` beside it. Mirrors `applyShebang` — kept in
493
494
  * the CLI (not a Rolldown plugin) because the native rolldown engine
494
495
  * exposes no `emitFile`.
495
496
  */
@@ -502,9 +503,15 @@ export class BuildAction {
502
503
  scriptSrc: htmlEntry.scriptSrc,
503
504
  jsOutPath,
504
505
  outHtmlPath,
506
+ assets: htmlEntry.assets,
505
507
  });
506
508
  await mkdir(dirname(outHtmlPath), { recursive: true });
507
509
  await writeFile(outHtmlPath, html);
510
+ for (const { source, target } of new Map(htmlEntry.assets.map((a) => [a.target, a])).values()) {
511
+ const dest = resolve(dirname(outHtmlPath), target);
512
+ await mkdir(dirname(dest), { recursive: true });
513
+ await copyFile(source, dest);
514
+ }
508
515
  if (verbose)
509
516
  console.debug(`[gjsify] --app browser: wrote ${outHtmlPath}`);
510
517
  }
@@ -667,7 +674,7 @@ export class BuildAction {
667
674
  const htmlSource = await readFile(htmlPath, 'utf-8');
668
675
  const { moduleEntry, scriptTag, scriptSrc } = parseHtmlEntry(htmlPath, htmlSource);
669
676
  userBundler.input = moduleEntry;
670
- htmlEntry = { htmlSource, scriptTag, scriptSrc };
677
+ htmlEntry = { htmlSource, scriptTag, scriptSrc, assets: collectLinkedAssets(htmlPath, htmlSource) };
671
678
  }
672
679
  }
673
680
  const formatRaw = userBundler.output?.format ??
@@ -18,6 +18,22 @@ export declare function detectHtmlEntry(input: RolldownOptions['input'] | undefi
18
18
  * path, with the module resolved against the HTML file's directory. Throws when there is none.
19
19
  */
20
20
  export declare function parseHtmlEntry(htmlPath: string, htmlSource: string): ParsedHtmlEntry;
21
+ export interface LinkedAsset {
22
+ /** The full matched `<link …>` tag text. */
23
+ linkTag: string;
24
+ /** The raw `href` attribute value from that tag (as authored in the HTML). */
25
+ href: string;
26
+ /** Absolute path to the referenced file. */
27
+ source: string;
28
+ /** Where the copy goes, relative to the emitted HTML's directory (`assets/<name>`). */
29
+ target: string;
30
+ }
31
+ /**
32
+ * Every `<link>` whose `rel` names an icon and whose `href` is a local path, resolved against
33
+ * the HTML file's directory, each with a unique `assets/` target. The same file linked twice
34
+ * shares one target; two files with one basename get `-1`, `-2`, … suffixes.
35
+ */
36
+ export declare function collectLinkedAssets(htmlPath: string, htmlSource: string): LinkedAsset[];
21
37
  export interface EmitBrowserHtmlInput {
22
38
  /** The original HTML source. */
23
39
  htmlSource: string;
@@ -29,11 +45,13 @@ export interface EmitBrowserHtmlInput {
29
45
  jsOutPath: string;
30
46
  /** Absolute path the emitted HTML will be written to. */
31
47
  outHtmlPath: string;
48
+ /** The page's linked icons from {@link collectLinkedAssets}; their `href`s point at the copies. */
49
+ assets?: readonly LinkedAsset[];
32
50
  }
33
51
  /**
34
52
  * The original source with the entry script's `src` rewritten to the built JS bundle, relative
35
- * to the output HTML's own directory so the page is portable wherever `dist/` is served from.
36
- * All other markup is preserved.
53
+ * to the output HTML's own directory so the page is portable wherever `dist/` is served from,
54
+ * and each linked icon's `href` rewritten to its `assets/` copy. All other markup is preserved.
37
55
  */
38
56
  export declare function emitBrowserHtml(input: EmitBrowserHtmlInput): string;
39
57
  /** Convenience: the emitted HTML filename for a given JS outfile (sibling `index.html`). */
@@ -8,9 +8,11 @@
8
8
  // plugin `emitFile`: the native `@gjsify/rolldown-native` engine implements no `emitFile`, so a
9
9
  // plugin-based emit would work on the npm engine (Node) and throw under the GJS-bundled CLI.
10
10
  // Parsing is regex-based and tolerant on purpose — this is a shell-page rewrite, not a full HTML
11
- // pipeline. CSS is NOT extracted here: it travels in the JS bundle as a string via
11
+ // pipeline. The page's own icons — a local `<link rel="icon" href>` and its kin — are copied
12
+ // beside the page under `assets/` with the `href` rewritten, as Vite does, so a favicon the HTML
13
+ // names is one the built page serves. CSS is NOT extracted here: it travels in the JS bundle as a string via
12
14
  // css-as-string and is injected by the app, the same pattern the `--app gjs` target uses.
13
- import { basename, dirname, isAbsolute, relative, resolve } from 'node:path';
15
+ import { basename, dirname, extname, isAbsolute, relative, resolve } from 'node:path';
14
16
  /**
15
17
  * The `.html` path when `input` is a SINGLE html entry (a literal `.html` string, a 1-element
16
18
  * array of one, or a 1-key record whose value is one) — else `null`. Multi-entry inputs and
@@ -54,20 +56,65 @@ export function parseHtmlEntry(htmlPath, htmlSource) {
54
56
  throw new Error(`gjsify build: ${htmlPath} has no local <script type="module" src="…"> to bundle. ` +
55
57
  `Add one pointing at your app entry (e.g. <script type="module" src="./main.ts">).`);
56
58
  }
59
+ const LINK_TAG_RE = /<link\b[^>]*>/gi;
60
+ const HREF_ATTR_RE = /\bhref\s*=\s*["']([^"']+)["']/i;
61
+ const REL_ATTR_RE = /\brel\s*=\s*["']([^"']+)["']/i;
62
+ /** `rel` tokens whose `href` is a file the page itself needs; `shortcut icon` splits to `icon`. */
63
+ const ASSET_RELS = new Set(['icon', 'apple-touch-icon', 'mask-icon']);
64
+ /** Hosted (`https://`, `//`), inline (`data:`, `blob:`) or root-relative (`/…`, the server's) hrefs stay as authored. */
65
+ const NOT_LOCAL_HREF_RE = /^(?:[a-z][a-z0-9+.-]*:|\/)/i;
66
+ /**
67
+ * Every `<link>` whose `rel` names an icon and whose `href` is a local path, resolved against
68
+ * the HTML file's directory, each with a unique `assets/` target. The same file linked twice
69
+ * shares one target; two files with one basename get `-1`, `-2`, … suffixes.
70
+ */
71
+ export function collectLinkedAssets(htmlPath, htmlSource) {
72
+ const assets = [];
73
+ const targetBySource = new Map();
74
+ const taken = new Set();
75
+ for (const match of htmlSource.matchAll(LINK_TAG_RE)) {
76
+ const linkTag = match[0];
77
+ const rel = REL_ATTR_RE.exec(linkTag)?.[1].toLowerCase().split(/\s+/) ?? [];
78
+ if (!rel.some((token) => ASSET_RELS.has(token)))
79
+ continue;
80
+ const href = HREF_ATTR_RE.exec(linkTag)?.[1];
81
+ if (!href || NOT_LOCAL_HREF_RE.test(href))
82
+ continue;
83
+ const path = href.replace(/[?#].*$/, '');
84
+ const source = isAbsolute(path) ? path : resolve(dirname(htmlPath), path);
85
+ let target = targetBySource.get(source);
86
+ if (!target) {
87
+ const ext = extname(source);
88
+ const stem = basename(source, ext);
89
+ target = `assets/${stem}${ext}`;
90
+ for (let n = 1; taken.has(target); n++)
91
+ target = `assets/${stem}-${n}${ext}`;
92
+ taken.add(target);
93
+ targetBySource.set(source, target);
94
+ }
95
+ assets.push({ linkTag, href, source, target });
96
+ }
97
+ return assets;
98
+ }
57
99
  /**
58
100
  * The original source with the entry script's `src` rewritten to the built JS bundle, relative
59
- * to the output HTML's own directory so the page is portable wherever `dist/` is served from.
60
- * All other markup is preserved.
101
+ * to the output HTML's own directory so the page is portable wherever `dist/` is served from,
102
+ * and each linked icon's `href` rewritten to its `assets/` copy. All other markup is preserved.
61
103
  */
62
104
  export function emitBrowserHtml(input) {
63
- const { htmlSource, scriptTag, scriptSrc, jsOutPath, outHtmlPath } = input;
105
+ const { htmlSource, scriptTag, scriptSrc, jsOutPath, outHtmlPath, assets = [] } = input;
64
106
  let rel = relative(dirname(outHtmlPath), jsOutPath).split(/[\\/]/).join('/');
65
107
  if (!rel.startsWith('.'))
66
108
  rel = `./${rel}`;
67
109
  // Rewrite the src WITHIN the matched tag, then swap that tag back in — so no other `src=`
68
110
  // in the document is touched.
69
111
  const newTag = scriptTag.replace(SRC_ATTR_RE, (whole) => whole.replace(scriptSrc, rel));
70
- return htmlSource.replace(scriptTag, newTag);
112
+ let html = htmlSource.replace(scriptTag, newTag);
113
+ for (const { linkTag, href, target } of new Map(assets.map((a) => [a.linkTag, a])).values()) {
114
+ const newLink = linkTag.replace(HREF_ATTR_RE, (whole) => whole.replace(href, `./${target}`));
115
+ html = html.split(linkTag).join(newLink);
116
+ }
117
+ return html;
71
118
  }
72
119
  /** Convenience: the emitted HTML filename for a given JS outfile (sibling `index.html`). */
73
120
  export function htmlOutPathFor(jsOutPath) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/cli",
3
- "version": "0.64.3-next.260",
3
+ "version": "0.64.3-next.262",
4
4
  "description": "CLI for Gjsify",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -142,22 +142,22 @@
142
142
  "cli"
143
143
  ],
144
144
  "dependencies": {
145
- "@gjsify/blueprint": "0.64.3-next.260",
146
- "@gjsify/buffer": "0.64.3-next.260",
147
- "@gjsify/create-app": "0.64.3-next.260",
148
- "@gjsify/node-globals": "0.64.3-next.260",
149
- "@gjsify/node-polyfills": "0.64.3-next.260",
150
- "@gjsify/npm-registry": "0.64.3-next.260",
151
- "@gjsify/resolve-npm": "0.64.3-next.260",
152
- "@gjsify/rolldown-plugin-gjsify": "0.64.3-next.260",
153
- "@gjsify/vite-plugin-blueprint": "0.64.3-next.260",
154
- "@gjsify/rolldown-plugin-pnp": "0.64.3-next.260",
155
- "@gjsify/semver": "0.64.3-next.260",
156
- "@gjsify/tar": "0.64.3-next.260",
157
- "@gjsify/tsc": "0.64.3-next.260",
158
- "@gjsify/utils": "0.64.3-next.260",
159
- "@gjsify/web-polyfills": "0.64.3-next.260",
160
- "@gjsify/workspace": "0.64.3-next.260",
145
+ "@gjsify/blueprint": "0.64.3-next.262",
146
+ "@gjsify/buffer": "0.64.3-next.262",
147
+ "@gjsify/create-app": "0.64.3-next.262",
148
+ "@gjsify/node-globals": "0.64.3-next.262",
149
+ "@gjsify/node-polyfills": "0.64.3-next.262",
150
+ "@gjsify/npm-registry": "0.64.3-next.262",
151
+ "@gjsify/resolve-npm": "0.64.3-next.262",
152
+ "@gjsify/rolldown-plugin-gjsify": "0.64.3-next.262",
153
+ "@gjsify/vite-plugin-blueprint": "0.64.3-next.262",
154
+ "@gjsify/rolldown-plugin-pnp": "0.64.3-next.262",
155
+ "@gjsify/semver": "0.64.3-next.262",
156
+ "@gjsify/tar": "0.64.3-next.262",
157
+ "@gjsify/tsc": "0.64.3-next.262",
158
+ "@gjsify/utils": "0.64.3-next.262",
159
+ "@gjsify/web-polyfills": "0.64.3-next.262",
160
+ "@gjsify/workspace": "0.64.3-next.262",
161
161
  "acorn": "^8.17.0",
162
162
  "cosmiconfig": "^9.0.2",
163
163
  "get-tsconfig": "^4.14.0",
@@ -166,13 +166,13 @@
166
166
  "yargs": "^18.0.0"
167
167
  },
168
168
  "devDependencies": {
169
- "@gjsify/unit": "0.64.3-next.260",
169
+ "@gjsify/unit": "0.64.3-next.262",
170
170
  "@types/yargs": "^17.0.35",
171
171
  "typescript": "^6.0.3"
172
172
  },
173
173
  "peerDependencies": {
174
- "@gjsify/oxfmt-native": "0.64.3-next.260",
175
- "@gjsify/rolldown-native": "0.64.3-next.260"
174
+ "@gjsify/oxfmt-native": "0.64.3-next.262",
175
+ "@gjsify/rolldown-native": "0.64.3-next.262"
176
176
  },
177
177
  "peerDependenciesMeta": {
178
178
  "@gjsify/oxfmt-native": {
@@ -192,5 +192,5 @@
192
192
  "url": "https://github.com/gjsify/gjsify/issues"
193
193
  },
194
194
  "homepage": "https://github.com/gjsify/gjsify/tree/main/packages/infra/cli#readme",
195
- "gitHead": "886de21be840429ce043cdbf8195172530e230d4"
195
+ "gitHead": "0f5ca82aab25bf9df8d1e790d28483b1f90fe3a9"
196
196
  }