@sasjs/server 3.6.0 → 3.6.2

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.
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ var path_1 = __importDefault(require("path"));
7
+ var swaggerUiAssets_1 = require("../src/utils/swaggerUiAssets");
8
+ // Runs from `npm run initial`, so the assets are in place for both the dev server
9
+ // (which serves api/public directly under ts-node) and the packaged build (which
10
+ // copies api/public into build/public). The generated files are gitignored: the
11
+ // installed swagger-ui-dist devDependency is the only source of truth.
12
+ var destination = path_1.default.resolve(__dirname, '..', 'public', 'SASjsApi');
13
+ (0, swaggerUiAssets_1.copySwaggerUiAssets)(destination);
14
+ console.log("Copied Swagger UI assets to ".concat(destination));
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  var express_1 = __importDefault(require("express"));
7
- var swagger_ui_express_1 = __importDefault(require("swagger-ui-express"));
8
7
  var middlewares_1 = require("../../middlewares");
9
8
  var info_1 = __importDefault(require("./info"));
10
9
  var admin_1 = __importDefault(require("./admin"));
@@ -34,17 +33,8 @@ router.use('/code', middlewares_1.authenticateAccessToken, code_1.default);
34
33
  router.use('/user', middlewares_1.desktopRestrict, user_1.default);
35
34
  router.use('/permission', middlewares_1.desktopRestrict, middlewares_1.authenticateAccessToken, permission_1.default);
36
35
  router.use('/authConfig', middlewares_1.desktopRestrict, middlewares_1.authenticateAccessToken, middlewares_1.verifyAdmin, authConfig_1.default);
37
- router.use('/', swagger_ui_express_1.default.serve, swagger_ui_express_1.default.setup(undefined, {
38
- swaggerOptions: {
39
- url: '/swagger.yaml',
40
- requestInterceptor: function (request) {
41
- request.credentials = 'include';
42
- var cookie = document.cookie;
43
- var startIndex = cookie.indexOf('XSRF-TOKEN');
44
- var csrf = cookie.slice(startIndex + 11).split('; ')[0];
45
- request.headers['X-XSRF-TOKEN'] = csrf;
46
- return request;
47
- }
48
- }
49
- }));
36
+ // The API documentation page is a file in public/SASjsApi/ (index.html plus the
37
+ // assets copied beside it at build time), served by the express.static mount in
38
+ // app.ts. It is not a route here: the page and its assets are plain files, and
39
+ // nothing in them is rendered per request.
50
40
  exports.default = router;
@@ -58,7 +58,7 @@ var controllers_1 = require("../../../controllers/");
58
58
  var utils_1 = require("../../../utils");
59
59
  var crypto_1 = require("crypto");
60
60
  var deployPayload = {
61
- appLoc: 'string',
61
+ appLoc: '/Public/example',
62
62
  streamWebFolder: 'string',
63
63
  fileTree: {
64
64
  members: [
@@ -772,7 +772,7 @@ describe('permission', function () {
772
772
  case 1:
773
773
  accessToken = _a.sent();
774
774
  return [4 /*yield*/, (0, supertest_1.default)(app)
775
- .get('/SASjsApi/drive/deploy')
775
+ .post('/SASjsApi/drive/deploy')
776
776
  .auth(accessToken, { type: 'bearer' })
777
777
  .send(deployPayload)
778
778
  .expect(200)];
@@ -3,63 +3,85 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- var crypto_1 = __importDefault(require("crypto"));
7
6
  var fs_1 = __importDefault(require("fs"));
7
+ var os_1 = __importDefault(require("os"));
8
8
  var path_1 = __importDefault(require("path"));
9
+ var swaggerUiAssets_1 = require("../../../utils/swaggerUiAssets");
9
10
  /**
10
- * Guard for the Swagger UI assets the API serves from disk.
11
+ * Guard for the Swagger UI the API serves.
11
12
  *
12
- * `express.static('../public')` is mounted at the root, so a file under
13
- * `public/SASjsApi/` wins over the copy `swagger-ui-express` would serve from
14
- * `swagger-ui-dist`. The copy exists on purpose: the packaged executable bundles
15
- * `build/public/**` and cannot read the dependency from disk. The cost is that a
16
- * stale copy silently styles a newer UI.
13
+ * `public/SASjsApi/index.html` is the documentation page, and the assets beside
14
+ * it are copied from the pinned `swagger-ui-dist` devDependency by
15
+ * `scripts/copySwaggerUi.ts`. Nothing from that package is installed at
16
+ * runtime, because it depends on `@scarf/scarf`, whose postinstall reports the
17
+ * install to scarf.sh.
17
18
  *
18
- * That is what happened here. A 2021 stylesheet served beside the 5.33.0 bundle
19
- * collapsed every operation path to one character per line: the bundle renders
20
- * `.opblock-summary-path-description-wrapper`, which the older stylesheet does
21
- * not know about, while its own `.opblock-summary-path` rule still caps the
22
- * width at `calc(100% - 110px - 15rem)`. With no wrapper rule the cap resolved
23
- * against a container narrower than the subtraction, so the path wrapped at
24
- * every character.
25
- *
26
- * These tests keep the copy in step with the package that renders the DOM.
19
+ * Two things can therefore go wrong silently, and both are covered here: a page
20
+ * that points at an asset the build does not copy (a blank or unstyled UI), and
21
+ * a page that reaches a third party (the leak this arrangement exists to stop).
27
22
  */
28
23
  var apiRoot = path_1.default.resolve(__dirname, '../../../..');
29
- var servedCssPath = path_1.default.join(apiRoot, 'public', 'SASjsApi', 'swagger-ui.css');
30
- var distDirectory = path_1.default.dirname(require.resolve('swagger-ui-dist/package.json'));
31
- var distCssPath = path_1.default.join(distDirectory, 'swagger-ui.css');
32
- var distVersion = require('swagger-ui-dist/package.json').version;
33
- var sha256 = function (buffer) {
34
- return crypto_1.default.createHash('sha256').update(buffer).digest('hex');
35
- };
24
+ var pagePath = path_1.default.join(apiRoot, 'public', 'SASjsApi', 'index.html');
25
+ var initPath = path_1.default.join(apiRoot, 'public', 'SASjsApi', 'swagger-ui-init.js');
26
+ var page = function () { return fs_1.default.readFileSync(pagePath, 'utf8'); };
27
+ var init = function () { return fs_1.default.readFileSync(initPath, 'utf8'); };
36
28
  describe('bundled Swagger UI assets', function () {
37
- it('serves the stylesheet shipped by swagger-ui-dist', function () {
38
- var served = fs_1.default.readFileSync(servedCssPath);
39
- var installed = fs_1.default.readFileSync(distCssPath);
40
- if (!served.equals(installed)) {
41
- throw new Error("public/SASjsApi/swagger-ui.css is out of step with swagger-ui-dist@".concat(distVersion, ".\n") +
42
- " served: ".concat(sha256(served), "\n") +
43
- " installed: ".concat(sha256(installed), "\n") +
44
- 'The API serves the copy in public/, so a stale copy styles a newer UI. Refresh it with:\n' +
45
- ' cp node_modules/swagger-ui-dist/swagger-ui.css public/SASjsApi/swagger-ui.css');
29
+ var destination;
30
+ beforeAll(function () {
31
+ destination = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'sasjs-swagger-ui-'));
32
+ (0, swaggerUiAssets_1.copySwaggerUiAssets)(destination);
33
+ });
34
+ afterAll(function () {
35
+ fs_1.default.rmSync(destination, { recursive: true, force: true });
36
+ });
37
+ it('copies every asset the page loads from swagger-ui-dist', function () {
38
+ var distFolder = (0, swaggerUiAssets_1.swaggerUiDistFolder)();
39
+ for (var _i = 0, swaggerUiAssets_2 = swaggerUiAssets_1.swaggerUiAssets; _i < swaggerUiAssets_2.length; _i++) {
40
+ var file = swaggerUiAssets_2[_i];
41
+ expect(fs_1.default.existsSync(path_1.default.join(destination, file))).toBe(true);
42
+ expect(fs_1.default
43
+ .readFileSync(path_1.default.join(destination, file))
44
+ .equals(fs_1.default.readFileSync(path_1.default.join(distFolder, file)))).toBe(true);
45
+ }
46
+ // Apache-2.0: the licence and notice travel with the files
47
+ for (var _a = 0, swaggerUiNotices_1 = swaggerUiAssets_1.swaggerUiNotices; _a < swaggerUiNotices_1.length; _a++) {
48
+ var file = swaggerUiNotices_1[_a];
49
+ expect(fs_1.default.existsSync(path_1.default.join(destination, "swagger-ui-".concat(file)))).toBe(true);
50
+ }
51
+ });
52
+ it('loads the page and its styles from this folder, not from a third party', function () {
53
+ var html = page();
54
+ var loaded = [
55
+ 'swagger-ui.css',
56
+ 'swagger-ui-bundle.js',
57
+ 'swagger-ui-standalone-preset.js',
58
+ 'swagger-ui-init.js',
59
+ 'favicon-32x32.png'
60
+ ];
61
+ for (var _i = 0, loaded_1 = loaded; _i < loaded_1.length; _i++) {
62
+ var file = loaded_1[_i];
63
+ expect(html).toContain("./".concat(file));
46
64
  }
65
+ var external = html.match(/(src|href)\s*=\s*["']?https?:\/\//gi) || [];
66
+ expect(external).toEqual([]);
47
67
  });
48
- it('styles the summary wrapper the current bundle renders', function () {
49
- var css = fs_1.default.readFileSync(servedCssPath, 'utf8');
50
- // Without this rule the wrapper stays at its initial `display: block`, the
51
- // path's `max-width: calc(100% - ...)` resolves against a narrow container,
52
- // and every route renders one character per line.
53
- expect(css).toContain('.opblock-summary-path-description-wrapper');
68
+ it('starts Swagger UI against the local spec with no third-party validator', function () {
69
+ var script = init();
70
+ expect(script).toContain("url: '/swagger.yaml'");
71
+ // the online validator posts the spec URL to validator.swagger.io
72
+ expect(script).toContain('validatorUrl: null');
73
+ // the API's own calls carry the session cookie and the CSRF header
74
+ expect(script).toContain("request.credentials = 'include'");
75
+ expect(script).toContain('X-XSRF-TOKEN');
54
76
  });
55
- it('does not shadow the generated swagger-ui-init.js', function () {
56
- // `swagger-ui-express` builds this file in memory from the options passed
57
- // to `setup()` (its `swaggerInitFunction`), so it never needs to be on
58
- // disk, and a checked-in copy wins over the generated one through the same
59
- // static mount. That copy silently pins the Swagger UI to whatever
60
- // `swaggerOptions` were in place when it was written, so edits to the
61
- // route's options have no effect.
62
- var initPath = path_1.default.join(apiRoot, 'public', 'SASjsApi', 'swagger-ui-init.js');
63
- expect(fs_1.default.existsSync(initPath)).toBe(false);
77
+ it('keeps swagger-ui out of the runtime dependencies', function () {
78
+ // swagger-ui-dist is a devDependency: it is the source of the copied
79
+ // assets, and its tree carries @scarf/scarf's postinstall. Installing
80
+ // @sasjs/server must not run anything that calls out.
81
+ var manifest = JSON.parse(fs_1.default.readFileSync(path_1.default.join(apiRoot, 'package.json'), 'utf8'));
82
+ var runtime = Object.keys(manifest.dependencies || {});
83
+ expect(runtime).not.toContain('swagger-ui-express');
84
+ expect(runtime).not.toContain('swagger-ui-dist');
85
+ expect(Object.keys(manifest.devDependencies || {})).toContain('swagger-ui-dist');
64
86
  });
65
87
  });
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env node
1
2
  "use strict";
2
3
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
4
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BUILD_INFO = void 0;
4
4
  // Generated by scripts/generateBuildInfo.ts. Do not edit by hand.
5
5
  exports.BUILD_INFO = {
6
- version: "v3.6.0",
7
- gitHash: "14c706a",
8
- buildDate: "2026-10-30T13:00:41.471Z"
6
+ version: "v3.6.2",
7
+ gitHash: "6141e36",
8
+ buildDate: "2026-10-30T14:54:32.248Z"
9
9
  };
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.copySwaggerUiAssets = exports.swaggerUiDistFolder = exports.swaggerUiNotices = exports.swaggerUiAssets = void 0;
7
+ var fs_1 = __importDefault(require("fs"));
8
+ var path_1 = __importDefault(require("path"));
9
+ /**
10
+ * The Swagger UI assets the API serves from its own public folder.
11
+ *
12
+ * They are generated from `swagger-ui-dist` at build time instead of being
13
+ * carried as a runtime dependency. `swagger-ui-dist` depends on `@scarf/scarf`,
14
+ * whose postinstall reports the installation to scarf.sh - a published package
15
+ * must not make outbound calls when it is installed, so nothing from
16
+ * swagger-ui-dist is installed with it. `swagger-ui-dist` stays a devDependency:
17
+ * it is the source these files are copied from, and swaggerAssets.spec.ts
18
+ * compares the two.
19
+ */
20
+ exports.swaggerUiAssets = [
21
+ 'swagger-ui.css',
22
+ 'swagger-ui-bundle.js',
23
+ 'swagger-ui-standalone-preset.js',
24
+ 'favicon-16x16.png',
25
+ 'favicon-32x32.png',
26
+ 'oauth2-redirect.html'
27
+ ];
28
+ /** Apache-2.0 requires the licence and the notice to travel with the files. */
29
+ exports.swaggerUiNotices = ['LICENSE', 'NOTICE'];
30
+ var swaggerUiDistFolder = function () {
31
+ return path_1.default.dirname(require.resolve('swagger-ui-dist/package.json'));
32
+ };
33
+ exports.swaggerUiDistFolder = swaggerUiDistFolder;
34
+ /**
35
+ * Copies the browser assets, and the licence files, into `destination`.
36
+ *
37
+ * Same function for the build (`api/scripts/copySwaggerUi.ts`) and for the
38
+ * spec, so what the test compares is what the build produces.
39
+ */
40
+ var copySwaggerUiAssets = function (destination) {
41
+ var source = (0, exports.swaggerUiDistFolder)();
42
+ fs_1.default.mkdirSync(destination, { recursive: true });
43
+ for (var _i = 0, swaggerUiAssets_1 = exports.swaggerUiAssets; _i < swaggerUiAssets_1.length; _i++) {
44
+ var file = swaggerUiAssets_1[_i];
45
+ fs_1.default.copyFileSync(path_1.default.join(source, file), path_1.default.join(destination, file));
46
+ }
47
+ for (var _a = 0, swaggerUiNotices_1 = exports.swaggerUiNotices; _a < swaggerUiNotices_1.length; _a++) {
48
+ var file = swaggerUiNotices_1[_a];
49
+ fs_1.default.copyFileSync(path_1.default.join(source, file), path_1.default.join(destination, "swagger-ui-".concat(file)));
50
+ }
51
+ return destination;
52
+ };
53
+ exports.copySwaggerUiAssets = copySwaggerUiAssets;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sasjs/server",
3
- "version": "3.6.0",
3
+ "version": "3.6.2",
4
4
  "description": "NodeJS wrapper for calling the SAS binary executable",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/sasjs/server",
@@ -43,7 +43,6 @@
43
43
  "morgan": "^1.12.1",
44
44
  "multer": "^2.4.0",
45
45
  "rotating-file-stream": "^3.2.10",
46
- "swagger-ui-express": "5.0.1",
47
46
  "tsoa": "5.1.1",
48
47
  "unzipper": "^0.12.5",
49
48
  "url": "^0.11.4"