@lightdash/cli 1.44.0 → 1.46.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/dist/.tsbuildinfo +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +39 -4
- package/dist/config.test.d.ts +2 -0
- package/dist/config.test.d.ts.map +1 -0
- package/dist/config.test.js +40 -0
- package/dist/handlers/apps/appCodeFiles.d.ts +1 -0
- package/dist/handlers/apps/appCodeFiles.d.ts.map +1 -1
- package/dist/handlers/apps/appCodeFiles.js +7 -4
- package/dist/handlers/apps/appCodeFiles.test.js +8 -0
- package/dist/handlers/apps/preview.d.ts +56 -0
- package/dist/handlers/apps/preview.d.ts.map +1 -0
- package/dist/handlers/apps/preview.js +262 -0
- package/dist/handlers/apps/preview.test.d.ts +2 -0
- package/dist/handlers/apps/preview.test.d.ts.map +1 -0
- package/dist/handlers/apps/preview.test.js +189 -0
- package/dist/handlers/apps/previewProxy.d.ts +20 -0
- package/dist/handlers/apps/previewProxy.d.ts.map +1 -0
- package/dist/handlers/apps/previewProxy.js +144 -0
- package/dist/handlers/apps/previewProxy.test.d.ts +2 -0
- package/dist/handlers/apps/previewProxy.test.d.ts.map +1 -0
- package/dist/handlers/apps/previewProxy.test.js +163 -0
- package/dist/handlers/apps/scaffolding.test.js +7 -0
- package/dist/handlers/utils.d.ts +8 -0
- package/dist/handlers/utils.d.ts.map +1 -1
- package/dist/handlers/utils.js +1 -0
- package/dist/index.js +16 -4
- package/dist/vendor/authoring/AGENTS.md.tmpl +3 -3
- package/dist/vendor/authoring/README.md.tmpl +7 -4
- package/dist/vendor/authoring/developing-data-apps-locally/SKILL.md +19 -6
- package/dist/vendor/authoring/gitignore +1 -0
- package/dist/vendor/template/vite.config.js +63 -3
- package/package.json +3 -3
package/dist/config.d.ts
CHANGED
|
@@ -23,6 +23,10 @@ export type Config = {
|
|
|
23
23
|
metadataFileGitignoreNoticeShown?: boolean;
|
|
24
24
|
};
|
|
25
25
|
};
|
|
26
|
+
/** @internal Exported for filesystem-permission regression tests. */
|
|
27
|
+
export declare const ensureConfigFilePermissions: (filePath: string) => Promise<void>;
|
|
28
|
+
/** @internal Exported for filesystem-permission regression tests. */
|
|
29
|
+
export declare const writeConfigToFile: (filePath: string, config: Config) => Promise<void>;
|
|
26
30
|
export declare const getConfig: () => Promise<Config>;
|
|
27
31
|
export declare const setProject: (projectUuid: string, projectName: string) => Promise<void>;
|
|
28
32
|
export declare const unsetProject: () => Promise<void>;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,cAAc,QAAsC,CAAC;AAElE,MAAM,MAAM,MAAM,GAAG;IACjB,IAAI,CAAC,EAAE;QACH,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC7B,CAAC;IACF,OAAO,CAAC,EAAE;QACN,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB;;;WAGG;QACH,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,OAAO,CAAC,EAAE;QACN,qCAAqC,CAAC,EAAE,OAAO,CAAC;QAChD,gCAAgC,CAAC,EAAE,OAAO,CAAC;KAC9C,CAAC;CACL,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,cAAc,QAAsC,CAAC;AAElE,MAAM,MAAM,MAAM,GAAG;IACjB,IAAI,CAAC,EAAE;QACH,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC7B,CAAC;IACF,OAAO,CAAC,EAAE;QACN,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB;;;WAGG;QACH,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,OAAO,CAAC,EAAE;QACN,qCAAqC,CAAC,EAAE,OAAO,CAAC;QAChD,gCAAgC,CAAC,EAAE,OAAO,CAAC;KAC9C,CAAC;CACL,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,2BAA2B,aAC1B,MAAM,KACjB,OAAO,CAAC,IAAI,CAgBd,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,iBAAiB,aAChB,MAAM,UACR,MAAM,KACf,OAAO,CAAC,IAAI,CAed,CAAC;AAiCF,eAAO,MAAM,SAAS,QAAa,OAAO,CAAC,MAAM,CAmBhD,CAAC;AAEF,eAAO,MAAM,UAAU,gBAAuB,MAAM,eAAe,MAAM,kBAUxE,CAAC;AAEF,eAAO,MAAM,YAAY,qBAUxB,CAAC;AAEF,eAAO,MAAM,iBAAiB,gBAAuB,MAAM,QAAQ,MAAM,kBAUxE,CAAC;AAEF,eAAO,MAAM,mBAAmB,qBAU/B,CAAC;AAEF,eAAO,MAAM,cAAc,aACb,MAAM,oBACE,MAAM,kBAW3B,CAAC;AAEF,eAAO,MAAM,UAAU,YAAmB,MAAM,CAAC,SAAS,CAAC,kBAM1D,CAAC;AAEF,eAAO,MAAM,SAAS,WAAkB,MAAM,CAAC,SAAS,CAAC,kBAMxD,CAAC"}
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.setAnswer = exports.setContext = exports.setDefaultUser = exports.unsetPreviewProject = exports.setPreviewProject = exports.unsetProject = exports.setProject = exports.getConfig = exports.configFilePath = void 0;
|
|
3
|
+
exports.setAnswer = exports.setContext = exports.setDefaultUser = exports.unsetPreviewProject = exports.setPreviewProject = exports.unsetProject = exports.setProject = exports.getConfig = exports.writeConfigToFile = exports.ensureConfigFilePermissions = exports.configFilePath = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const fs_1 = require("fs");
|
|
6
6
|
const yaml = tslib_1.__importStar(require("js-yaml"));
|
|
@@ -10,13 +10,48 @@ const path = tslib_1.__importStar(require("path"));
|
|
|
10
10
|
const uuid_1 = require("uuid");
|
|
11
11
|
const configDir = path.join(os.homedir(), '.config', 'lightdash');
|
|
12
12
|
exports.configFilePath = path.join(configDir, 'config.yaml');
|
|
13
|
+
/** @internal Exported for filesystem-permission regression tests. */
|
|
14
|
+
const ensureConfigFilePermissions = async (filePath) => {
|
|
15
|
+
if (process.platform === 'win32')
|
|
16
|
+
return;
|
|
17
|
+
await fs_1.promises.chmod(path.dirname(filePath), 0o700);
|
|
18
|
+
try {
|
|
19
|
+
await fs_1.promises.chmod(filePath, 0o600);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (!(error instanceof Error &&
|
|
23
|
+
'code' in error &&
|
|
24
|
+
error.code === 'ENOENT')) {
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
exports.ensureConfigFilePermissions = ensureConfigFilePermissions;
|
|
30
|
+
/** @internal Exported for filesystem-permission regression tests. */
|
|
31
|
+
const writeConfigToFile = async (filePath, config) => {
|
|
32
|
+
const directory = path.dirname(filePath);
|
|
33
|
+
await fs_1.promises.mkdir(directory, { recursive: true, mode: 0o700 });
|
|
34
|
+
// Tighten an existing config before overwriting it. `mode` on writeFile
|
|
35
|
+
// only applies when it creates a new file.
|
|
36
|
+
await (0, exports.ensureConfigFilePermissions)(filePath);
|
|
37
|
+
await fs_1.promises.writeFile(filePath, yaml.dump(config), {
|
|
38
|
+
encoding: 'utf8',
|
|
39
|
+
mode: 0o600,
|
|
40
|
+
});
|
|
41
|
+
// `mode` only applies when mkdir/writeFile creates a path. Tighten
|
|
42
|
+
// existing CLI config paths as well; Windows does not implement POSIX
|
|
43
|
+
// permission bits, so leave ACL management to the OS there.
|
|
44
|
+
await (0, exports.ensureConfigFilePermissions)(filePath);
|
|
45
|
+
};
|
|
46
|
+
exports.writeConfigToFile = writeConfigToFile;
|
|
13
47
|
const setConfig = async (config) => {
|
|
14
|
-
await
|
|
15
|
-
await fs_1.promises.writeFile(exports.configFilePath, yaml.dump(config), 'utf8');
|
|
48
|
+
await (0, exports.writeConfigToFile)(exports.configFilePath, config);
|
|
16
49
|
};
|
|
17
50
|
const getRawConfig = async () => {
|
|
18
51
|
try {
|
|
19
|
-
|
|
52
|
+
await (0, exports.ensureConfigFilePermissions)(exports.configFilePath);
|
|
53
|
+
const contents = await fs_1.promises.readFile(exports.configFilePath, 'utf8');
|
|
54
|
+
const raw = yaml.load(contents);
|
|
20
55
|
return raw;
|
|
21
56
|
}
|
|
22
57
|
catch (e) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../src/config.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const os = tslib_1.__importStar(require("os"));
|
|
6
|
+
const path = tslib_1.__importStar(require("path"));
|
|
7
|
+
const config_1 = require("./config");
|
|
8
|
+
// Masking file-type bits is the standard way to assert POSIX permissions.
|
|
9
|
+
// eslint-disable-next-line no-bitwise
|
|
10
|
+
const permissionBits = (mode) => mode & 0o777;
|
|
11
|
+
const describePosix = process.platform === 'win32' ? describe.skip : describe;
|
|
12
|
+
describePosix('CLI config permissions', () => {
|
|
13
|
+
let tempDir;
|
|
14
|
+
let configDir;
|
|
15
|
+
let configPath;
|
|
16
|
+
beforeEach(async () => {
|
|
17
|
+
tempDir = await fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'ld-config-'));
|
|
18
|
+
configDir = path.join(tempDir, 'lightdash');
|
|
19
|
+
configPath = path.join(configDir, 'config.yaml');
|
|
20
|
+
});
|
|
21
|
+
afterEach(async () => {
|
|
22
|
+
await fs_1.promises.rm(tempDir, { recursive: true, force: true });
|
|
23
|
+
});
|
|
24
|
+
it('creates the config directory and file with private permissions', async () => {
|
|
25
|
+
await (0, config_1.writeConfigToFile)(configPath, {
|
|
26
|
+
context: { apiKey: 'ldpat_secret' },
|
|
27
|
+
});
|
|
28
|
+
expect(permissionBits((await fs_1.promises.stat(configDir)).mode)).toBe(0o700);
|
|
29
|
+
expect(permissionBits((await fs_1.promises.stat(configPath)).mode)).toBe(0o600);
|
|
30
|
+
});
|
|
31
|
+
it('tightens permissions on an existing config path', async () => {
|
|
32
|
+
await fs_1.promises.mkdir(configDir, { recursive: true, mode: 0o755 });
|
|
33
|
+
await fs_1.promises.writeFile(configPath, 'context: {}', { mode: 0o644 });
|
|
34
|
+
await fs_1.promises.chmod(configDir, 0o755);
|
|
35
|
+
await fs_1.promises.chmod(configPath, 0o644);
|
|
36
|
+
await (0, config_1.ensureConfigFilePermissions)(configPath);
|
|
37
|
+
expect(permissionBits((await fs_1.promises.stat(configDir)).mode)).toBe(0o700);
|
|
38
|
+
expect(permissionBits((await fs_1.promises.stat(configPath)).mode)).toBe(0o600);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -53,5 +53,6 @@ export declare const applySdkMirrorToTemplateDeps: (templateDependencies: Record
|
|
|
53
53
|
* deps (payload stays identical to today's format).
|
|
54
54
|
*/
|
|
55
55
|
export declare const attachDependenciesToCode: (code: DataAppCode, customDeps: Record<string, string>, deps: DataAppDependencies) => DataAppCode;
|
|
56
|
+
export declare const readManifestFromDir: (dir: string) => Promise<DataAppManifest>;
|
|
56
57
|
export declare const readBundleFromDir: (dir: string) => Promise<DataAppCode>;
|
|
57
58
|
//# sourceMappingURL=appCodeFiles.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appCodeFiles.d.ts","sourceRoot":"","sources":["../../../src/handlers/apps/appCodeFiles.ts"],"names":[],"mappings":"AAAA,OAAO,EAKH,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,cAAc,EAEnB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,wBAAwB,EAChC,MAAM,mBAAmB,CAAC;AAO3B;;;;GAIG;AACH,eAAO,MAAM,aAAa,SAChB,MAAM,WACH,MAAM,gBACD,GAAG,CAAC,MAAM,CAAC,KAC1B,MAcF,CAAC;AAEF,eAAO,MAAM,eAAe,QACnB,MAAM,SACJ,eAAe,EAAE,KACzB,OAAO,CAAC,IAAI,CAsBd,CAAC;AAEF,eAAO,MAAM,iBAAiB,QACrB,MAAM,WACF,cAAc,KACxB,OAAO,CAAC,IAAI,CASd,CAAC;AAEF,eAAO,MAAM,gBAAgB,QACpB,MAAM,QACL,WAAW,KAClB,OAAO,CAAC,IAAI,CAWd,CAAC;AAmCF,eAAO,MAAM,eAAe,SAClB,WAAW,qBACE,MAAM,QACnB;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,KAC5D,wBAoBF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,aACnB,eAAe,gBACX,GAAG,CAAC,MAAM,CAAC,KAC1B,MAgBF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAC1B,MAAM,QACL,mBAAmB,KAC1B,OAAO,CAAC,IAAI,CAGd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IAC/B,WAAW,EAAE,MAAM,CAAC;IAKpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,QAC3B,MAAM,KACZ,OAAO,CAAC,oBAAoB,GAAG,IAAI,CA6BrC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,eAClB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,wBACZ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC7C,MAAM,EAOH,CAAC;AAEP;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,yBACf,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,eAC/B,MAAM,KACpB,MAAM,CAAC,MAAM,EAAE,MAAM,CAgBvB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,SAC3B,WAAW,cACL,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,QAC5B,mBAAmB,KAC1B,WAC4E,CAAC;AAEhF,eAAO,MAAM,iBAAiB,QAAe,MAAM,KAAG,OAAO,CAAC,WAAW,
|
|
1
|
+
{"version":3,"file":"appCodeFiles.d.ts","sourceRoot":"","sources":["../../../src/handlers/apps/appCodeFiles.ts"],"names":[],"mappings":"AAAA,OAAO,EAKH,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,cAAc,EAEnB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,wBAAwB,EAChC,MAAM,mBAAmB,CAAC;AAO3B;;;;GAIG;AACH,eAAO,MAAM,aAAa,SAChB,MAAM,WACH,MAAM,gBACD,GAAG,CAAC,MAAM,CAAC,KAC1B,MAcF,CAAC;AAEF,eAAO,MAAM,eAAe,QACnB,MAAM,SACJ,eAAe,EAAE,KACzB,OAAO,CAAC,IAAI,CAsBd,CAAC;AAEF,eAAO,MAAM,iBAAiB,QACrB,MAAM,WACF,cAAc,KACxB,OAAO,CAAC,IAAI,CASd,CAAC;AAEF,eAAO,MAAM,gBAAgB,QACpB,MAAM,QACL,WAAW,KAClB,OAAO,CAAC,IAAI,CAWd,CAAC;AAmCF,eAAO,MAAM,eAAe,SAClB,WAAW,qBACE,MAAM,QACnB;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,KAC5D,wBAoBF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,aACnB,eAAe,gBACX,GAAG,CAAC,MAAM,CAAC,KAC1B,MAgBF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAC1B,MAAM,QACL,mBAAmB,KAC1B,OAAO,CAAC,IAAI,CAGd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IAC/B,WAAW,EAAE,MAAM,CAAC;IAKpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,QAC3B,MAAM,KACZ,OAAO,CAAC,oBAAoB,GAAG,IAAI,CA6BrC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,eAClB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,wBACZ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC7C,MAAM,EAOH,CAAC;AAEP;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,yBACf,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,eAC/B,MAAM,KACpB,MAAM,CAAC,MAAM,EAAE,MAAM,CAgBvB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,SAC3B,WAAW,cACL,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,QAC5B,mBAAmB,KAC1B,WAC4E,CAAC;AAEhF,eAAO,MAAM,mBAAmB,QACvB,MAAM,KACZ,OAAO,CAAC,eAAe,CAYzB,CAAC;AAEF,eAAO,MAAM,iBAAiB,QAAe,MAAM,KAAG,OAAO,CAAC,WAAW,CAYxE,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.readBundleFromDir = exports.attachDependenciesToCode = exports.applySdkMirrorToTemplateDeps = exports.buildDepsWarningLines = exports.readDependenciesFromDir = exports.writeDependenciesToDir = exports.resolveAppFolderName = exports.buildImportBody = exports.writeBundleToDir = exports.writeContextToDir = exports.writeFilesToDir = exports.appFolderName = void 0;
|
|
3
|
+
exports.readBundleFromDir = exports.readManifestFromDir = exports.attachDependenciesToCode = exports.applySdkMirrorToTemplateDeps = exports.buildDepsWarningLines = exports.readDependenciesFromDir = exports.writeDependenciesToDir = exports.resolveAppFolderName = exports.buildImportBody = exports.writeBundleToDir = exports.writeContextToDir = exports.writeFilesToDir = exports.appFolderName = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const common_1 = require("@lightdash/common");
|
|
6
6
|
const fs_1 = require("fs");
|
|
@@ -209,15 +209,18 @@ exports.applySdkMirrorToTemplateDeps = applySdkMirrorToTemplateDeps;
|
|
|
209
209
|
*/
|
|
210
210
|
const attachDependenciesToCode = (code, customDeps, deps) => Object.keys(customDeps).length > 0 ? { ...code, dependencies: deps } : code;
|
|
211
211
|
exports.attachDependenciesToCode = attachDependenciesToCode;
|
|
212
|
-
const
|
|
212
|
+
const readManifestFromDir = async (dir) => {
|
|
213
213
|
const manifestRaw = await fs_1.promises.readFile(path.join(dir, MANIFEST_FILENAME), 'utf-8');
|
|
214
|
-
let manifest;
|
|
215
214
|
try {
|
|
216
|
-
|
|
215
|
+
return YAML.parse(manifestRaw);
|
|
217
216
|
}
|
|
218
217
|
catch (err) {
|
|
219
218
|
throw new Error(`Could not parse ${MANIFEST_FILENAME}: ${(0, common_1.getErrorMessage)(err)}`);
|
|
220
219
|
}
|
|
220
|
+
};
|
|
221
|
+
exports.readManifestFromDir = readManifestFromDir;
|
|
222
|
+
const readBundleFromDir = async (dir) => {
|
|
223
|
+
const manifest = await (0, exports.readManifestFromDir)(dir);
|
|
221
224
|
const srcDir = path.join(dir, 'src');
|
|
222
225
|
const srcExists = await fs_1.promises
|
|
223
226
|
.stat(srcDir)
|
|
@@ -107,6 +107,12 @@ const bundle = {
|
|
|
107
107
|
},
|
|
108
108
|
],
|
|
109
109
|
};
|
|
110
|
+
it('readManifestFromDir returns just the manifest', async () => {
|
|
111
|
+
const dir = await fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'ld-app-'));
|
|
112
|
+
await (0, appCodeFiles_1.writeBundleToDir)(dir, bundle);
|
|
113
|
+
const manifest = await (0, appCodeFiles_1.readManifestFromDir)(dir);
|
|
114
|
+
expect(manifest).toEqual(bundle.manifest);
|
|
115
|
+
});
|
|
110
116
|
it('writes then reads back an identical bundle', async () => {
|
|
111
117
|
const dir = await fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'ld-app-'));
|
|
112
118
|
await (0, appCodeFiles_1.writeBundleToDir)(dir, bundle);
|
|
@@ -141,8 +147,10 @@ it('upload reads back only src/ files, ignoring scaffolding and context', async
|
|
|
141
147
|
await fs_1.promises.writeFile(path.join(dir, 'package.json'), '{"name":"x"}');
|
|
142
148
|
await fs_1.promises.writeFile(path.join(dir, '.lightdash/context/semantic-layer.yml'), 'models: []');
|
|
143
149
|
await fs_1.promises.writeFile(path.join(dir, '.claude/skills/x/SKILL.md'), '# skill');
|
|
150
|
+
await fs_1.promises.writeFile(path.join(dir, '.env.local'), 'VITE_LIGHTDASH_API_KEY=secret');
|
|
144
151
|
const read = await (0, appCodeFiles_1.readBundleFromDir)(dir);
|
|
145
152
|
expect(read.files.every((f) => f.path.startsWith('src/'))).toBe(true);
|
|
153
|
+
expect(read.files.map((f) => f.path)).not.toContain('.env.local');
|
|
146
154
|
expect(read.files.map((f) => f.path).sort()).toEqual(bundle.files.map((f) => f.path).sort());
|
|
147
155
|
});
|
|
148
156
|
it('throws a clear error when the manifest is not valid YAML', async () => {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment passed to the `npm run dev` child process. Only the small set of
|
|
3
|
+
* host variables needed to launch a portable child process is inherited. In
|
|
4
|
+
* particular, no Lightdash credential or unrelated parent-process secret is
|
|
5
|
+
* copied into vite. The nonce is exposed to browser code as a run-scoped,
|
|
6
|
+
* project- and route-limited capability; it is not a Lightdash credential.
|
|
7
|
+
*/
|
|
8
|
+
export declare const buildPreviewChildEnv: (args: {
|
|
9
|
+
serverUrl: string;
|
|
10
|
+
projectUuid: string;
|
|
11
|
+
proxyPort: number;
|
|
12
|
+
proxyNonce: string;
|
|
13
|
+
parentEnv?: NodeJS.ProcessEnv;
|
|
14
|
+
}) => Record<string, string>;
|
|
15
|
+
/**
|
|
16
|
+
* Older downloads have a vite.config.js that proxies /api straight to the
|
|
17
|
+
* instance and expects the credential in .env.local — incompatible with the
|
|
18
|
+
* proxy-based flow (requests would go out uncredentialed and 401).
|
|
19
|
+
*/
|
|
20
|
+
export declare const assertScaffoldingSupportsPreviewProxy: (appDir: string) => Promise<void>;
|
|
21
|
+
export declare const resolvePreviewTarget: (args: {
|
|
22
|
+
pathArg?: string;
|
|
23
|
+
projectFlag?: string;
|
|
24
|
+
cwd: string;
|
|
25
|
+
}) => Promise<{
|
|
26
|
+
appDir: string;
|
|
27
|
+
projectUuid: string;
|
|
28
|
+
}>;
|
|
29
|
+
export declare const assertNodeModulesPresent: (appDir: string) => Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* The login context can point at a different instance than the one the app
|
|
32
|
+
* was downloaded from — the credential pre-flight passes there, but the
|
|
33
|
+
* app's project doesn't exist, which would surface as a confusing 404
|
|
34
|
+
* inside the running app.
|
|
35
|
+
*/
|
|
36
|
+
export declare const projectNotFoundMessage: (args: {
|
|
37
|
+
projectUuid: string;
|
|
38
|
+
serverUrl: string;
|
|
39
|
+
}) => string;
|
|
40
|
+
export declare const preflightPreviewRequest: (args: {
|
|
41
|
+
apiPath: string;
|
|
42
|
+
serverUrl: string;
|
|
43
|
+
authorization: string;
|
|
44
|
+
proxyAuthorization?: string;
|
|
45
|
+
fetchFn?: typeof fetch;
|
|
46
|
+
}) => Promise<boolean>;
|
|
47
|
+
export declare const removeLegacyPreviewCredential: (appDir: string) => Promise<boolean>;
|
|
48
|
+
type AppsPreviewOptions = {
|
|
49
|
+
project?: string;
|
|
50
|
+
url?: string;
|
|
51
|
+
token?: string;
|
|
52
|
+
verbose: boolean;
|
|
53
|
+
};
|
|
54
|
+
export declare const appsPreviewHandler: (pathArg: string | undefined, options: AppsPreviewOptions) => Promise<void>;
|
|
55
|
+
export {};
|
|
56
|
+
//# sourceMappingURL=preview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../../src/handlers/apps/preview.ts"],"names":[],"mappings":"AAaA;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,SAAU;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACjC,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CA2CxB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,WACtC,MAAM,KACf,OAAO,CAAC,IAAI,CASd,CAAC;AAEF,eAAO,MAAM,oBAAoB,SAAgB;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;CACf,KAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAqBlD,CAAC;AAEF,eAAO,MAAM,wBAAwB,WACzB,MAAM,KACf,OAAO,CAAC,IAAI,CAUd,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,SAAU;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACrB,KAAG,MACgT,CAAC;AAErT,eAAO,MAAM,uBAAuB,SAAgB;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B,KAAG,OAAO,CAAC,OAAO,CAmBlB,CAAC;AAKF,eAAO,MAAM,6BAA6B,WAC9B,MAAM,KACf,OAAO,CAAC,OAAO,CAmBjB,CAAC;AAkBF,KAAK,kBAAkB,GAAG;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,eAAO,MAAM,kBAAkB,YAClB,MAAM,GAAG,SAAS,WAClB,kBAAkB,KAC5B,OAAO,CAAC,IAAI,CA+Gd,CAAC"}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.appsPreviewHandler = exports.removeLegacyPreviewCredential = exports.preflightPreviewRequest = exports.projectNotFoundMessage = exports.assertNodeModulesPresent = exports.resolvePreviewTarget = exports.assertScaffoldingSupportsPreviewProxy = exports.buildPreviewChildEnv = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@lightdash/common");
|
|
6
|
+
const crypto_1 = require("crypto");
|
|
7
|
+
const execa_1 = tslib_1.__importDefault(require("execa"));
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const path = tslib_1.__importStar(require("path"));
|
|
10
|
+
const config_1 = require("../../config");
|
|
11
|
+
const globalState_1 = tslib_1.__importDefault(require("../../globalState"));
|
|
12
|
+
const styles = tslib_1.__importStar(require("../../styles"));
|
|
13
|
+
const apiClient_1 = require("../dbt/apiClient");
|
|
14
|
+
const utils_1 = require("../utils");
|
|
15
|
+
const appCodeFiles_1 = require("./appCodeFiles");
|
|
16
|
+
const previewProxy_1 = require("./previewProxy");
|
|
17
|
+
/**
|
|
18
|
+
* Environment passed to the `npm run dev` child process. Only the small set of
|
|
19
|
+
* host variables needed to launch a portable child process is inherited. In
|
|
20
|
+
* particular, no Lightdash credential or unrelated parent-process secret is
|
|
21
|
+
* copied into vite. The nonce is exposed to browser code as a run-scoped,
|
|
22
|
+
* project- and route-limited capability; it is not a Lightdash credential.
|
|
23
|
+
*/
|
|
24
|
+
const buildPreviewChildEnv = (args) => {
|
|
25
|
+
const parentEnv = args.parentEnv ?? process.env;
|
|
26
|
+
const passthroughKeys = [
|
|
27
|
+
'APPDATA',
|
|
28
|
+
'CI',
|
|
29
|
+
'COLORTERM',
|
|
30
|
+
'COMSPEC',
|
|
31
|
+
'ComSpec',
|
|
32
|
+
'FORCE_COLOR',
|
|
33
|
+
'HOME',
|
|
34
|
+
'LANG',
|
|
35
|
+
'LC_ALL',
|
|
36
|
+
'LOCALAPPDATA',
|
|
37
|
+
'NODE_OPTIONS',
|
|
38
|
+
'NODE_PATH',
|
|
39
|
+
'NO_COLOR',
|
|
40
|
+
'PATH',
|
|
41
|
+
'PATHEXT',
|
|
42
|
+
'PNPM_HOME',
|
|
43
|
+
'SHELL',
|
|
44
|
+
'SYSTEMROOT',
|
|
45
|
+
'SystemRoot',
|
|
46
|
+
'TEMP',
|
|
47
|
+
'TERM',
|
|
48
|
+
'TMP',
|
|
49
|
+
'TMPDIR',
|
|
50
|
+
'USERPROFILE',
|
|
51
|
+
'WINDIR',
|
|
52
|
+
];
|
|
53
|
+
const childEnv = {};
|
|
54
|
+
for (const key of passthroughKeys) {
|
|
55
|
+
const value = parentEnv[key];
|
|
56
|
+
if (value !== undefined)
|
|
57
|
+
childEnv[key] = value;
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
...childEnv,
|
|
61
|
+
VITE_LIGHTDASH_URL: args.serverUrl.replace(/\/+$/, ''),
|
|
62
|
+
VITE_LIGHTDASH_API_KEY: args.proxyNonce,
|
|
63
|
+
VITE_LIGHTDASH_PROJECT_UUID: args.projectUuid,
|
|
64
|
+
LIGHTDASH_PREVIEW_PROXY_TARGET: `http://127.0.0.1:${args.proxyPort}`,
|
|
65
|
+
LIGHTDASH_PREVIEW_PROXY_NONCE: args.proxyNonce,
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
exports.buildPreviewChildEnv = buildPreviewChildEnv;
|
|
69
|
+
/**
|
|
70
|
+
* Older downloads have a vite.config.js that proxies /api straight to the
|
|
71
|
+
* instance and expects the credential in .env.local — incompatible with the
|
|
72
|
+
* proxy-based flow (requests would go out uncredentialed and 401).
|
|
73
|
+
*/
|
|
74
|
+
const assertScaffoldingSupportsPreviewProxy = async (appDir) => {
|
|
75
|
+
const viteConfig = await fs_1.promises
|
|
76
|
+
.readFile(path.join(appDir, 'vite.config.js'), 'utf-8')
|
|
77
|
+
.catch(() => '');
|
|
78
|
+
if (!viteConfig.includes('LIGHTDASH_PREVIEW_PROXY_TARGET')) {
|
|
79
|
+
throw new Error(`This app's scaffolding predates the secure preview proxy. Re-download the app ('lightdash download --apps <appUuid>') to refresh vite.config.js, then run preview again.`);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
exports.assertScaffoldingSupportsPreviewProxy = assertScaffoldingSupportsPreviewProxy;
|
|
83
|
+
const resolvePreviewTarget = async (args) => {
|
|
84
|
+
const appDir = args.pathArg
|
|
85
|
+
? path.resolve(args.cwd, args.pathArg)
|
|
86
|
+
: args.cwd;
|
|
87
|
+
let manifest;
|
|
88
|
+
try {
|
|
89
|
+
manifest = await (0, appCodeFiles_1.readManifestFromDir)(appDir);
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
if (err.code === 'ENOENT') {
|
|
93
|
+
throw new Error(`No lightdash-app.yml found in ${appDir}. Run this from a downloaded app folder (apps/<slug>/), or pass it as an argument: lightdash apps preview <path>.`);
|
|
94
|
+
}
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
appDir,
|
|
99
|
+
projectUuid: args.projectFlag ?? manifest.projectUuid,
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
exports.resolvePreviewTarget = resolvePreviewTarget;
|
|
103
|
+
const assertNodeModulesPresent = async (appDir) => {
|
|
104
|
+
const isDir = await fs_1.promises
|
|
105
|
+
.stat(path.join(appDir, 'node_modules'))
|
|
106
|
+
.then((s) => s.isDirectory())
|
|
107
|
+
.catch(() => false);
|
|
108
|
+
if (!isDir) {
|
|
109
|
+
throw new Error(`Dependencies are not installed. Run 'npm install' in ${appDir} first (preview does not auto-install).`);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
exports.assertNodeModulesPresent = assertNodeModulesPresent;
|
|
113
|
+
/**
|
|
114
|
+
* The login context can point at a different instance than the one the app
|
|
115
|
+
* was downloaded from — the credential pre-flight passes there, but the
|
|
116
|
+
* app's project doesn't exist, which would surface as a confusing 404
|
|
117
|
+
* inside the running app.
|
|
118
|
+
*/
|
|
119
|
+
const projectNotFoundMessage = (args) => `Project ${args.projectUuid} was not found on ${args.serverUrl} (or you don't have access). This app belongs to a different project or instance than the one you're logged into — run 'lightdash login <url>' for the server the app was downloaded from, or pass --project to preview against another project.`;
|
|
120
|
+
exports.projectNotFoundMessage = projectNotFoundMessage;
|
|
121
|
+
const preflightPreviewRequest = async (args) => {
|
|
122
|
+
const fetchFn = args.fetchFn ?? fetch;
|
|
123
|
+
try {
|
|
124
|
+
const res = await fetchFn(new URL(args.apiPath, args.serverUrl), {
|
|
125
|
+
headers: {
|
|
126
|
+
Authorization: args.authorization,
|
|
127
|
+
...(args.proxyAuthorization
|
|
128
|
+
? { 'Proxy-Authorization': args.proxyAuthorization }
|
|
129
|
+
: {}),
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
return res.ok;
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
const reason = error instanceof Error ? error.message : 'Unknown network error';
|
|
136
|
+
throw new Error(`Could not connect to ${args.serverUrl} while starting preview: ${reason}`);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
exports.preflightPreviewRequest = preflightPreviewRequest;
|
|
140
|
+
const LEGACY_PREVIEW_CREDENTIAL = /^[ \t]*(?:export[ \t]+)?LIGHTDASH_PREVIEW_API_KEY[ \t]*=.*(?:\r?\n|$)/gm;
|
|
141
|
+
const removeLegacyPreviewCredential = async (appDir) => {
|
|
142
|
+
const legacyEnvPath = path.join(appDir, '.env.local');
|
|
143
|
+
let legacyEnv;
|
|
144
|
+
try {
|
|
145
|
+
legacyEnv = await fs_1.promises.readFile(legacyEnvPath, 'utf-8');
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
if (error.code === 'ENOENT')
|
|
149
|
+
return false;
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
const sanitizedEnv = legacyEnv.replace(LEGACY_PREVIEW_CREDENTIAL, '');
|
|
153
|
+
if (sanitizedEnv === legacyEnv)
|
|
154
|
+
return false;
|
|
155
|
+
if (sanitizedEnv.trim() === '') {
|
|
156
|
+
await fs_1.promises.unlink(legacyEnvPath);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
await fs_1.promises.writeFile(legacyEnvPath, sanitizedEnv, 'utf-8');
|
|
160
|
+
}
|
|
161
|
+
return true;
|
|
162
|
+
};
|
|
163
|
+
exports.removeLegacyPreviewCredential = removeLegacyPreviewCredential;
|
|
164
|
+
// Ctrl-C is the documented way to stop the dev server, so an interrupted child
|
|
165
|
+
// is a normal exit rather than a crash to report. The runner may either die
|
|
166
|
+
// from the signal or translate it into the conventional 128+signal exit code.
|
|
167
|
+
const isUserStoppedDevServer = (error) => {
|
|
168
|
+
if (!(error instanceof Error)) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
const { signal, exitCode } = error;
|
|
172
|
+
return (signal === 'SIGINT' ||
|
|
173
|
+
signal === 'SIGTERM' ||
|
|
174
|
+
exitCode === 130 ||
|
|
175
|
+
exitCode === 143);
|
|
176
|
+
};
|
|
177
|
+
const appsPreviewHandler = async (pathArg, options) => {
|
|
178
|
+
globalState_1.default.setVerbose(options.verbose);
|
|
179
|
+
const config = await (0, config_1.getConfig)();
|
|
180
|
+
const serverUrl = options.url ?? config.context?.serverUrl;
|
|
181
|
+
// getConfig() already folds LIGHTDASH_API_KEY into context.apiKey, so the
|
|
182
|
+
// env-var path is safe. The only exposure is an explicit --token on the
|
|
183
|
+
// command line (visible in shell history / `ps aux`) — warn rather than
|
|
184
|
+
// block, since --token is a supported CI path.
|
|
185
|
+
const apiKey = options.token ?? config.context?.apiKey;
|
|
186
|
+
if (!serverUrl || !apiKey) {
|
|
187
|
+
throw new common_1.AuthorizationError(`Not logged in or missing server URL. Run 'lightdash login <url>' first, or set LIGHTDASH_URL and LIGHTDASH_API_KEY.`);
|
|
188
|
+
}
|
|
189
|
+
const authorization = (0, utils_1.getAuthHeader)(apiKey);
|
|
190
|
+
const proxyAuthorization = config.context?.proxyAuthorization;
|
|
191
|
+
if (options.token) {
|
|
192
|
+
globalState_1.default.log(styles.warning(`Passing --token on the command line exposes it in your shell history and process list. Prefer 'lightdash login' or the LIGHTDASH_API_KEY environment variable.`));
|
|
193
|
+
}
|
|
194
|
+
const target = await (0, exports.resolvePreviewTarget)({
|
|
195
|
+
pathArg,
|
|
196
|
+
projectFlag: options.project,
|
|
197
|
+
cwd: process.cwd(),
|
|
198
|
+
});
|
|
199
|
+
await (0, exports.assertNodeModulesPresent)(target.appDir);
|
|
200
|
+
await (0, exports.assertScaffoldingSupportsPreviewProxy)(target.appDir);
|
|
201
|
+
// Pre-flight the credential before starting vite: an expired/revoked
|
|
202
|
+
// token would otherwise surface as opaque query failures inside the app.
|
|
203
|
+
// Flag-supplied credentials (--url/--token) bypass the stored config, so
|
|
204
|
+
// hit the API directly with exactly what the proxy will forward.
|
|
205
|
+
if (!options.url && !options.token) {
|
|
206
|
+
await (0, apiClient_1.checkLightdashVersion)();
|
|
207
|
+
}
|
|
208
|
+
const preflight = (apiPath) => (0, exports.preflightPreviewRequest)({
|
|
209
|
+
apiPath,
|
|
210
|
+
serverUrl,
|
|
211
|
+
authorization,
|
|
212
|
+
proxyAuthorization,
|
|
213
|
+
});
|
|
214
|
+
if (!(await preflight('/api/v1/user'))) {
|
|
215
|
+
throw new common_1.AuthorizationError(`Your Lightdash credential was rejected by ${serverUrl}. Run 'lightdash login ${serverUrl}' to refresh it, or check the --url/--token values.`);
|
|
216
|
+
}
|
|
217
|
+
if (!(await preflight(`/api/v1/projects/${target.projectUuid}`))) {
|
|
218
|
+
throw new Error((0, exports.projectNotFoundMessage)({
|
|
219
|
+
projectUuid: target.projectUuid,
|
|
220
|
+
serverUrl,
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
// Previous versions persisted this credential in .env.local. Remove only
|
|
224
|
+
// that obsolete entry, preserving any unrelated user-managed settings.
|
|
225
|
+
if (await (0, exports.removeLegacyPreviewCredential)(target.appDir)) {
|
|
226
|
+
globalState_1.default.log(styles.warning(`Removed an obsolete preview credential from ${path.join(target.appDir, '.env.local')}.`));
|
|
227
|
+
}
|
|
228
|
+
const proxyNonce = (0, crypto_1.randomBytes)(16).toString('hex');
|
|
229
|
+
const proxy = await (0, previewProxy_1.startPreviewProxy)({
|
|
230
|
+
upstreamUrl: serverUrl,
|
|
231
|
+
authorization,
|
|
232
|
+
proxyAuthorization,
|
|
233
|
+
projectUuid: target.projectUuid,
|
|
234
|
+
nonce: proxyNonce,
|
|
235
|
+
});
|
|
236
|
+
globalState_1.default.log(`Preview proxy on 127.0.0.1:${proxy.port} — your credential is not passed to the app; SDK traffic is restricted to project ${target.projectUuid}.`);
|
|
237
|
+
globalState_1.default.log(styles.warning(`Preview renders YOUR data with YOUR permissions and user attributes — viewers of the deployed app may see different data.`));
|
|
238
|
+
globalState_1.default.log(`Starting dev server (Ctrl-C to stop)…`);
|
|
239
|
+
try {
|
|
240
|
+
await (0, execa_1.default)('npm', ['run', 'dev'], {
|
|
241
|
+
cwd: target.appDir,
|
|
242
|
+
stdio: 'inherit',
|
|
243
|
+
extendEnv: false,
|
|
244
|
+
env: (0, exports.buildPreviewChildEnv)({
|
|
245
|
+
serverUrl,
|
|
246
|
+
projectUuid: target.projectUuid,
|
|
247
|
+
proxyPort: proxy.port,
|
|
248
|
+
proxyNonce,
|
|
249
|
+
}),
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
catch (e) {
|
|
253
|
+
if (!isUserStoppedDevServer(e)) {
|
|
254
|
+
throw e;
|
|
255
|
+
}
|
|
256
|
+
globalState_1.default.log('Preview stopped.');
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
await proxy.close();
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
exports.appsPreviewHandler = appsPreviewHandler;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.test.d.ts","sourceRoot":"","sources":["../../../src/handlers/apps/preview.test.ts"],"names":[],"mappings":""}
|