dsh-ris-yds 0.0.0-stage → 1.1.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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +160 -2
  3. package/assets/yds-chrome-resource-override.crx +0 -0
  4. package/bin/check-listing.mjs +247 -0
  5. package/bin/yds.js +19 -0
  6. package/cordis.patch.yml +13 -0
  7. package/lib/browser.js +428 -0
  8. package/lib/build.js +115 -0
  9. package/lib/cli.js +1130 -0
  10. package/lib/confcenter.js +377 -0
  11. package/lib/config.js +552 -0
  12. package/lib/fe-dev.js +206 -0
  13. package/lib/ide-proxy.js +906 -0
  14. package/lib/ide-watch.js +188 -0
  15. package/lib/ide.js +728 -0
  16. package/lib/index.js +267 -0
  17. package/lib/proc.js +270 -0
  18. package/lib/risd-peer.js +353 -0
  19. package/lib/rules.js +193 -0
  20. package/lib/socks.js +279 -0
  21. package/lib/token.js +36 -0
  22. package/lib/yms.js +336 -0
  23. package/lib/yoncode.js +249 -0
  24. package/native/yds-rust.darwin-arm64.node +0 -0
  25. package/native/yds-rust.darwin-x64.node +0 -0
  26. package/native/yds-rust.linux-arm64.node +0 -0
  27. package/native/yds-rust.linux-x64.node +0 -0
  28. package/native/yds-rust.win32-arm64.node +0 -0
  29. package/native/yds-rust.win32-x64.node +0 -0
  30. package/package.json +58 -4
  31. package/skills/dsh-ris-yds/SKILL.md +134 -0
  32. package/test/browser.test.mjs +442 -0
  33. package/test/cli-stop.test.mjs +40 -0
  34. package/test/cli.test.mjs +246 -0
  35. package/test/config.test.mjs +106 -0
  36. package/test/fe-dev.test.mjs +93 -0
  37. package/test/hints.test.mjs +13 -0
  38. package/test/ide-config.test.mjs +155 -0
  39. package/test/ide-proxy.test.mjs +256 -0
  40. package/test/ide-watch.test.mjs +79 -0
  41. package/test/launch-proxy.test.mjs +83 -0
  42. package/test/lease-release.test.mjs +76 -0
  43. package/test/mode-separation.test.mjs +147 -0
  44. package/test/pidfile.test.mjs +105 -0
  45. package/test/proc.test.mjs +172 -0
  46. package/test/proxy-standalone.test.mjs +121 -0
  47. package/test/risd-leases.test.mjs +111 -0
  48. package/test/risd-tier.test.mjs +361 -0
  49. package/test/rules-e2e.test.mjs +122 -0
  50. package/test/rules.test.mjs +154 -0
  51. package/test/yoncode.test.mjs +159 -0
package/lib/fe-dev.js ADDED
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Front-end dev-server wiring for the yds project.
3
+ *
4
+ * A yds page loads its extender script from the platform (`/mdf-wh/<domainKey>/…`),
5
+ * but during development it has to come from the local webpack dev server on
6
+ * `http://127.0.0.1:3004/static`. Two ways to get there, and this module serves
7
+ * both:
8
+ *
9
+ * 1. The Resource Override Chrome extension (`yds-chrome-resource-override.crx`)
10
+ * reads a JSON rule file — {@link writeOverrideRules} generates it from THIS
11
+ * project's values (module/domainKey, workbench host, dev port) instead of a
12
+ * hand-edited copy that goes stale the moment the project or the port changes.
13
+ * 2. Without the extension, an agent can intercept requests itself (CDP `Fetch`
14
+ * domain) — {@link requestRedirects} is the extracted logic of the same rules,
15
+ * already converted to the regex form a `Fetch.continueRequest({ url })` needs.
16
+ *
17
+ * The regex conversion is a port of the extension's own glob→regex transform, so a
18
+ * rule means the same thing whichever path is used.
19
+ */
20
+
21
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
22
+ import { dirname, join } from 'node:path'
23
+ import { fileURLToPath } from 'node:url'
24
+
25
+ /** The plugin's own directory (`lib/` sits directly under it). */
26
+ const PLUGIN_ROOT = dirname(dirname(fileURLToPath(import.meta.url)))
27
+
28
+ /** Chrome extension shipped WITH the plugin, so no machine-specific path is needed. */
29
+ export const CRX_FILENAME = 'yds-chrome-resource-override.crx'
30
+
31
+ /** Default webpack dev-server port of the yds front end (`npm run debug:extend`). */
32
+ export const DEFAULT_FE_PORT = 3004
33
+
34
+ /** File the Chrome extension imports (a copy can live anywhere; this is ours). */
35
+ export const OVERRIDE_FILENAME = 'resource-override-rules.json'
36
+
37
+ /**
38
+ * The dynamic half of the override rules, taken from the project's own config.
39
+ *
40
+ * Nothing here is written into the rule text by hand: the module comes from
41
+ * `yonbuilder.json`/the deployed module, the host from `workbenchUrl`, the port
42
+ * from the project (`yds.webConfig` of the front end, else its own override file).
43
+ * @param {object} cfg - resolved yds config.
44
+ * @returns {{module: string, host: string, fePort: number, scriptBase: string, staticBase: string, crxPath: string}} values.
45
+ */
46
+ export function feConfig(cfg) {
47
+ const module = typeof cfg?.module === 'string' ? cfg.module : ''
48
+ let host = ''
49
+ try {
50
+ host = new URL(String(cfg?.workbenchUrl ?? '')).host
51
+ } catch {
52
+ host = ''
53
+ }
54
+ const fePort = Number.isInteger(cfg?.fePort) && cfg.fePort > 0 ? cfg.fePort : DEFAULT_FE_PORT
55
+ return {
56
+ module,
57
+ host,
58
+ fePort,
59
+ scriptBase: `http://127.0.0.1:${fePort}/static/javascripts/extend.js`,
60
+ staticBase: `http://127.0.0.1:${fePort}/static`,
61
+ // Shipped inside the plugin: the extension is part of this workflow, so its
62
+ // location is not a machine setting.
63
+ crxPath: existsSync(join(PLUGIN_ROOT, 'assets', CRX_FILENAME)) ? join(PLUGIN_ROOT, 'assets', CRX_FILENAME) : '',
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Escape a glob for a regular expression, keeping `*` as the only wildcard.
69
+ * @param {string} glob - the extension's match text.
70
+ * @returns {string} escaped text.
71
+ */
72
+ function escapeGlob(glob) {
73
+ return String(glob).replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\\\*/g, '*')
74
+ }
75
+
76
+ /**
77
+ * Convert one glob to the regex + substitution the browser applies.
78
+ *
79
+ * Ported from the extension (`globMatchToDNRRegex`): the LAST `*` becomes a
80
+ * capture group `(.*)` used by the replacement, the second-to-last becomes
81
+ * `[^/]+` (so a trailing path segment is not swallowed), and any other `*` is a
82
+ * plain `.*`. The replacement's last `*` becomes `\1`.
83
+ * @param {string} glob - match pattern, e.g. `https://*\/mod\/*\/javascripts\/extend*.min.js*`.
84
+ * @param {string} [replace] - replacement, may contain one `*` to reuse the capture.
85
+ * @returns {{regex: string, substitution: string}} DNR-ready pair.
86
+ */
87
+ export function globToDnrRegex(glob, replace = '') {
88
+ const escaped = escapeGlob(glob)
89
+ const stars = escaped.match(/(\*+)/g) ?? []
90
+ let pattern = escaped
91
+ let cursor = 0
92
+ stars.forEach((star, index) => {
93
+ const isLast = index === stars.length - 1
94
+ const isSecondLast = index === stars.length - 2
95
+ const replacement = isLast ? '(.*)' : (isSecondLast ? '[^/]+' : '.*')
96
+ const at = pattern.indexOf(star, cursor)
97
+ pattern = pattern.slice(0, at) + replacement + pattern.slice(at + star.length)
98
+ cursor = at + replacement.length
99
+ })
100
+ let substitution = String(replace).replace(/[\\]/g, '\\&')
101
+ if (stars.length > 0) substitution = substitution.replace(stars[stars.length - 1], '\\1')
102
+ // The extension anchors the whole rule (`^…$`); keeping that makes the browser
103
+ // and a CDP interception agree on what matches.
104
+ return { regex: `^${pattern}$`, substitution }
105
+ }
106
+
107
+ /**
108
+ * The seven rules, generated for THIS project.
109
+ *
110
+ * Both the query-string and the plain URL shapes are covered because the platform
111
+ * loads the extender with a cache-busting query in prod and without one in some
112
+ * builds; the `//` shape appears when a domain builds the URL from an empty
113
+ * version segment.
114
+ * @param {object} cfg - resolved yds config.
115
+ * @returns {object} the extension's file format: v=2 with one rule group.
116
+ */
117
+ export function overrideRules(cfg) {
118
+ const fe = feConfig(cfg)
119
+ const module = fe.module === '' ? 'c-ass-kk-hans' : fe.module
120
+ const host = fe.host === '' ? 'srmdev.hanslaser.com' : fe.host
121
+ const js = fe.scriptBase
122
+ const css = `${fe.staticBase}/stylesheets/extend.css`
123
+ const rules = [
124
+ { match: `https://*/${module}/*/javascripts/extend*.min.js*`, replace: js },
125
+ { match: `https://*/${module}/*/stylesheets/extend*.min.css*`, replace: css },
126
+ { match: `https://*/${module}/*/javascripts/extend*.min.js`, replace: js },
127
+ { match: `https://*/${module}/*/stylesheets/extend*.min.css`, replace: css },
128
+ { match: `https://*/${module}//javascripts/extend.min.js`, replace: js },
129
+ { match: `https://*/${module}//stylesheets/extend.min.css`, replace: css },
130
+ { match: `https://${host}/${module}-festatic/*`, replace: `${fe.staticBase}/*` },
131
+ ]
132
+ return {
133
+ v: 2,
134
+ ruleGroups: [{
135
+ id: 1,
136
+ matchUrl: '*',
137
+ on: true,
138
+ rules: rules.map((rule, index) => ({
139
+ id: index + 1,
140
+ match: rule.match,
141
+ on: true,
142
+ replace: rule.replace,
143
+ type: 'normalOverride',
144
+ })),
145
+ }],
146
+ }
147
+ }
148
+
149
+ /**
150
+ * The same rules as plain redirects (the logic, without the extension).
151
+ *
152
+ * An agent driving the browser itself needs `{regex, substitution}` pairs: enable
153
+ * the CDP `Fetch` domain, and for every paused request whose URL matches, continue
154
+ * it at the substituted URL. No extension, no 3004 CORS surprises.
155
+ * @param {object} cfg - resolved yds config.
156
+ * @returns {Array<{id: number, match: string, replace: string, regex: string, substitution: string}>} redirects.
157
+ */
158
+ export function requestRedirects(cfg) {
159
+ const file = overrideRules(cfg)
160
+ return file.ruleGroups[0].rules.map((rule) => {
161
+ const converted = globToDnrRegex(rule.match, rule.replace)
162
+ return { id: rule.id, match: rule.match, replace: rule.replace, regex: converted.regex, substitution: converted.substitution }
163
+ })
164
+ }
165
+
166
+ /**
167
+ * Write the rule file where the extension can import it.
168
+ * @param {object} cfg - resolved yds config.
169
+ * @param {{path?: string}} [options] - destination override.
170
+ * @returns {{path: string, module: string, host: string, fePort: number, rules: number}} summary.
171
+ */
172
+ export function writeOverrideRules(cfg, options = {}) {
173
+ const fe = feConfig(cfg)
174
+ const workspace = typeof cfg?.workspace === 'string' && cfg.workspace !== '' ? cfg.workspace : process.cwd()
175
+ const path = options.path ?? join(workspace, '.dsh-ris-yds', OVERRIDE_FILENAME)
176
+ const file = overrideRules(cfg)
177
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 })
178
+ writeFileSync(path, `${JSON.stringify(file, null, 2)}\n`, { encoding: 'utf8' })
179
+ return { path, module: fe.module, host: fe.host, fePort: fe.fePort, rules: file.ruleGroups[0].rules.length }
180
+ }
181
+
182
+ /**
183
+ * A page URL that loads the local extender without any extension.
184
+ *
185
+ * The yds front end honours `scriptUrl=…` (its own documented debug switch), so
186
+ * this is the zero-setup path: open the workbench page with the parameter appended.
187
+ * @param {object} cfg - resolved yds config.
188
+ * @param {string} page - a workbench page URL (absolute or root-relative).
189
+ * @returns {string} URL with `scriptUrl` set, or '' when no page/workbench is known.
190
+ */
191
+ export function pageUrlWithScriptUrl(cfg, page) {
192
+ const fe = feConfig(cfg)
193
+ const base = typeof cfg?.workbenchUrl === 'string' ? cfg.workbenchUrl : ''
194
+ const raw = typeof page === 'string' ? page.trim() : ''
195
+ // No page means the caller has nothing to open: hand back an empty string so the
196
+ // CLI prints usage instead of a bare workspace URL nobody asked for.
197
+ if (raw === '') return ''
198
+ let url
199
+ try {
200
+ url = new URL(raw, base === '' ? undefined : base)
201
+ } catch {
202
+ return ''
203
+ }
204
+ url.searchParams.set('scriptUrl', fe.staticBase)
205
+ return url.toString()
206
+ }