@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.
- package/build/commands/build/internal/mockSite/assets/sw.js +57 -4
- package/build/commands/build/internal/mockSite/mockSite.js +7 -6
- package/build/commands/build/internal/mockSite/spec/mockSite.spec.js +1 -0
- package/build/commands/build/internal/mockSite/spec/sw.spec.js +279 -0
- package/build/commands/create/spec/create.spec.js +22 -18
- package/build/commands/deploy/spec/cbd.spec.js +149 -75
- package/build/utils/test.d.ts +12 -0
- package/build/utils/test.js +66 -1
- package/build/utils/utils.d.ts +8 -0
- package/build/utils/utils.js +16 -4
- package/package.json +2 -2
- package/build/commands/create/spec/angularAppFiles.d.ts +0 -2
- package/build/commands/create/spec/angularAppFiles.js +0 -117
- package/build/commands/create/spec/minimalAppFiles.d.ts +0 -2
- package/build/commands/create/spec/minimalAppFiles.js +0 -44
- package/build/commands/create/spec/reactAppFiles.d.ts +0 -2
- package/build/commands/create/spec/reactAppFiles.js +0 -93
|
@@ -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
|
|
974
|
-
//
|
|
975
|
-
//
|
|
976
|
-
|
|
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
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
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
|
|
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
|
-
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
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
|
|
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,
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
190
|
+
_k.sent();
|
|
155
191
|
_a = process;
|
|
156
192
|
return [4 /*yield*/, (0, config_1.getLocalConfig)()];
|
|
157
193
|
case 3:
|
|
158
|
-
_a.sasjsConfig =
|
|
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 = (
|
|
197
|
+
customTarget = (_k.sent()).target;
|
|
162
198
|
return [4 /*yield*/, (0, compile_1.compile)(customTarget)];
|
|
163
199
|
case 5:
|
|
164
|
-
|
|
200
|
+
_k.sent();
|
|
165
201
|
return [4 /*yield*/, expect((0, build_1.build)(customTarget)).toResolve()];
|
|
166
202
|
case 6:
|
|
167
|
-
|
|
203
|
+
_k.sent();
|
|
168
204
|
return [4 /*yield*/, (0, deploy_1.deploy)(customTarget, true)];
|
|
169
205
|
case 7:
|
|
170
|
-
|
|
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, [
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
});
|
package/build/utils/test.d.ts
CHANGED
|
@@ -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>;
|
package/build/utils/test.js
CHANGED
|
@@ -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++) {
|
package/build/utils/utils.d.ts
CHANGED
|
@@ -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>;
|
package/build/utils/utils.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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,
|
|
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.
|
|
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.
|
|
128
|
+
"version": "6.30.0"
|
|
129
129
|
}
|
|
@@ -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,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,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
|
-
};
|