datacontract-editor 0.1.10 → 0.1.12

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 (94) hide show
  1. package/README.md +1 -3
  2. package/bin/datacontract-editor.js +73 -29
  3. package/dist/assets/editor.worker-4.7.0.js +30 -9034
  4. package/dist/assets/yaml.worker-4.7.0.js +432 -26435
  5. package/dist/datacontract-editor.css +2 -1
  6. package/dist/datacontract-editor.es.js +82727 -77765
  7. package/dist/monaco-4.7.0.js +114774 -125871
  8. package/dist/rolldown-runtime-DY7j01NX.js +30 -0
  9. package/package.json +20 -12
  10. package/dist/abap-Bs8kvjsq.js +0 -1398
  11. package/dist/apex-Dspwrl5m.js +0 -326
  12. package/dist/azcli-k1BiOau1.js +0 -68
  13. package/dist/bat-BIP72KWc.js +0 -100
  14. package/dist/bicep-VhQeYhS3.js +0 -102
  15. package/dist/cameligo-DnuAXtHg.js +0 -174
  16. package/dist/clojure-BAuDPsal.js +0 -761
  17. package/dist/coffee-BV06qSnj.js +0 -232
  18. package/dist/cpp-Cz8Jeyrf.js +0 -389
  19. package/dist/csharp-F6hhlXFb.js +0 -326
  20. package/dist/csp-KzlEHCyI.js +0 -53
  21. package/dist/css-e4FKntZ3.js +0 -187
  22. package/dist/cssMode-BNkTa2xp.js +0 -1618
  23. package/dist/cypher-CpvDGKRr.js +0 -263
  24. package/dist/dart-DdHeFWR0.js +0 -281
  25. package/dist/dockerfile-aoNXDcio.js +0 -130
  26. package/dist/ecl-C4Q0DYuA.js +0 -456
  27. package/dist/elixir-C9X42b0T.js +0 -569
  28. package/dist/flow9-Bff42pfB.js +0 -142
  29. package/dist/freemarker2-Dyfzmb5A.js +0 -989
  30. package/dist/fsharp-C6vPAsHG.js +0 -217
  31. package/dist/go-DEr8zx9f.js +0 -218
  32. package/dist/graphql-BiNBBRFh.js +0 -151
  33. package/dist/handlebars-BtDNI-uC.js +0 -419
  34. package/dist/hcl-2T9lKuFg.js +0 -183
  35. package/dist/html-Cn4TGw_o.js +0 -308
  36. package/dist/htmlMode-Bf84vbo2.js +0 -1628
  37. package/dist/ini-CZGtssYM.js +0 -71
  38. package/dist/java-B260NeH3.js +0 -232
  39. package/dist/javascript-B86Kac07.js +0 -70
  40. package/dist/jsonMode-Ckgg5lAE.js +0 -2051
  41. package/dist/julia-kuNLM9c5.js +0 -511
  42. package/dist/kotlin-Bz6kAefA.js +0 -253
  43. package/dist/less-AZUtS89I.js +0 -162
  44. package/dist/lexon-BcmgnyRb.js +0 -157
  45. package/dist/liquid-D0NPwuu_.js +0 -240
  46. package/dist/lua-Dz1RbATg.js +0 -162
  47. package/dist/m3-CIMooLFn.js +0 -210
  48. package/dist/markdown-C1BsCK9P.js +0 -229
  49. package/dist/mdx-QFRSBtVQ.js +0 -165
  50. package/dist/mips-BovsnfLQ.js +0 -198
  51. package/dist/msdax-CiRB2ZB-.js +0 -375
  52. package/dist/mysql-BwdpPE19.js +0 -878
  53. package/dist/objective-c-D4nfHfso.js +0 -183
  54. package/dist/pascal--j5-PYtz.js +0 -251
  55. package/dist/pascaligo-C7TcJxNL.js +0 -164
  56. package/dist/perl-BzEWqUb5.js +0 -626
  57. package/dist/pgsql-B_KZ0mGX.js +0 -851
  58. package/dist/php-A19QHzQV.js +0 -500
  59. package/dist/pla-COeKY1hA.js +0 -137
  60. package/dist/postiats-BfMp7zJL.js +0 -907
  61. package/dist/powerquery-B0TS_5Gb.js +0 -890
  62. package/dist/powershell-5jSbKdYB.js +0 -239
  63. package/dist/protobuf-YgnOutn3.js +0 -420
  64. package/dist/pug-DLej6kn5.js +0 -402
  65. package/dist/python-C8RW9B6A.js +0 -301
  66. package/dist/qsharp-AhjqtSHB.js +0 -301
  67. package/dist/r-D-F4P80w.js +0 -243
  68. package/dist/razor-CLZqdY_r.js +0 -550
  69. package/dist/redis-hwwIlMnV.js +0 -302
  70. package/dist/redshift-Bcm_kjEG.js +0 -809
  71. package/dist/restructuredtext-CrdzfPP-.js +0 -174
  72. package/dist/ruby-QC2UNm1s.js +0 -511
  73. package/dist/rust-5aqiQ3L5.js +0 -343
  74. package/dist/sb-B1g1ZfLJ.js +0 -115
  75. package/dist/scala-C2U4R5Gb.js +0 -370
  76. package/dist/scheme-DeFRbslr.js +0 -108
  77. package/dist/scss-RSTCiCmI.js +0 -262
  78. package/dist/shell-BpF-iNu8.js +0 -221
  79. package/dist/solidity-C00r2mbQ.js +0 -1367
  80. package/dist/sophia-DVW5ZKDB.js +0 -199
  81. package/dist/sparql-B0nmpxeX.js +0 -201
  82. package/dist/sql-CJwKPERn.js +0 -853
  83. package/dist/st-DPlKG5DT.js +0 -416
  84. package/dist/swift-YZBAbVHV.js +0 -309
  85. package/dist/systemverilog-CDQSaH63.js +0 -576
  86. package/dist/tcl-Dlu8sp6Q.js +0 -232
  87. package/dist/tsMode-yleMwhPq.js +0 -933
  88. package/dist/twig-C5TO7-kw.js +0 -392
  89. package/dist/typescript-CJBeebKU.js +0 -343
  90. package/dist/typespec-BsmjDEu7.js +0 -117
  91. package/dist/vb-ChHvIDrk.js +0 -372
  92. package/dist/wgsl-XEp1ju8c.js +0 -439
  93. package/dist/xml-vC-NMVX1.js +0 -95
  94. package/dist/yaml-Dlf2VF-T.js +0 -206
package/README.md CHANGED
@@ -92,12 +92,10 @@ docker run -d -p 4173:4173 \
92
92
 
93
93
  ### Data Contract CLI
94
94
 
95
- Coming soon!
96
-
97
95
  You can start the editor from the Data Contract CLI:
98
96
 
99
97
  ```
100
- datacontract editor datacontract.yaml
98
+ datacontract edit datacontract.yaml
101
99
  ```
102
100
 
103
101
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { createServer } from 'http';
4
- import { readFileSync, writeFileSync, existsSync } from 'fs';
4
+ import { readFileSync, writeFileSync, existsSync, readdirSync } from 'fs';
5
5
  import { join, extname, resolve, basename } from 'path';
6
6
  import { fileURLToPath } from 'url';
7
7
  import { createRequire } from 'module';
@@ -111,7 +111,7 @@ async function findPort(start = 9090) {
111
111
 
112
112
  return new Promise((resolve) => {
113
113
  const server = net.createServer();
114
- server.listen(start, () => {
114
+ server.listen(start, '127.0.0.1', () => {
115
115
  const port = server.address().port;
116
116
  server.close(() => resolve(port));
117
117
  });
@@ -123,13 +123,52 @@ async function findPort(start = 9090) {
123
123
 
124
124
  // Helper to send JSON response
125
125
  function sendJson(res, statusCode, data) {
126
- res.writeHead(statusCode, {
127
- 'Content-Type': 'application/json',
128
- 'Access-Control-Allow-Origin': '*'
129
- });
126
+ res.writeHead(statusCode, { 'Content-Type': 'application/json' });
130
127
  res.end(JSON.stringify(data));
131
128
  }
132
129
 
130
+ // Index every file under dist/ once at startup, keyed by the URL path it is
131
+ // served at. Requests then only ever look up a key in this map: the paths handed
132
+ // to the filesystem come from readdirSync, never from the request, so a request
133
+ // path cannot address a file outside dist/ no matter how it is encoded.
134
+ function indexDistFiles(dir, urlPrefix = '') {
135
+ const files = new Map();
136
+
137
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
138
+ const urlPath = `${urlPrefix}/${entry.name}`;
139
+ if (entry.isDirectory()) {
140
+ for (const [key, value] of indexDistFiles(join(dir, entry.name), urlPath)) {
141
+ files.set(key, value);
142
+ }
143
+ } else if (entry.isFile()) {
144
+ files.set(urlPath, join(dir, entry.name));
145
+ }
146
+ }
147
+
148
+ return files;
149
+ }
150
+
151
+ // dist/ is a build artifact and does not change while the server runs.
152
+ const distFiles = indexDistFiles(distDir);
153
+
154
+ // Turn a request path into a lookup key. Returns null when the percent-encoding
155
+ // is malformed, which simply means no file will match.
156
+ function toLookupKey(url) {
157
+ try {
158
+ return decodeURIComponent(url);
159
+ } catch {
160
+ return null;
161
+ }
162
+ }
163
+
164
+ // The server is only ever meant to be reached by a browser on this machine.
165
+ // Requiring a loopback Host blocks DNS rebinding, where a page on an attacker
166
+ // domain resolves that domain to 127.0.0.1 and then talks to us same-origin.
167
+ function isLocalRequest(req) {
168
+ const host = (req.headers.host || '').replace(/:\d+$/, '').replace(/^\[|\]$/g, '');
169
+ return host === 'localhost' || host === '127.0.0.1' || host === '::1';
170
+ }
171
+
133
172
  // Helper to read request body
134
173
  function readBody(req) {
135
174
  return new Promise((resolve, reject) => {
@@ -144,6 +183,13 @@ function readBody(req) {
144
183
  function generateCliIndexHtml() {
145
184
  const baseHtml = readFileSync(indexPath, 'utf-8');
146
185
 
186
+ // The filename comes from argv and can contain quotes or '</script>', so every
187
+ // injection point below goes through JSON.stringify rather than raw
188
+ // interpolation. encodeURIComponent alone is not enough: it leaves apostrophes
189
+ // untouched, which is enough to break out of a single-quoted string literal.
190
+ const fileUrl = JSON.stringify('/api/files/' + encodeURIComponent(targetFileName));
191
+ const fileNameLiteral = JSON.stringify(targetFileName);
192
+
147
193
  // Replace the init call with CLI-specific configuration
148
194
  const cliInitScript = `
149
195
  import { init } from './datacontract-editor.es.js';
@@ -152,7 +198,7 @@ function generateCliIndexHtml() {
152
198
  async function initCli() {
153
199
  try {
154
200
  // Fetch the file content from CLI server
155
- const response = await fetch('/api/files/${encodeURIComponent(targetFileName)}');
201
+ const response = await fetch(${fileUrl});
156
202
  if (!response.ok) {
157
203
  throw new Error('Failed to load file: ' + response.statusText);
158
204
  }
@@ -165,7 +211,7 @@ function generateCliIndexHtml() {
165
211
  persistence: 'none',
166
212
  initialView: 'form',
167
213
  onSave: async (yamlContent) => {
168
- const saveResponse = await fetch('/api/files/${encodeURIComponent(targetFileName)}', {
214
+ const saveResponse = await fetch(${fileUrl}, {
169
215
  method: 'PUT',
170
216
  headers: { 'Content-Type': 'text/yaml' },
171
217
  body: yamlContent
@@ -177,7 +223,7 @@ function generateCliIndexHtml() {
177
223
  }
178
224
  });
179
225
 
180
- console.log('CLI mode: Loaded ${targetFileName}');
226
+ console.log('CLI mode: Loaded ' + ${fileNameLiteral});
181
227
  } catch (error) {
182
228
  console.error('Failed to initialize CLI mode:', error);
183
229
  alert('Failed to load file: ' + error.message);
@@ -201,13 +247,17 @@ const server = createServer(async (req, res) => {
201
247
  let url = req.url.split('?')[0]; // Remove query string
202
248
  const method = req.method;
203
249
 
204
- // Handle CORS preflight
250
+ if (!isLocalRequest(req)) {
251
+ res.writeHead(403);
252
+ res.end('Forbidden');
253
+ return;
254
+ }
255
+
256
+ // The editor is served from this same origin, so it never needs CORS. Not
257
+ // answering preflights is what stops another site from PUTing over the file
258
+ // being edited.
205
259
  if (method === 'OPTIONS') {
206
- res.writeHead(204, {
207
- 'Access-Control-Allow-Origin': '*',
208
- 'Access-Control-Allow-Methods': 'GET, PUT, POST, OPTIONS',
209
- 'Access-Control-Allow-Headers': 'Content-Type'
210
- });
260
+ res.writeHead(204);
211
261
  res.end();
212
262
  return;
213
263
  }
@@ -264,10 +314,7 @@ const server = createServer(async (req, res) => {
264
314
  if (method === 'GET') {
265
315
  try {
266
316
  const content = readFileSync(targetFilePath, 'utf-8');
267
- res.writeHead(200, {
268
- 'Content-Type': 'text/yaml',
269
- 'Access-Control-Allow-Origin': '*'
270
- });
317
+ res.writeHead(200, { 'Content-Type': 'text/yaml' });
271
318
  res.end(content);
272
319
  } catch (err) {
273
320
  sendJson(res, 500, { error: 'Failed to read file', details: err.message });
@@ -298,16 +345,11 @@ const server = createServer(async (req, res) => {
298
345
  url = '/index.html';
299
346
  }
300
347
 
301
- let filePath = join(distDir, url);
302
- const ext = extname(filePath).toLowerCase();
303
-
304
- // If no extension, serve index.html (SPA routing)
305
- if (!ext && !existsSync(filePath)) {
306
- filePath = indexPath;
307
- }
348
+ // Serve a known file from dist/, or fall back to index.html for SPA routes
349
+ const lookupKey = toLookupKey(url);
350
+ const filePath = lookupKey === null ? undefined : distFiles.get(lookupKey);
308
351
 
309
- // Try to serve the file
310
- if (existsSync(filePath)) {
352
+ if (filePath) {
311
353
  try {
312
354
  const content = readFileSync(filePath);
313
355
  const contentType = mimeTypes[extname(filePath).toLowerCase()] || 'application/octet-stream';
@@ -335,7 +377,9 @@ async function start() {
335
377
  const port = requestedPort || await findPort(9090);
336
378
  const url = `http://localhost:${port}`;
337
379
 
338
- server.listen(port, async () => {
380
+ // Bind to loopback only: this server exposes local files and has no auth,
381
+ // so it must not be reachable from the network.
382
+ server.listen(port, '127.0.0.1', async () => {
339
383
  console.log(`
340
384
  datacontract-editor v${pkg.version}
341
385