@sasjs/cli 6.29.0 → 6.30.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.
@@ -946,6 +946,58 @@ const homeService = API.createHomeService(async () => {
946
946
  }
947
947
  })
948
948
 
949
+ /**
950
+ * The lint rules a SAS file is linted against: the nearest `.sasjslint` at or
951
+ * above its folder on the drive.
952
+ *
953
+ * This is the server's rule, applied here so the demo behaves like the
954
+ * installation it demonstrates: a folder in the tree carries rules for
955
+ * everything beneath it, and changing a rule is a plain edit of that file. It
956
+ * is why the build seeds a `.sasjslint` at the drive root - a file that is
957
+ * displayed but never read would be worse than absent.
958
+ *
959
+ * Paths go through the package's own normaliser, so a backslashed or escaping
960
+ * path resolves the way the drive routes resolve it rather than by a second set
961
+ * of string rules that could disagree with them.
962
+ *
963
+ * A file that is missing, is not JSON, or parses to something that is not an
964
+ * object is skipped and the walk continues upwards - a broken rules file must
965
+ * never break linting or formatting. A walk that reaches the root with nothing
966
+ * to show returns undefined, which lints against the linter's own defaults: the
967
+ * answer for a drive with no rules file at all.
968
+ */
969
+ const LINT_CONFIG_FILE = '.sasjslint'
970
+
971
+ const lintConfig = async (filePath) => {
972
+ // A path that escapes the drive has no folder to walk from, and a buffer that
973
+ // names no file has none either: both take the drive root's rules, which is
974
+ // what a server answers for each - a refused path there falls back to the
975
+ // root file rather than to the linter's defaults.
976
+ let folder = API.dirname(API.normaliseDrivePath(filePath) ?? '/')
977
+ for (;;) {
978
+ const configPath =
979
+ folder === '/' ? `/${LINT_CONFIG_FILE}` : `${folder}/${LINT_CONFIG_FILE}`
980
+ const contents = await DRIVE_STORE.read(configPath).catch(() => null)
981
+ const parsed = parseLintConfig(contents)
982
+ if (parsed) return new CODE.LintConfig(parsed)
983
+ if (folder === '/') return undefined
984
+ folder = API.dirname(folder)
985
+ }
986
+ }
987
+
988
+ /** A `.sasjslint` as the object a configuration is built from, or null. */
989
+ const parseLintConfig = (contents) => {
990
+ if (contents === null || contents === undefined) return null
991
+ const text =
992
+ typeof contents === 'string' ? contents : new TextDecoder().decode(contents)
993
+ try {
994
+ const parsed = JSON.parse(text)
995
+ return parsed && typeof parsed === 'object' ? parsed : null
996
+ } catch (e) {
997
+ return null
998
+ }
999
+ }
1000
+
949
1001
  const SERVICES = {
950
1002
  drive: API.createDriveService(DRIVE_STORE),
951
1003
  // The package's browser fixture states one run time; this host serves two, and
@@ -970,10 +1022,11 @@ const SERVICES = {
970
1022
  directory: API.createDirectoryService(IDENTITY_STORE, directorySource),
971
1023
  appStream: APP_STREAM,
972
1024
  home: homeService,
973
- // the package's own code service over the bundled linter; the host passes no
974
- // lint config, so a buffer is linted against the linter's defaults - a mock
975
- // has no `.sasjslint` tree to resolve one from
976
- code: CODE.createCodeService({})
1025
+ // the package's own code service over the bundled linter, with the drive's
1026
+ // own rules: a buffer is linted and formatted against the nearest
1027
+ // `.sasjslint` at or above its folder, so editing that file changes the
1028
+ // editor here exactly as it does on a server
1029
+ code: CODE.createCodeService({ lintConfig })
977
1030
  }
978
1031
 
979
1032
  /** The routes this host performs itself, because the answer is not JSON. */
@@ -130,10 +130,11 @@ var toPosix = function (value) { return value.split(path_1.default.sep).join('/'
130
130
  * configuration by walking a filesystem - but the linter's core (`lintText`,
131
131
  * `formatText`) reads only the text it is given and the configuration it is
132
132
  * passed, so the two can run anywhere the module graph can be built. This
133
- * bundle is that graph: the package's own code service, the linter it wraps, and
134
- * shims for the four node modules reached at import time (`path` and `os` for
135
- * resolution, `fs` and `@sasjs/utils/file` for the config lookup that a mock
136
- * never makes, because it hands the linter the default configuration).
133
+ * bundle is that graph: the package's own code service, the configuration class
134
+ * a host builds from its own rules file, the linter behind both, and shims for
135
+ * the four node modules reached at import time (`path` and `os` for resolution,
136
+ * `fs` and `@sasjs/utils/file` for the linter's own filesystem lookup, which
137
+ * this host never makes - it hands the routes the rules it read from the drive).
137
138
  *
138
139
  * It is built from the installed @sasjs/api and @sasjs/lint on every mock
139
140
  * build, so the rules a client sees are the version the CLI resolved - a
@@ -142,8 +143,8 @@ var toPosix = function (value) { return value.split(path_1.default.sep).join('/'
142
143
  var LINT_BUNDLE_FILES = {
143
144
  'entry.js': [
144
145
  "import './process.js'",
145
- "import { createCodeService } from '@sasjs/api/code'",
146
- 'globalThis.SASjsCode = { createCodeService }'
146
+ "import { createCodeService, LintConfig } from '@sasjs/api/code'",
147
+ 'globalThis.SASjsCode = { createCodeService, LintConfig }'
147
148
  ].join('\n'),
148
149
  'process.js': [
149
150
  '// the linter reads process.env for a default and process.cwd() for a root;',
@@ -297,6 +297,7 @@ describe('browser-hosted build', function () {
297
297
  vm.createContext(realm);
298
298
  vm.runInContext(bundle, realm);
299
299
  expect(typeof realm.SASjsCode.createCodeService).toBe('function');
300
+ expect(typeof realm.SASjsCode.LintConfig).toBe('function');
300
301
  return [2 /*return*/];
301
302
  }
302
303
  });
@@ -1412,6 +1412,285 @@ describe('service worker host', function () {
1412
1412
  }
1413
1413
  });
1414
1414
  }); });
1415
+ /**
1416
+ * The rules are the drive's, not the linter's defaults.
1417
+ *
1418
+ * The Studio shows a visitor the drive's `.sasjslint` and invites them to
1419
+ * edit it, so a rules file nothing read would be a displayed lie. Each case
1420
+ * below changes a rule and reads the answer back through the route the
1421
+ * editor calls, because "the resolver was passed" is not the claim - "the
1422
+ * squiggle went away" is.
1423
+ */
1424
+ describe('the drive’s .sasjslint', function () {
1425
+ var rootConfig = '/sasjs/drive/files/.sasjslint';
1426
+ /** 81 characters: over the default 80, under the 100 these cases set. */
1427
+ var longLine = 'x'.repeat(81);
1428
+ var code = "data _null_;\n".concat(longLine, "\nrun;\n");
1429
+ var tooLong = function (diagnostics) {
1430
+ return diagnostics.filter(function (diagnostic) {
1431
+ return String(diagnostic.message).startsWith('Line exceeds maximum length');
1432
+ });
1433
+ };
1434
+ var lint = function (worker, body) { return __awaiter(void 0, void 0, void 0, function () {
1435
+ var response;
1436
+ return __generator(this, function (_a) {
1437
+ switch (_a.label) {
1438
+ case 0: return [4 /*yield*/, worker.dispatch('/SASjsApi/code/lint', runRequest(body))];
1439
+ case 1:
1440
+ response = _a.sent();
1441
+ expect(response.status).toBe(200);
1442
+ return [2 /*return*/, response.json()];
1443
+ }
1444
+ });
1445
+ }); };
1446
+ it('lints against the rules file at the drive root', function () { return __awaiter(void 0, void 0, void 0, function () {
1447
+ var worker, _a, _b, _c, _d;
1448
+ var _e, _f;
1449
+ return __generator(this, function (_g) {
1450
+ switch (_g.label) {
1451
+ case 0:
1452
+ worker = seeded();
1453
+ return [4 /*yield*/, worker.runtime.load((_e = {},
1454
+ _e[rootConfig] = JSON.stringify({ maxLineLength: 100 }),
1455
+ _e[drivePath('program.sas')] = code,
1456
+ _e))];
1457
+ case 1:
1458
+ _g.sent();
1459
+ _a = expect;
1460
+ _b = tooLong;
1461
+ return [4 /*yield*/, lint(worker, { code: code, filePath: appLoc + '/program.sas' })];
1462
+ case 2:
1463
+ _a.apply(void 0, [_b.apply(void 0, [_g.sent()])]).toHaveLength(0);
1464
+ // the same buffer with the default 80 in place: the finding the rule
1465
+ // exists to report
1466
+ return [4 /*yield*/, worker.runtime.load((_f = {},
1467
+ _f[rootConfig] = JSON.stringify({ maxLineLength: 80 }),
1468
+ _f[drivePath('program.sas')] = code,
1469
+ _f))];
1470
+ case 3:
1471
+ // the same buffer with the default 80 in place: the finding the rule
1472
+ // exists to report
1473
+ _g.sent();
1474
+ _c = expect;
1475
+ _d = tooLong;
1476
+ return [4 /*yield*/, lint(worker, { code: code, filePath: appLoc + '/program.sas' })];
1477
+ case 4:
1478
+ _c.apply(void 0, [_d.apply(void 0, [_g.sent()])]).toHaveLength(1);
1479
+ return [2 /*return*/];
1480
+ }
1481
+ });
1482
+ }); });
1483
+ it('re-reads the rules per request, so an edit takes effect', function () { return __awaiter(void 0, void 0, void 0, function () {
1484
+ var worker, path, _a, _b, form, patched, _c, _d;
1485
+ var _e;
1486
+ return __generator(this, function (_f) {
1487
+ switch (_f.label) {
1488
+ case 0:
1489
+ worker = seeded();
1490
+ path = appLoc + '/program.sas';
1491
+ return [4 /*yield*/, worker.runtime.load((_e = {},
1492
+ _e[rootConfig] = JSON.stringify({ maxLineLength: 80 }),
1493
+ _e[drivePath('program.sas')] = code,
1494
+ _e))];
1495
+ case 1:
1496
+ _f.sent();
1497
+ _a = expect;
1498
+ _b = tooLong;
1499
+ return [4 /*yield*/, lint(worker, { code: code, filePath: path })];
1500
+ case 2:
1501
+ _a.apply(void 0, [_b.apply(void 0, [_f.sent()])]).toHaveLength(1);
1502
+ form = new FormData();
1503
+ form.set('file', new Blob([JSON.stringify({ maxLineLength: 100 })], {
1504
+ type: 'application/json'
1505
+ }), '.sasjslint');
1506
+ return [4 /*yield*/, worker.dispatch("/SASjsApi/drive/file?_filePath=".concat(encodeURIComponent('/.sasjslint')), { method: 'PATCH', body: form })];
1507
+ case 3:
1508
+ patched = _f.sent();
1509
+ expect(patched.status).toBe(200);
1510
+ _c = expect;
1511
+ _d = tooLong;
1512
+ return [4 /*yield*/, lint(worker, { code: code, filePath: path })];
1513
+ case 4:
1514
+ _c.apply(void 0, [_d.apply(void 0, [_f.sent()])]).toHaveLength(0);
1515
+ return [2 /*return*/];
1516
+ }
1517
+ });
1518
+ }); });
1519
+ it('gives a folder its own rules for everything beneath it', function () { return __awaiter(void 0, void 0, void 0, function () {
1520
+ var worker, _a, _b, _c, _d;
1521
+ var _e;
1522
+ return __generator(this, function (_f) {
1523
+ switch (_f.label) {
1524
+ case 0:
1525
+ worker = seeded();
1526
+ return [4 /*yield*/, worker.runtime.load((_e = {},
1527
+ _e[rootConfig] = JSON.stringify({ maxLineLength: 80 }),
1528
+ // the nearer file relaxes the rule for this folder alone
1529
+ _e["/sasjs/drive/files".concat(appLoc, "/.sasjslint")] = JSON.stringify({
1530
+ maxLineLength: 200
1531
+ }),
1532
+ _e))];
1533
+ case 1:
1534
+ _f.sent();
1535
+ _a = expect;
1536
+ _b = tooLong;
1537
+ return [4 /*yield*/, lint(worker, {
1538
+ code: code,
1539
+ filePath: "".concat(appLoc, "/services/public/program.sas")
1540
+ })];
1541
+ case 2:
1542
+ _a.apply(void 0, [_b.apply(void 0, [_f.sent()])]).toHaveLength(0);
1543
+ // outside that folder the root file still applies
1544
+ _c = expect;
1545
+ _d = tooLong;
1546
+ return [4 /*yield*/, lint(worker, {
1547
+ code: code,
1548
+ filePath: '/home/sasjsadmin/program.sas'
1549
+ })];
1550
+ case 3:
1551
+ // outside that folder the root file still applies
1552
+ _c.apply(void 0, [_d.apply(void 0, [_f.sent()])]).toHaveLength(1);
1553
+ return [2 /*return*/];
1554
+ }
1555
+ });
1556
+ }); });
1557
+ it('finds the rules for a path spelled the way a Windows client sends it', function () { return __awaiter(void 0, void 0, void 0, function () {
1558
+ var worker, _a, _b;
1559
+ var _c;
1560
+ return __generator(this, function (_d) {
1561
+ switch (_d.label) {
1562
+ case 0:
1563
+ worker = seeded();
1564
+ return [4 /*yield*/, worker.runtime.load((_c = {},
1565
+ _c["/sasjs/drive/files".concat(appLoc, "/.sasjslint")] = JSON.stringify({
1566
+ maxLineLength: 100
1567
+ }),
1568
+ _c))];
1569
+ case 1:
1570
+ _d.sent();
1571
+ _a = expect;
1572
+ _b = tooLong;
1573
+ return [4 /*yield*/, lint(worker, {
1574
+ code: code,
1575
+ filePath: "".concat(appLoc, "\\services\\public\\program.sas")
1576
+ })];
1577
+ case 2:
1578
+ _a.apply(void 0, [_b.apply(void 0, [_d.sent()])]).toHaveLength(0);
1579
+ return [2 /*return*/];
1580
+ }
1581
+ });
1582
+ }); });
1583
+ it('skips a rules file that does not parse and uses the one above', function () { return __awaiter(void 0, void 0, void 0, function () {
1584
+ var worker, _a, _b;
1585
+ var _c;
1586
+ return __generator(this, function (_d) {
1587
+ switch (_d.label) {
1588
+ case 0:
1589
+ worker = seeded();
1590
+ return [4 /*yield*/, worker.runtime.load((_c = {},
1591
+ _c[rootConfig] = JSON.stringify({ maxLineLength: 100 }),
1592
+ _c["/sasjs/drive/files".concat(appLoc, "/.sasjslint")] = '{ this is not json',
1593
+ _c))
1594
+ // a broken near file must not break linting, and must not silently
1595
+ // replace the rules that were working
1596
+ ];
1597
+ case 1:
1598
+ _d.sent();
1599
+ // a broken near file must not break linting, and must not silently
1600
+ // replace the rules that were working
1601
+ _a = expect;
1602
+ _b = tooLong;
1603
+ return [4 /*yield*/, lint(worker, {
1604
+ code: code,
1605
+ filePath: "".concat(appLoc, "/services/public/program.sas")
1606
+ })];
1607
+ case 2:
1608
+ // a broken near file must not break linting, and must not silently
1609
+ // replace the rules that were working
1610
+ _a.apply(void 0, [_b.apply(void 0, [_d.sent()])]).toHaveLength(0);
1611
+ return [2 /*return*/];
1612
+ }
1613
+ });
1614
+ }); });
1615
+ it('takes the drive root rules for a buffer with no file, and for a path that escapes the drive', function () { return __awaiter(void 0, void 0, void 0, function () {
1616
+ var worker, _a, _b, _c, _d;
1617
+ var _e;
1618
+ return __generator(this, function (_f) {
1619
+ switch (_f.label) {
1620
+ case 0:
1621
+ worker = seeded();
1622
+ return [4 /*yield*/, worker.runtime.load((_e = {},
1623
+ _e[rootConfig] = JSON.stringify({ maxLineLength: 100 }),
1624
+ _e))];
1625
+ case 1:
1626
+ _f.sent();
1627
+ _a = expect;
1628
+ _b = tooLong;
1629
+ return [4 /*yield*/, lint(worker, { code: code })];
1630
+ case 2:
1631
+ _a.apply(void 0, [_b.apply(void 0, [_f.sent()])]).toHaveLength(0);
1632
+ _c = expect;
1633
+ _d = tooLong;
1634
+ return [4 /*yield*/, lint(worker, {
1635
+ code: code,
1636
+ filePath: '../../outside/program.sas'
1637
+ })];
1638
+ case 3:
1639
+ _c.apply(void 0, [_d.apply(void 0, [_f.sent()])]).toHaveLength(0);
1640
+ return [2 /*return*/];
1641
+ }
1642
+ });
1643
+ }); });
1644
+ it('falls back to the linter defaults when the drive has no rules file', function () { return __awaiter(void 0, void 0, void 0, function () {
1645
+ var worker, _a, _b;
1646
+ var _c;
1647
+ return __generator(this, function (_d) {
1648
+ switch (_d.label) {
1649
+ case 0:
1650
+ worker = seeded();
1651
+ return [4 /*yield*/, worker.runtime.load((_c = {}, _c[drivePath('program.sas')] = code, _c))];
1652
+ case 1:
1653
+ _d.sent();
1654
+ _a = expect;
1655
+ _b = tooLong;
1656
+ return [4 /*yield*/, lint(worker, {
1657
+ code: code,
1658
+ filePath: "".concat(appLoc, "/program.sas")
1659
+ })];
1660
+ case 2:
1661
+ _a.apply(void 0, [_b.apply(void 0, [_d.sent()])]).toHaveLength(1);
1662
+ return [2 /*return*/];
1663
+ }
1664
+ });
1665
+ }); });
1666
+ it('formats with the rules file too', function () { return __awaiter(void 0, void 0, void 0, function () {
1667
+ var worker, formatted, _a;
1668
+ var _b;
1669
+ return __generator(this, function (_c) {
1670
+ switch (_c.label) {
1671
+ case 0:
1672
+ worker = seeded();
1673
+ return [4 /*yield*/, worker.runtime.load((_b = {},
1674
+ _b[rootConfig] = JSON.stringify({
1675
+ hasDoxygenHeader: true,
1676
+ defaultHeader: '/**\n @brief the drive header\n**/'
1677
+ }),
1678
+ _b))];
1679
+ case 1:
1680
+ _c.sent();
1681
+ return [4 /*yield*/, worker.dispatch('/SASjsApi/code/format', runRequest({ code: code, filePath: '/home/sasjsadmin/program.sas' }))];
1682
+ case 2:
1683
+ formatted = _c.sent();
1684
+ expect(formatted.status).toBe(200);
1685
+ _a = expect;
1686
+ return [4 /*yield*/, formatted.text()];
1687
+ case 3:
1688
+ _a.apply(void 0, [_c.sent()]).toContain('@brief the drive header');
1689
+ return [2 /*return*/];
1690
+ }
1691
+ });
1692
+ }); });
1693
+ });
1415
1694
  it('keeps an app’s URL and tiles inside the folder the site is served from', function () { return __awaiter(void 0, void 0, void 0, function () {
1416
1695
  var worker, archive, form, published, app, listing, apps, landing, _a, page, _b;
1417
1696
  return __generator(this, function (_c) {
@@ -44,11 +44,9 @@ var path_1 = __importDefault(require("path"));
44
44
  var test_1 = require("../../../utils/test");
45
45
  var utils_1 = require("@sasjs/utils");
46
46
  var config_1 = require("../../../utils/config");
47
- var minimalAppFiles_1 = require("./minimalAppFiles");
48
- var reactAppFiles_1 = require("./reactAppFiles");
49
- var angularAppFiles_1 = require("./angularAppFiles");
50
47
  var create_1 = require("../create");
51
48
  var utils_2 = require("../../../utils");
49
+ var utils_3 = require("../../../utils/utils");
52
50
  describe('sasjs create', function () {
53
51
  beforeAll(function () {
54
52
  dotenv_1.default.config();
@@ -249,28 +247,34 @@ describe('sasjs create', function () {
249
247
  }); });
250
248
  });
251
249
  var verifyCreateWeb = function (appName, appType) { return __awaiter(void 0, void 0, void 0, function () {
252
- var fileStructure;
250
+ var template, docsCandidates, present;
253
251
  return __generator(this, function (_a) {
254
252
  switch (_a.label) {
255
- case 0:
256
- fileStructure = appType === 'minimal'
257
- ? minimalAppFiles_1.minimalAppFiles
258
- : appType === 'react'
259
- ? reactAppFiles_1.reactAppFiles
260
- : appType === 'angular'
261
- ? angularAppFiles_1.angularAppFiles
262
- : minimalAppFiles_1.minimalAppFiles;
263
- fileStructure.folderName = appName;
264
- return [4 /*yield*/, (0, test_1.verifyFolder)(fileStructure)];
253
+ case 0: return [4 /*yield*/, (0, test_1.getTemplateFileTree)(utils_3.seedAppUrls[appType])];
265
254
  case 1:
255
+ template = _a.sent();
256
+ template.folderName = appName;
257
+ return [4 /*yield*/, (0, test_1.verifyFolder)(template)];
258
+ case 2:
266
259
  _a.sent();
267
260
  return [4 /*yield*/, (0, test_1.verifyPackageJsonContent)(appName)
268
- // A seed app carries its dependencies in the template's package.json, so the
269
- // command create issues for it is a plain install - the same trade as above:
270
- // the stub writes what npm would, and this asserts the CLI asked for it.
261
+ // The docs are a submodule fetched after the archive, and the archive carries
262
+ // the folder empty - so the derived tree can only assert that the folder is
263
+ // there. A create whose docs download silently failed used to pass; assert
264
+ // the site itself arrived. Every template gets the docs (create routes all
265
+ // three through a create*App that passes the docs URL), and the path differs:
266
+ // react keeps them under `public`, the others at the root.
271
267
  ];
272
- case 2:
268
+ case 3:
273
269
  _a.sent();
270
+ docsCandidates = [
271
+ path_1.default.join(process.projectDir, appName, 'public', 'docs', 'index.html'),
272
+ path_1.default.join(process.projectDir, appName, 'docs', 'index.html')
273
+ ];
274
+ return [4 /*yield*/, Promise.all(docsCandidates.map(function (candidate) { return (0, utils_1.fileExists)(candidate); }))];
275
+ case 4:
276
+ present = _a.sent();
277
+ expect(present.some(Boolean)).toEqual(true);
274
278
  // A seed app carries its dependencies in the template's package.json, so the
275
279
  // command create issues for it is a plain install - the same trade as above:
276
280
  // the stub writes what npm would, and this asserts the CLI asked for it.
@@ -68,10 +68,38 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
68
68
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
69
69
  }
70
70
  };
71
+ var __read = (this && this.__read) || function (o, n) {
72
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
73
+ if (!m) return o;
74
+ var i = m.call(o), r, ar = [], e;
75
+ try {
76
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
77
+ }
78
+ catch (error) { e = { error: error }; }
79
+ finally {
80
+ try {
81
+ if (r && !r.done && (m = i["return"])) m.call(i);
82
+ }
83
+ finally { if (e) throw e.error; }
84
+ }
85
+ return ar;
86
+ };
87
+ var __values = (this && this.__values) || function(o) {
88
+ var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
89
+ if (m) return m.call(o);
90
+ if (o && typeof o.length === "number") return {
91
+ next: function () {
92
+ if (o && i >= o.length) o = void 0;
93
+ return { value: o && o[i++], done: !o };
94
+ }
95
+ };
96
+ throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
97
+ };
71
98
  var __importDefault = (this && this.__importDefault) || function (mod) {
72
99
  return (mod && mod.__esModule) ? mod : { "default": mod };
73
100
  };
74
101
  Object.defineProperty(exports, "__esModule", { value: true });
102
+ var fs_1 = __importDefault(require("fs"));
75
103
  var path_1 = __importDefault(require("path"));
76
104
  var node_1 = require("@sasjs/adapter/node");
77
105
  var js_base64_1 = require("js-base64");
@@ -85,6 +113,13 @@ var compile_1 = require("../../compile/compile");
85
113
  var deploy_1 = require("../deploy");
86
114
  var utils_2 = require("./utils");
87
115
  var config_1 = require("../../../utils/config");
116
+ /** Every file under a folder, recursively, as absolute paths. */
117
+ var listFiles = function (folderPath) {
118
+ return fs_1.default.readdirSync(folderPath, { withFileTypes: true }).flatMap(function (entry) {
119
+ var entryPath = path_1.default.join(folderPath, entry.name);
120
+ return entry.isDirectory() ? listFiles(entryPath) : [entryPath];
121
+ });
122
+ };
88
123
  describe('sasjs cbd with server type SASJS', function () {
89
124
  var target;
90
125
  var appName;
@@ -115,9 +150,10 @@ describe('sasjs cbd with server type SASJS', function () {
115
150
  });
116
151
  }); });
117
152
  it("should deploy compile and build to sasjs/server", function () { return __awaiter(void 0, void 0, void 0, function () {
118
- var deployMock, _a, customTarget, macroCorePath, mf_getuser, _b, mp_jsonout, ms_webout, webout, mocksPath, appinitJSPath, getdataJSPath, springsJSONPath, appinitJSContents, getdataJSContents, springsJSONContents, depsInserts, appinit, getdata, getCode;
119
- return __generator(this, function (_c) {
120
- switch (_c.label) {
153
+ var deployMock, _a, customTarget, macroCorePath, mf_getuser, _b, mp_jsonout, ms_webout, webout, depsInserts, getCode, _c, payload, deployed, indexMembers, projectRoot, mocksRoot, mockFiles, mockFiles_1, mockFiles_1_1, filePath, relative, _d, _e, _f, e_1_1, serviceFiles, serviceFiles_1, serviceFiles_1_1, filePath, name_1, contents, member, e_2_1;
154
+ var e_1, _g, _h, e_2, _j;
155
+ return __generator(this, function (_k) {
156
+ switch (_k.label) {
121
157
  case 0:
122
158
  jest.spyOn(fileModule, 'fileExists').mockImplementation(function (filePath) {
123
159
  if (filePath.endsWith('.zip'))
@@ -141,7 +177,7 @@ describe('sasjs cbd with server type SASJS', function () {
141
177
  }
142
178
  })];
143
179
  case 1:
144
- _c.sent();
180
+ _k.sent();
145
181
  return [4 /*yield*/, (0, utils_2.updateLocalTarget)(target.name, {
146
182
  jobConfig: undefined,
147
183
  serviceConfig: undefined,
@@ -151,101 +187,139 @@ describe('sasjs cbd with server type SASJS', function () {
151
187
  }
152
188
  })];
153
189
  case 2:
154
- _c.sent();
190
+ _k.sent();
155
191
  _a = process;
156
192
  return [4 /*yield*/, (0, config_1.getLocalConfig)()];
157
193
  case 3:
158
- _a.sasjsConfig = _c.sent();
194
+ _a.sasjsConfig = _k.sent();
159
195
  return [4 /*yield*/, (0, config_1.findTargetInConfiguration)(target.name, types_1.TargetScope.Local)];
160
196
  case 4:
161
- customTarget = (_c.sent()).target;
197
+ customTarget = (_k.sent()).target;
162
198
  return [4 /*yield*/, (0, compile_1.compile)(customTarget)];
163
199
  case 5:
164
- _c.sent();
200
+ _k.sent();
165
201
  return [4 /*yield*/, expect((0, build_1.build)(customTarget)).toResolve()];
166
202
  case 6:
167
- _c.sent();
203
+ _k.sent();
168
204
  return [4 /*yield*/, (0, deploy_1.deploy)(customTarget, true)];
169
205
  case 7:
170
- _c.sent();
206
+ _k.sent();
171
207
  macroCorePath = process.sasjsConstants.macroCorePath;
172
208
  _b = utils_1.removeHeader;
173
209
  return [4 /*yield*/, (0, utils_1.readFile)("".concat(macroCorePath, "/base/mf_getuser.sas"))];
174
210
  case 8:
175
- mf_getuser = _b.apply(void 0, [_c.sent()]);
211
+ mf_getuser = _b.apply(void 0, [_k.sent()]);
176
212
  return [4 /*yield*/, (0, utils_1.readFile)("".concat(macroCorePath, "/base/mp_jsonout.sas"))];
177
213
  case 9:
178
- mp_jsonout = _c.sent();
214
+ mp_jsonout = _k.sent();
179
215
  return [4 /*yield*/, (0, utils_1.readFile)("".concat(macroCorePath, "/server/ms_webout.sas"))];
180
216
  case 10:
181
- ms_webout = _c.sent();
217
+ ms_webout = _k.sent();
182
218
  webout = "%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);\n %ms_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt\n ,missing=&missing\n ,showmeta=&showmeta\n ,maxobs=&maxobs\n )\n%mend;\n";
183
- mocksPath = path_1.default.join(__dirname, appName, 'sasjs', 'mocks');
184
- appinitJSPath = path_1.default.join(mocksPath, 'services', 'common', 'appinit.js');
185
- getdataJSPath = path_1.default.join(mocksPath, 'services', 'common', 'getdata.js');
186
- springsJSONPath = path_1.default.join(mocksPath, 'services', 'common', 'springs.json');
187
- return [4 /*yield*/, (0, utils_1.readFile)(appinitJSPath)];
188
- case 11:
189
- appinitJSContents = _c.sent();
190
- return [4 /*yield*/, (0, utils_1.readFile)(getdataJSPath)];
191
- case 12:
192
- getdataJSContents = _c.sent();
193
- return [4 /*yield*/, (0, utils_1.readFile)(springsJSONPath)];
194
- case 13:
195
- springsJSONContents = _c.sent();
196
219
  depsInserts = "\n\n* Service Variables start;\n\n\n* Service Variables end;\n* SAS Macros start;\n\n\n\n* SAS Macros end;\n* SAS Includes start;\n\n* SAS Includes end;\n* Binary Files start;\n\n* Binary Files end;\n\n\n* Service start;\n";
197
- appinit = "/**\n @file appinit.sas\n @brief Initialisation service - runs on app startup\n @details This is always the first service called when the app is opened.\n\n <h4> SAS Macros </h4>\n\n**/\n\nproc sql;\ncreate table areas as select distinct area\n from mydb.springs;\n\n%webout(OPEN)\n%webout(OBJ,areas)\n%webout(CLOSE)\n\n";
198
- getdata = "/**\n @file getdata.sas\n @brief Get Data service - runs on app startup\n @details This is always the first service called when the app is opened.\n\n <h4> SAS Macros </h4>\n\n**/\n\n%webout(FETCH)\n\nproc sql;\ncreate table springs as select * from mydb.springs\n where area in (select area from work.areas);\n\n%webout(OPEN)\n%webout(OBJ,springs)\n%webout(CLOSE)\n\n";
199
220
  getCode = function (file) {
200
221
  return "".concat(mf_getuser).concat(mp_jsonout).concat(ms_webout).concat(webout, "/* provide additional debug info */\n%global _program;\n%put &=syscc;\n%put user=%mf_getuser();\n%put pgm=&_program;\n%put timestamp=%sysfunc(datetime(),datetime19.);").concat(depsInserts).concat(file, "* Service end;");
201
222
  };
202
- expect(deployMock).toHaveBeenCalledWith({
203
- appLoc: target.appLoc,
204
- fileTree: {
205
- members: [
206
- {
207
- name: 'services',
208
- type: 'folder',
209
- members: [
210
- {
211
- name: 'common',
212
- type: 'folder',
213
- members: [
214
- {
215
- name: 'appinit.js',
216
- type: 'file',
217
- encoding: 'base64',
218
- code: (0, js_base64_1.encode)(appinitJSContents)
219
- },
220
- {
221
- name: 'appinit',
222
- type: 'service',
223
- code: getCode((0, utils_1.removeHeader)(appinit))
224
- },
225
- {
226
- name: 'getdata.js',
227
- type: 'file',
228
- encoding: 'base64',
229
- code: (0, js_base64_1.encode)(getdataJSContents)
230
- },
231
- {
232
- name: 'getdata',
233
- type: 'service',
234
- code: getCode((0, utils_1.removeHeader)(getdata))
235
- },
236
- {
237
- name: 'springs.json',
238
- type: 'file',
239
- encoding: 'base64',
240
- code: (0, js_base64_1.encode)(springsJSONContents)
241
- }
242
- ]
243
- }
244
- ]
245
- }
246
- ]
247
- }
248
- }, target.appLoc, undefined);
223
+ // The payload is derived from the project the command created rather than
224
+ // listed by hand. A hand-kept list turned this spec red when the seed app
225
+ // gained a data file beside its services - a file the CLI deploys correctly
226
+ // - so the reference here is the project itself. What stays explicit is the
227
+ // CLI's own contribution: the wrapper, built by `getCode` above.
228
+ expect(deployMock).toHaveBeenCalledTimes(1);
229
+ _c = __read(deployMock.mock.calls[0], 1), payload = _c[0];
230
+ expect(payload.appLoc).toEqual(target.appLoc);
231
+ deployed = new Map();
232
+ indexMembers = function (members, prefix) {
233
+ if (members === void 0) { members = []; }
234
+ if (prefix === void 0) { prefix = ''; }
235
+ return members.forEach(function (member) {
236
+ var memberPath = prefix ? "".concat(prefix, "/").concat(member.name) : member.name;
237
+ if (member.type === 'folder')
238
+ indexMembers(member.members, memberPath);
239
+ else
240
+ deployed.set(memberPath, member);
241
+ });
242
+ };
243
+ indexMembers(payload.fileTree.members);
244
+ projectRoot = path_1.default.join(__dirname, appName);
245
+ mocksRoot = path_1.default.join(projectRoot, 'sasjs', 'mocks');
246
+ mockFiles = listFiles(mocksRoot);
247
+ _k.label = 11;
248
+ case 11:
249
+ _k.trys.push([11, 16, 17, 18]);
250
+ mockFiles_1 = __values(mockFiles), mockFiles_1_1 = mockFiles_1.next();
251
+ _k.label = 12;
252
+ case 12:
253
+ if (!!mockFiles_1_1.done) return [3 /*break*/, 15];
254
+ filePath = mockFiles_1_1.value;
255
+ relative = path_1.default
256
+ .relative(mocksRoot, filePath)
257
+ .split(path_1.default.sep)
258
+ .join('/');
259
+ _e = (_d = expect(deployed.get(relative))).toEqual;
260
+ _h = {
261
+ name: path_1.default.basename(filePath),
262
+ type: 'file',
263
+ encoding: 'base64'
264
+ };
265
+ _f = js_base64_1.encode;
266
+ return [4 /*yield*/, (0, utils_1.readFile)(filePath)];
267
+ case 13:
268
+ _e.apply(_d, [(_h.code = _f.apply(void 0, [_k.sent()]),
269
+ _h)]);
270
+ _k.label = 14;
271
+ case 14:
272
+ mockFiles_1_1 = mockFiles_1.next();
273
+ return [3 /*break*/, 12];
274
+ case 15: return [3 /*break*/, 18];
275
+ case 16:
276
+ e_1_1 = _k.sent();
277
+ e_1 = { error: e_1_1 };
278
+ return [3 /*break*/, 18];
279
+ case 17:
280
+ try {
281
+ if (mockFiles_1_1 && !mockFiles_1_1.done && (_g = mockFiles_1.return)) _g.call(mockFiles_1);
282
+ }
283
+ finally { if (e_1) throw e_1.error; }
284
+ return [7 /*endfinally*/];
285
+ case 18:
286
+ serviceFiles = listFiles(path_1.default.join(projectRoot, 'sasjs', 'services', 'common')).filter(function (filePath) { return filePath.endsWith('.sas'); });
287
+ _k.label = 19;
288
+ case 19:
289
+ _k.trys.push([19, 24, 25, 26]);
290
+ serviceFiles_1 = __values(serviceFiles), serviceFiles_1_1 = serviceFiles_1.next();
291
+ _k.label = 20;
292
+ case 20:
293
+ if (!!serviceFiles_1_1.done) return [3 /*break*/, 23];
294
+ filePath = serviceFiles_1_1.value;
295
+ name_1 = path_1.default.basename(filePath, '.sas');
296
+ return [4 /*yield*/, (0, utils_1.readFile)(filePath)];
297
+ case 21:
298
+ contents = _k.sent();
299
+ member = deployed.get("services/common/".concat(name_1));
300
+ expect(member).toMatchObject({ name: name_1, type: 'service' });
301
+ // the compiled service ends its text with a blank line before the end
302
+ // marker, whatever trailing whitespace the source file carries
303
+ expect(member.code).toEqual(getCode((0, utils_1.removeHeader)(contents).trimEnd() + '\n\n'));
304
+ _k.label = 22;
305
+ case 22:
306
+ serviceFiles_1_1 = serviceFiles_1.next();
307
+ return [3 /*break*/, 20];
308
+ case 23: return [3 /*break*/, 26];
309
+ case 24:
310
+ e_2_1 = _k.sent();
311
+ e_2 = { error: e_2_1 };
312
+ return [3 /*break*/, 26];
313
+ case 25:
314
+ try {
315
+ if (serviceFiles_1_1 && !serviceFiles_1_1.done && (_j = serviceFiles_1.return)) _j.call(serviceFiles_1);
316
+ }
317
+ finally { if (e_2) throw e_2.error; }
318
+ return [7 /*endfinally*/];
319
+ case 26:
320
+ // and nothing else: a member the CLI gains or loses shows as a difference
321
+ // in the count, rather than silently passing inside a hand-written tree
322
+ expect(deployed.size).toEqual(mockFiles.length + serviceFiles.length);
249
323
  return [2 /*return*/];
250
324
  }
251
325
  });
@@ -50,6 +50,18 @@ export declare const generateTestTarget: (targetName: string, appLoc: string, se
50
50
  export declare const createTestGlobalTarget: (targetName: string, appLoc: string, serviceConfig?: ServiceConfig, serverType?: ServerType) => Promise<Target>;
51
51
  export declare const verifyStep: (step?: "db" | "compile" | "build", buildFileName?: string, customFiles?: "custom" | "no") => Promise<void>;
52
52
  export declare const mockProcessExit: () => jest.SpyInstance<never, [code?: string | number | null | undefined], any>;
53
+ /**
54
+ * The file tree of a seed app, read from the archive `sasjs create` downloads.
55
+ *
56
+ * A created project used to be compared against a hand-kept list of the files
57
+ * each template ships, so every seed release that added or removed one turned
58
+ * this suite red with no change to the CLI - twice in one afternoon, for
59
+ * `springs.json` and for the angular template's component specs. The template
60
+ * is what the command copies, so it is what the test compares against: the same
61
+ * repository, fetched the same way, with the copy's own normalisations applied
62
+ * where the command makes them.
63
+ */
64
+ export declare const getTemplateFileTree: (repoUrl: string) => Promise<Folder>;
53
65
  export declare const verifyFolder: (folder: Folder, parentFolderName?: string) => Promise<boolean>;
54
66
  export declare const verifyPackageJsonContent: (parentFolderName?: string) => Promise<void>;
55
67
  export declare const removeAllTargetsFromConfigs: () => Promise<void>;
@@ -108,8 +108,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
108
108
  return (mod && mod.__esModule) ? mod : { "default": mod };
109
109
  };
110
110
  Object.defineProperty(exports, "__esModule", { value: true });
111
- exports.verifyGitNotPresent = exports.verifyCompiledJob = exports.verifyCompiledService = exports.verifyDotFilesNotGenerated = exports.verifyDotFiles = exports.verifyDocs = exports.updateConfig = exports.updateTarget = exports.removeAllTargetsFromConfigs = exports.verifyPackageJsonContent = exports.verifyFolder = exports.mockProcessExit = exports.verifyStep = exports.createTestGlobalTarget = exports.generateTestTarget = exports.removeTestServerFolder = exports.removeTestApp = exports.createTestMinimalApp = exports.createTestJobsApp = exports.createTestApp = exports.stubNpm = exports.npmCommands = exports.generateUniqueTimestamp = void 0;
111
+ exports.verifyGitNotPresent = exports.verifyCompiledJob = exports.verifyCompiledService = exports.verifyDotFilesNotGenerated = exports.verifyDotFiles = exports.verifyDocs = exports.updateConfig = exports.updateTarget = exports.removeAllTargetsFromConfigs = exports.verifyPackageJsonContent = exports.verifyFolder = exports.getTemplateFileTree = exports.mockProcessExit = exports.verifyStep = exports.createTestGlobalTarget = exports.generateTestTarget = exports.removeTestServerFolder = exports.removeTestApp = exports.createTestMinimalApp = exports.createTestJobsApp = exports.createTestApp = exports.stubNpm = exports.npmCommands = exports.generateUniqueTimestamp = void 0;
112
112
  var fs_1 = __importDefault(require("fs"));
113
+ var os_1 = __importDefault(require("os"));
113
114
  var path_1 = __importDefault(require("path"));
114
115
  var dotenv_1 = __importDefault(require("dotenv"));
115
116
  var utils_1 = require("@sasjs/utils");
@@ -122,6 +123,8 @@ var compiledFilesCustom1_1 = require("./fileStructures/compiledFilesCustom1");
122
123
  var builtFiles_1 = require("./fileStructures/builtFiles");
123
124
  var builtFilesCustom1_1 = require("./fileStructures/builtFilesCustom1");
124
125
  var create_1 = require("../commands/create/create");
126
+ var utils_2 = require("./utils");
127
+ var zip_1 = require("./zip");
125
128
  var setConstants_1 = require("./setConstants");
126
129
  var shell = __importStar(require("./shell"));
127
130
  /**
@@ -524,6 +527,68 @@ var verifyBuildJson = function (folders, buildJson, buildSas) {
524
527
  });
525
528
  return true;
526
529
  };
530
+ /**
531
+ * The file tree of a seed app, read from the archive `sasjs create` downloads.
532
+ *
533
+ * A created project used to be compared against a hand-kept list of the files
534
+ * each template ships, so every seed release that added or removed one turned
535
+ * this suite red with no change to the CLI - twice in one afternoon, for
536
+ * `springs.json` and for the angular template's component specs. The template
537
+ * is what the command copies, so it is what the test compares against: the same
538
+ * repository, fetched the same way, with the copy's own normalisations applied
539
+ * where the command makes them.
540
+ */
541
+ var getTemplateFileTree = function (repoUrl) { return __awaiter(void 0, void 0, void 0, function () {
542
+ var workspace, archive, main_1, root;
543
+ return __generator(this, function (_a) {
544
+ switch (_a.label) {
545
+ case 0:
546
+ if (!repoUrl) {
547
+ throw new Error("No seed app URL for that template - add it to seedAppUrls in src/utils/utils.ts");
548
+ }
549
+ workspace = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'sasjs-template-'));
550
+ _a.label = 1;
551
+ case 1:
552
+ _a.trys.push([1, , 3, 4]);
553
+ archive = path_1.default.join(workspace, 'main.zip');
554
+ main_1 = (0, utils_2.downloadFile)("".concat(repoUrl, "/archive/refs/heads/main.zip"), archive);
555
+ // the seed repos are on `main`, but the command falls back to `master` and
556
+ // the reference has to be taken from whatever it would have downloaded
557
+ if (main_1.code || main_1.stderr.includes('404')) {
558
+ (0, utils_2.downloadFile)("".concat(repoUrl, "/archive/refs/heads/master.zip"), archive);
559
+ }
560
+ return [4 /*yield*/, (0, zip_1.extractZip)(archive, workspace, true)];
561
+ case 2:
562
+ _a.sent();
563
+ root = fs_1.default
564
+ .readdirSync(workspace, { withFileTypes: true })
565
+ .find(function (entry) { return entry.isDirectory(); });
566
+ if (!root) {
567
+ throw new Error("Could not find the unzipped folder for ".concat(repoUrl));
568
+ }
569
+ return [2 /*return*/, toFileTree(path_1.default.join(workspace, root.name), root.name)];
570
+ case 3:
571
+ fs_1.default.rmSync(workspace, { recursive: true, force: true });
572
+ return [7 /*endfinally*/];
573
+ case 4: return [2 /*return*/];
574
+ }
575
+ });
576
+ }); };
577
+ exports.getTemplateFileTree = getTemplateFileTree;
578
+ var toFileTree = function (folderPath, folderName) {
579
+ var entries = fs_1.default.readdirSync(folderPath, { withFileTypes: true });
580
+ return {
581
+ folderName: folderName,
582
+ // `create` removes this once the docs have been fetched into the folder, so
583
+ // it is the one file the project is not expected to carry
584
+ files: entries
585
+ .filter(function (entry) { return entry.isFile() && entry.name !== '.gitmodules'; })
586
+ .map(function (entry) { return ({ fileName: entry.name }); }),
587
+ subFolders: entries
588
+ .filter(function (entry) { return entry.isDirectory(); })
589
+ .map(function (entry) { return toFileTree(path_1.default.join(folderPath, entry.name), entry.name); })
590
+ };
591
+ };
527
592
  var verifyFolder = function (folder_1) {
528
593
  var args_1 = [];
529
594
  for (var _i = 1; _i < arguments.length; _i++) {
@@ -4,6 +4,14 @@ import SASjs from '@sasjs/adapter/node';
4
4
  import { LogJson } from '../types';
5
5
  export declare function inExistingProject(folderPath: string): Promise<boolean>;
6
6
  export declare function diff(a: any[], b: any[]): any[];
7
+ /**
8
+ * The seed apps `sasjs create` downloads, by template name.
9
+ *
10
+ * One map for the commands that fetch them and the specs that compare their
11
+ * output, so a template added here is covered by both rather than downloaded
12
+ * by one and unknown to the other.
13
+ */
14
+ export declare const seedAppUrls: Record<string, string>;
7
15
  export declare function createReactApp(folderPath: string): Promise<void>;
8
16
  export declare function createAngularApp(folderPath: string): Promise<void>;
9
17
  export declare function createMinimalApp(folderPath: string): Promise<void>;
@@ -90,7 +90,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
90
90
  return (mod && mod.__esModule) ? mod : { "default": mod };
91
91
  };
92
92
  Object.defineProperty(exports, "__esModule", { value: true });
93
- exports.isSasJsServerInServerMode = exports.getNodeModulePath = exports.isSASjsProject = exports.terminateProcess = exports.loadEnvVariables = exports.millisecondsToDdHhMmSs = exports.arrToObj = void 0;
93
+ exports.isSasJsServerInServerMode = exports.getNodeModulePath = exports.isSASjsProject = exports.terminateProcess = exports.loadEnvVariables = exports.millisecondsToDdHhMmSs = exports.arrToObj = exports.seedAppUrls = void 0;
94
94
  exports.inExistingProject = inExistingProject;
95
95
  exports.diff = diff;
96
96
  exports.createReactApp = createReactApp;
@@ -147,6 +147,18 @@ function diff(a, b) {
147
147
  });
148
148
  return __spreadArray(__spreadArray([], __read(notInA), false), __read(notInB), false);
149
149
  }
150
+ /**
151
+ * The seed apps `sasjs create` downloads, by template name.
152
+ *
153
+ * One map for the commands that fetch them and the specs that compare their
154
+ * output, so a template added here is covered by both rather than downloaded
155
+ * by one and unknown to the other.
156
+ */
157
+ exports.seedAppUrls = {
158
+ angular: 'https://github.com/sasjs/angular-seed-app',
159
+ minimal: 'https://github.com/sasjs/minimal-seed-app',
160
+ react: 'https://github.com/sasjs/react-seed-app'
161
+ };
150
162
  function createReactApp(folderPath) {
151
163
  return __awaiter(this, void 0, void 0, function () {
152
164
  var _this = this;
@@ -154,7 +166,7 @@ function createReactApp(folderPath) {
154
166
  return [2 /*return*/, new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
155
167
  return __generator(this, function (_a) {
156
168
  switch (_a.label) {
157
- case 0: return [4 /*yield*/, createApp(folderPath, 'https://github.com/sasjs/react-seed-app', 'https://github.com/sasjs/docs', function (err) {
169
+ case 0: return [4 /*yield*/, createApp(folderPath, exports.seedAppUrls.react, 'https://github.com/sasjs/docs', function (err) {
158
170
  return reject(new Error(err));
159
171
  })];
160
172
  case 1:
@@ -173,7 +185,7 @@ function createAngularApp(folderPath) {
173
185
  return [2 /*return*/, new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
174
186
  return __generator(this, function (_a) {
175
187
  switch (_a.label) {
176
- case 0: return [4 /*yield*/, createApp(folderPath, 'https://github.com/sasjs/angular-seed-app', 'https://github.com/sasjs/docs', function (err) {
188
+ case 0: return [4 /*yield*/, createApp(folderPath, exports.seedAppUrls.angular, 'https://github.com/sasjs/docs', function (err) {
177
189
  return reject(new Error(err));
178
190
  })];
179
191
  case 1:
@@ -192,7 +204,7 @@ function createMinimalApp(folderPath) {
192
204
  return [2 /*return*/, new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
193
205
  return __generator(this, function (_a) {
194
206
  switch (_a.label) {
195
- case 0: return [4 /*yield*/, createApp(folderPath, 'https://github.com/sasjs/minimal-seed-app', 'https://github.com/sasjs/docs', function (err) {
207
+ case 0: return [4 /*yield*/, createApp(folderPath, exports.seedAppUrls.minimal, 'https://github.com/sasjs/docs', function (err) {
196
208
  return reject(new Error(err));
197
209
  })];
198
210
  case 1:
package/package.json CHANGED
@@ -71,7 +71,7 @@
71
71
  },
72
72
  "dependencies": {
73
73
  "@sasjs/adapter": "6.0.2",
74
- "@sasjs/api": "0.28.0",
74
+ "@sasjs/api": "0.38.0",
75
75
  "@sasjs/core": "7.1.12",
76
76
  "@sasjs/lint": "5.3.0",
77
77
  "@sasjs/utils": "4.11.0",
@@ -125,5 +125,5 @@
125
125
  "ws": "8.21.0",
126
126
  "nanoid": "3.3.18"
127
127
  },
128
- "version": "6.29.0"
128
+ "version": "6.30.0"
129
129
  }
@@ -1,2 +0,0 @@
1
- import { Folder } from '../../../types';
2
- export declare const angularAppFiles: Folder;
@@ -1,117 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.angularAppFiles = void 0;
4
- exports.angularAppFiles = {
5
- folderName: '',
6
- files: [
7
- { fileName: 'package.json' },
8
- { fileName: 'package-lock.json' },
9
- { fileName: 'tsconfig.json' },
10
- { fileName: 'angular.json' },
11
- { fileName: 'tsconfig.app.json' },
12
- { fileName: 'README.md' }
13
- ],
14
- subFolders: [
15
- {
16
- folderName: 'sasjs',
17
- files: [{ fileName: 'sasjsconfig.json' }],
18
- subFolders: [
19
- {
20
- folderName: 'macros',
21
- files: [],
22
- subFolders: []
23
- },
24
- {
25
- folderName: 'services',
26
- files: [],
27
- subFolders: [
28
- {
29
- folderName: 'common',
30
- subFolders: [],
31
- files: [{ fileName: 'appinit.sas' }, { fileName: 'getdata.sas' }]
32
- }
33
- ]
34
- }
35
- ]
36
- },
37
- {
38
- folderName: 'src',
39
- files: [
40
- { fileName: 'favicon.ico' },
41
- { fileName: 'index.html' },
42
- { fileName: 'main.ts' },
43
- { fileName: 'polyfills.ts' },
44
- { fileName: 'styles.scss' }
45
- ],
46
- subFolders: [
47
- {
48
- folderName: 'app',
49
- files: [
50
- { fileName: 'app.component.html' },
51
- { fileName: 'app.component.scss' },
52
- { fileName: 'app.component.ts' },
53
- { fileName: 'app.module.ts' },
54
- { fileName: 'app-routing.module.ts' },
55
- { fileName: 'sas.service.spec.ts' },
56
- { fileName: 'sas.service.ts' },
57
- { fileName: 'state.service.spec.ts' },
58
- { fileName: 'state.service.ts' }
59
- ],
60
- subFolders: [
61
- {
62
- folderName: 'sasjs-ui',
63
- files: [
64
- { fileName: 'sasjs-ui.component.html' },
65
- { fileName: 'sasjs-ui.component.scss' },
66
- { fileName: 'sasjs-ui.component.ts' }
67
- ],
68
- subFolders: []
69
- },
70
- {
71
- folderName: 'data',
72
- files: [
73
- { fileName: 'data.component.html' },
74
- { fileName: 'data.component.scss' },
75
- { fileName: 'data.component.ts' }
76
- ],
77
- subFolders: []
78
- },
79
- {
80
- folderName: 'home-page',
81
- files: [
82
- { fileName: 'home-page.component.html' },
83
- { fileName: 'home-page.component.scss' },
84
- { fileName: 'home-page.component.ts' }
85
- ],
86
- subFolders: []
87
- }
88
- ]
89
- },
90
- {
91
- folderName: 'assets',
92
- files: [
93
- { fileName: 'angular-logo.png' },
94
- { fileName: 'logo-white.png' }
95
- ],
96
- subFolders: []
97
- },
98
- {
99
- folderName: 'environments',
100
- files: [
101
- { fileName: 'environment.prod.ts' },
102
- { fileName: 'environment.ts' }
103
- ],
104
- subFolders: []
105
- }
106
- ]
107
- },
108
- {
109
- // The docs folder is a submodule, and its pages are the submodule's own
110
- // (a docsify site today, an index.html before that). The folder is what
111
- // this asserts; naming a page here breaks on the submodule's next change.
112
- folderName: 'docs',
113
- files: [],
114
- subFolders: []
115
- }
116
- ]
117
- };
@@ -1,2 +0,0 @@
1
- import { Folder } from '../../../types';
2
- export declare const minimalAppFiles: Folder;
@@ -1,44 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.minimalAppFiles = void 0;
4
- exports.minimalAppFiles = {
5
- folderName: '',
6
- files: [
7
- { fileName: 'package.json' },
8
- { fileName: 'package-lock.json' },
9
- { fileName: 'README.md' }
10
- ],
11
- subFolders: [
12
- {
13
- folderName: 'sasjs',
14
- files: [{ fileName: 'sasjsconfig.json' }],
15
- subFolders: [
16
- {
17
- folderName: 'macros',
18
- files: [],
19
- subFolders: []
20
- },
21
- {
22
- folderName: 'services',
23
- files: [],
24
- subFolders: [
25
- {
26
- folderName: 'common',
27
- files: [{ fileName: 'appinit.sas' }, { fileName: 'getdata.sas' }],
28
- subFolders: []
29
- }
30
- ]
31
- }
32
- ]
33
- },
34
- {
35
- folderName: 'src',
36
- files: [
37
- { fileName: 'index.html' },
38
- { fileName: 'scripts.js' },
39
- { fileName: 'style.css' }
40
- ],
41
- subFolders: []
42
- }
43
- ]
44
- };
@@ -1,2 +0,0 @@
1
- import { Folder } from '../../../types';
2
- export declare const reactAppFiles: Folder;
@@ -1,93 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.reactAppFiles = void 0;
4
- exports.reactAppFiles = {
5
- folderName: '',
6
- files: [
7
- { fileName: 'package.json' },
8
- { fileName: 'package-lock.json' },
9
- { fileName: 'tsconfig.json' },
10
- { fileName: 'README.md' },
11
- { fileName: 'webpack.common.js' },
12
- { fileName: 'webpack.dev.js' },
13
- { fileName: 'webpack.prod.js' }
14
- ],
15
- subFolders: [
16
- {
17
- folderName: 'sasjs',
18
- files: [{ fileName: 'sasjsconfig.json' }],
19
- subFolders: [
20
- {
21
- folderName: 'macros',
22
- files: [],
23
- subFolders: []
24
- },
25
- {
26
- folderName: 'services',
27
- files: [],
28
- subFolders: [
29
- {
30
- folderName: 'common',
31
- files: [{ fileName: 'appinit.sas' }, { fileName: 'getdata.sas' }],
32
- subFolders: []
33
- }
34
- ]
35
- }
36
- ]
37
- },
38
- {
39
- folderName: 'src',
40
- files: [
41
- { fileName: 'App.scss' },
42
- { fileName: 'index.scss' },
43
- { fileName: 'index.tsx' },
44
- { fileName: 'logo.svg' }
45
- ],
46
- subFolders: [
47
- {
48
- folderName: 'components',
49
- files: [
50
- { fileName: 'data-page.component.tsx' },
51
- { fileName: 'home-page.component.tsx' },
52
- { fileName: 'login.component.tsx' },
53
- { fileName: 'request-modal.component.tsx' },
54
- { fileName: 'syntax-highlighting.css' },
55
- { fileName: 'user-name.component.tsx' }
56
- ],
57
- subFolders: []
58
- },
59
- {
60
- folderName: 'context',
61
- files: [{ fileName: 'sasContext.tsx' }],
62
- subFolders: []
63
- },
64
- {
65
- folderName: 'layouts',
66
- files: [{ fileName: 'Main.jsx' }],
67
- subFolders: []
68
- },
69
- {
70
- folderName: 'routes',
71
- files: [{ fileName: 'index.js' }],
72
- subFolders: []
73
- },
74
- {
75
- folderName: 'theme',
76
- files: [{ fileName: 'index.ts' }],
77
- subFolders: []
78
- }
79
- ]
80
- },
81
- {
82
- folderName: 'public',
83
- files: [],
84
- subFolders: [
85
- {
86
- folderName: 'docs',
87
- files: [{ fileName: 'index.html' }],
88
- subFolders: []
89
- }
90
- ]
91
- }
92
- ]
93
- };