@corva/create-app 0.124.0 → 0.125.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.
@@ -2,8 +2,8 @@
2
2
  // @corva/dc-platform-shared once the standalone flow is proven, which is why
3
3
  // nothing here imports from the app's own source.
4
4
  import { createRequire } from 'node:module';
5
- import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
6
- import { join, resolve } from 'node:path';
5
+ import { existsSync, readFileSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
6
+ import { join, relative, resolve } from 'node:path';
7
7
 
8
8
  import dotenv from 'dotenv';
9
9
  import { expand } from 'dotenv-expand';
@@ -100,10 +100,22 @@ const normalizeIifeGlobalAssignment = (jsContent, appIdentifier, emittedGlobalNa
100
100
  return normalizedAssignment + jsContent;
101
101
  };
102
102
 
103
+ // webpack's `output.publicPath: 'auto'` read the bundle's own <script> src once, at bundle
104
+ // init, and served every emitted asset from next to it. Same expression, same moment.
105
+ const ASSET_BASE_EXPR =
106
+ '(function(){var d=document,s=d.currentScript;if(!s||s.tagName.toUpperCase()!=="SCRIPT"){' +
107
+ 'var a=d.getElementsByTagName("script");s=a[a.length-1];}' +
108
+ 'return((s&&s.src)||d.baseURI).replace(/[^/]*(?:[?#].*)?$/,"");})()';
109
+
110
+ // url() in the extracted CSS carries this marker instead of a path (see renderBuiltUrl); the
111
+ // marker is spliced out into a concatenation with the base, so the injected stylesheet points
112
+ // at the CDN directory rather than at the host document's.
113
+ const ASSET_MARKER = '__CORVA_ASSET__';
114
+
103
115
  const createCssInjection = cssContent =>
104
- `(function(){var s=document.createElement('style');s.textContent=${JSON.stringify(
116
+ `(function(){var B=${ASSET_BASE_EXPR};var s=document.createElement('style');s.textContent=${JSON.stringify(
105
117
  cssContent,
106
- )};document.head.appendChild(s)})();`;
118
+ ).replaceAll(ASSET_MARKER, '"+B+"')};document.head.appendChild(s)})();`;
107
119
 
108
120
  // The platform loads a single file, so the emitted stylesheets are folded back into it.
109
121
  const finalizeJsBundle = ({ outDir, cssFileNames, appIdentifier, emittedGlobalName }) => {
@@ -124,6 +136,107 @@ const finalizeJsBundle = ({ outDir, cssFileNames, appIdentifier, emittedGlobalNa
124
136
  cssFileNames.forEach(fileName => unlinkSync(join(outDir, fileName)));
125
137
  };
126
138
 
139
+ // webpack emitted binaries as files (`asset/resource`, publicPath resolved at runtime) and
140
+ // inlined only SVGs under svg-url-loader's 10 KB limit. Vite's library mode inlines every asset
141
+ // whatever `assetsInlineLimit` says — `?no-inline` is the one thing it still honours, so the
142
+ // same split is restored by tagging the id.
143
+ const EMITTED_ASSET_RE = /\.(png|jpe?g|gif|webp|avif|woff2?|eot|ttf|otf|mp4|webm|ogg|mp3|wav)$/i;
144
+ const SVG_INLINE_LIMIT = 10000;
145
+
146
+ const emitAssetsAsFilesPlugin = () => {
147
+ const resolving = new Set();
148
+
149
+ return {
150
+ name: 'corva-emit-assets-as-files',
151
+ // before vite:resolve, which would otherwise resolve the asset id first and never let this
152
+ // hook see it
153
+ enforce: 'pre',
154
+ async resolveId(source, importer, options) {
155
+ if (source.includes('?') || (!EMITTED_ASSET_RE.test(source) && !/\.svg$/i.test(source))) {
156
+ return null;
157
+ }
158
+
159
+ const key = `${importer}\u0000${source}`;
160
+
161
+ if (resolving.has(key)) {
162
+ return null;
163
+ }
164
+
165
+ resolving.add(key);
166
+
167
+ try {
168
+ const resolved = await this.resolve(source, importer, options);
169
+
170
+ if (!resolved || resolved.external) {
171
+ return null;
172
+ }
173
+
174
+ if (/\.svg$/i.test(resolved.id) && statSync(resolved.id).size < SVG_INLINE_LIMIT) {
175
+ return null;
176
+ }
177
+
178
+ return `${resolved.id}?no-inline`;
179
+ } finally {
180
+ resolving.delete(key);
181
+ }
182
+ },
183
+ };
184
+ };
185
+
186
+ // Every DC app on a dashboard renders into the same document, which is why webpack's css-loader
187
+ // ran with `modules` on for every stylesheet: nothing an app shipped could leak onto its
188
+ // neighbours. Vite decides by filename, so a plain `.css` would quietly become global. Refuse it
189
+ // instead — the selectors that really have to be global still go through `:global(...)`, exactly
190
+ // as they did under webpack.
191
+ const UNSCOPED_CSS_RE = /(?<!\.module)\.(css|scss|sass)(\?|$)/;
192
+ const STYLESHEET_IMPORTER_RE = /\.(css|scss|sass)(\?|$)/;
193
+
194
+ const rejectUnscopedCssPlugin = pwd => {
195
+ const resolving = new Set();
196
+
197
+ return {
198
+ name: 'corva-reject-unscoped-css',
199
+ enforce: 'pre',
200
+ async resolveId(source, importer, options) {
201
+ // no importer: an entry. Stylesheet importer: a sass/postcss partial, which is not a module
202
+ // in either build system.
203
+ if (!importer || !UNSCOPED_CSS_RE.test(source) || STYLESHEET_IMPORTER_RE.test(importer)) {
204
+ return null;
205
+ }
206
+
207
+ const key = `${importer}\u0000${source}`;
208
+
209
+ if (resolving.has(key)) {
210
+ return null;
211
+ }
212
+
213
+ resolving.add(key);
214
+
215
+ let resolved;
216
+
217
+ try {
218
+ resolved = await this.resolve(source, importer, options);
219
+ } finally {
220
+ resolving.delete(key);
221
+ }
222
+
223
+ // a dependency's own stylesheet is its business, and it was never scoped to this app
224
+ if (!resolved || resolved.external || resolved.id.includes('node_modules')) {
225
+ return null;
226
+ }
227
+
228
+ throw new Error(
229
+ `${relative(pwd, resolved.id)} is imported as a plain stylesheet by ${relative(pwd, importer)}.\n` +
230
+ 'Please use *.module.css — global styles may leak into other platform elements, and ' +
231
+ 'that surfaces later as weird bugs nobody traces back to this file.\n' +
232
+ 'If you really do want a global style and know what you are doing, keep the .module.css ' +
233
+ 'file and wrap that one selector:\n' +
234
+ ' :global(.my-global-class) { color: red; }',
235
+ );
236
+ },
237
+ };
238
+ };
239
+
127
240
  const getCorvaApiEnvVariables = () => {
128
241
  const apiEnv = process.env.CORVA_API_ENV || CORVA_API_ENVS.production;
129
242
 
@@ -233,7 +346,12 @@ export const createViteConfig = ({ pwd, overrides = {} }) => {
233
346
  return `this["${dep}"].default || this["${dep}"]`;
234
347
  }
235
348
 
236
- return `this["${dep}"]${id.slice(dep.length).replace(/\//g, '.')}`;
349
+ return `this["${dep}"]${id
350
+ .slice(dep.length)
351
+ .split('/')
352
+ .filter(Boolean)
353
+ .map(segment => `.${segment}`)
354
+ .join('')}`;
237
355
  };
238
356
 
239
357
  const getAlias = () => {
@@ -279,10 +397,14 @@ export const createViteConfig = ({ pwd, overrides = {} }) => {
279
397
  },
280
398
  css: {
281
399
  modules: { localsConvention: 'camelCase' },
282
- postcss: { plugins: [presetEnv({ stage: 3 })] },
400
+ // webpack's postcss-loader ran preset-env with no options, i.e. stage 2; a narrower
401
+ // stage silently stops transpiling CSS features the app already relies on
402
+ postcss: { plugins: [presetEnv()] },
283
403
  },
284
- plugins: [react(), dotEnvEndpointPlugin(pwd)],
285
- resolve: { alias: getAlias() },
404
+ plugins: [react(), dotEnvEndpointPlugin(pwd), rejectUnscopedCssPlugin(pwd)],
405
+ // webpack ran with `resolve.symlinks: false`; a yarn-linked @corva/ui has to keep
406
+ // resolving through the link, not through its real path
407
+ resolve: { alias: getAlias(), preserveSymlinks: true },
286
408
  test: {
287
409
  watch: false,
288
410
  globals: true,
@@ -334,11 +456,20 @@ export const createViteConfig = ({ pwd, overrides = {} }) => {
334
456
  return {
335
457
  ...config,
336
458
  define: {
459
+ // webpack's prod config ran `EnvironmentPlugin(process.env)`: one textual replacement
460
+ // per key, so only the keys the source actually reads reach the bundle. Without it an
461
+ // app reading e.g. process.env.CORVA_API_ENV silently compiles to undefined.
462
+ ...Object.fromEntries(
463
+ Object.entries(process.env)
464
+ .filter(([key]) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key))
465
+ .map(([key, value]) => [`process.env.${key}`, JSON.stringify(value)]),
466
+ ),
337
467
  'process.env': { NODE_ENV: 'production' },
338
468
  'process.env.NODE_ENV': '"production"',
339
469
  },
340
470
  plugins: [
341
471
  ...config.plugins,
472
+ emitAssetsAsFilesPlugin(),
342
473
  viteStaticCopy({ targets: [{ src: 'manifest.json', dest: '.' }] }),
343
474
  {
344
475
  name: 'corva-single-file-bundle',
@@ -352,6 +483,14 @@ export const createViteConfig = ({ pwd, overrides = {} }) => {
352
483
  },
353
484
  },
354
485
  ],
486
+ // an asset URL has to be resolved at runtime, from the bundle's own location: the same
487
+ // app.js is served from a different package directory for every version
488
+ experimental: {
489
+ renderBuiltUrl: (filename, { hostType }) =>
490
+ (hostType === 'css'
491
+ ? `${ASSET_MARKER}${filename}`
492
+ : { runtime: `__CORVA_ASSET_BASE__+${JSON.stringify(filename)}` }),
493
+ },
355
494
  build: {
356
495
  outDir: 'dist',
357
496
  emptyOutDir: true,
@@ -360,7 +499,6 @@ export const createViteConfig = ({ pwd, overrides = {} }) => {
360
499
  cssCodeSplit: false,
361
500
  minify: 'terser',
362
501
  terserOptions: {
363
- compress: { drop_console: true, drop_debugger: true },
364
502
  format: { comments: false },
365
503
  },
366
504
  // lib mode plus named exports keeps the IIFE compatible with the platform
@@ -378,13 +516,16 @@ export const createViteConfig = ({ pwd, overrides = {} }) => {
378
516
  format: 'iife',
379
517
  name: appIifeName,
380
518
  entryFileNames: 'app.js',
519
+ // flat next to app.js, the way webpack's default assetModuleFilename emitted them
520
+ assetFileNames: '[hash][extname]',
381
521
  globals: toGlobal,
382
522
  exports: 'named',
383
523
  // Rolldown doesn't rewrite `require()` inside bundled CJS modules (e.g.
384
524
  // react-is) to their external global references. This shim resolves them
385
525
  // to the globals the IIFE receives as params.
386
526
  intro:
387
- 'var require=function(id){var g=globalThis[id];return g&&g.default||g;};',
527
+ `var require=function(id){var g=globalThis[id];return g&&g.default||g;};` +
528
+ `var __CORVA_ASSET_BASE__=${ASSET_BASE_EXPR};`,
388
529
  },
389
530
  plugins: [
390
531
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@corva/create-app",
3
- "version": "0.124.0",
3
+ "version": "0.125.0",
4
4
  "private": false,
5
5
  "description": "Create an app to use it in CORVA.AI",
6
6
  "keywords": [
@@ -20,4 +20,5 @@ jobs:
20
20
  prod-api-key: ${{ secrets.API_KEY }}
21
21
  npm-token: ${{ secrets.CORVA_NPM_TOKEN }}
22
22
  jira-user-email: ${{ secrets.JIRA_AUTOTEST_USERNAME }}
23
+ linear-api-key: ${{ secrets.LINEAR_API_KEY }}
23
24
  jira-api-token: ${{ secrets.JIRA_AUTOTEST_API_TOKEN }}