@ms-cloudpack/api-server 0.2.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/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @ms-cloudpack/api-server
2
+
3
+ Provides an API server that exposes a pub/sub endpoint for trigger long running tasks.
4
+
5
+ ## Usage
@@ -0,0 +1,40 @@
1
+ import type { Task, TaskOptions } from './types.js';
2
+ /**
3
+ * TaskRunner class manages the state of Tasks.
4
+ * This class is to be called by the api server.
5
+ */
6
+ export declare class TaskRunner {
7
+ private _queue;
8
+ private _pendingTasks;
9
+ private _completedTasks;
10
+ private _requiresRerun;
11
+ private _prevTasks;
12
+ constructor();
13
+ /**
14
+ * The add method enqueues a Task and returns its Promise.
15
+ * If a Task with the same id has already been processed,
16
+ * it returns either the result or the promise.
17
+ * Rerun flag forces the Task to be run again.
18
+ */
19
+ add<TReturn>(task: Task<TReturn>, options?: TaskOptions): Promise<TReturn>;
20
+ /**
21
+ * The reset method removes a task from the TaskRunner
22
+ * and then adds it again through a previous execute function.
23
+ *
24
+ * This is useful for when a task needs to be forcibly rerun.
25
+ *
26
+ * It is intented to be used by the api server
27
+ * to rerun a task when it is stuck or it failed.
28
+ */
29
+ reset<TReturn>(id: string): Promise<TReturn>;
30
+ /**
31
+ * The remove method disposes a task if available
32
+ * and then removes it from the TaskRunner.
33
+ *
34
+ * This is useful when the state of a task needs to be cleared.
35
+ *
36
+ * It is intented to be used by the api server
37
+ * to clear the TaskRunner's state before reloading the page.
38
+ */
39
+ remove(id: string): void;
40
+ }
@@ -0,0 +1,119 @@
1
+ import PQueue from 'p-queue';
2
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
3
+ const log = (...messages) => {
4
+ // console.log(`TaskRunner:`, ...messages);
5
+ };
6
+ /**
7
+ * TaskRunner class manages the state of Tasks.
8
+ * This class is to be called by the api server.
9
+ */
10
+ export class TaskRunner {
11
+ constructor() {
12
+ this._queue = new PQueue({ concurrency: 10 });
13
+ this._pendingTasks = new Map();
14
+ this._completedTasks = new Map();
15
+ this._requiresRerun = new Set();
16
+ this._prevTasks = new Map();
17
+ }
18
+ /**
19
+ * The add method enqueues a Task and returns its Promise.
20
+ * If a Task with the same id has already been processed,
21
+ * it returns either the result or the promise.
22
+ * Rerun flag forces the Task to be run again.
23
+ */
24
+ add(task, options) {
25
+ const { name, id, execute } = task;
26
+ const { rerun, watch } = options || {};
27
+ watch?.();
28
+ if (!this._prevTasks.get(id)) {
29
+ this._prevTasks.set(id, task);
30
+ }
31
+ // Check if we are already completed the task with that id.
32
+ const action = this._completedTasks.get(id);
33
+ if (action !== undefined && !rerun) {
34
+ log(`Previous action found, returning: "${name}:${id}"`);
35
+ return Promise.resolve(action);
36
+ }
37
+ // Check if we are already running the task with that id.
38
+ const pending = this._pendingTasks.get(id);
39
+ if (pending !== undefined) {
40
+ if (rerun) {
41
+ this._requiresRerun.add(id);
42
+ }
43
+ log(`Previous action found, waiting: "${name}:${id}"`);
44
+ return pending;
45
+ }
46
+ const resultPromise = (async () => {
47
+ let taskResult;
48
+ let taskError;
49
+ let completed = false;
50
+ do {
51
+ // Delete from rerun list (if present).
52
+ this._requiresRerun.delete(id);
53
+ try {
54
+ // TODO: possibly remove cast once this issue is resolved
55
+ // https://github.com/sindresorhus/p-queue/issues/175
56
+ taskResult = (await this._queue.add(execute));
57
+ completed = true;
58
+ }
59
+ catch (err) {
60
+ // Save the error--it will be thrown later if a rerun was not requested.
61
+ taskError = err;
62
+ }
63
+ } while (this._requiresRerun.has(id));
64
+ log(`Task finished: "${name}:${id}"`);
65
+ // Delete from pending list.
66
+ this._pendingTasks.delete(id);
67
+ if (completed) {
68
+ // Update finished Tasks.
69
+ this._completedTasks.set(id, taskResult);
70
+ // Resolve the promise.
71
+ return taskResult;
72
+ }
73
+ // If the task never completed successfully, throw the error from earlier.
74
+ throw taskError;
75
+ })();
76
+ this._pendingTasks.set(id, resultPromise);
77
+ this._completedTasks.delete(id);
78
+ return resultPromise;
79
+ }
80
+ /**
81
+ * The reset method removes a task from the TaskRunner
82
+ * and then adds it again through a previous execute function.
83
+ *
84
+ * This is useful for when a task needs to be forcibly rerun.
85
+ *
86
+ * It is intented to be used by the api server
87
+ * to rerun a task when it is stuck or it failed.
88
+ */
89
+ reset(id) {
90
+ this.remove(id);
91
+ const prevTask = this._prevTasks.get(id);
92
+ if (prevTask === undefined) {
93
+ throw new Error(`Task not found: ${id}`);
94
+ }
95
+ return this.add(prevTask, { rerun: true });
96
+ }
97
+ /**
98
+ * The remove method disposes a task if available
99
+ * and then removes it from the TaskRunner.
100
+ *
101
+ * This is useful when the state of a task needs to be cleared.
102
+ *
103
+ * It is intented to be used by the api server
104
+ * to clear the TaskRunner's state before reloading the page.
105
+ */
106
+ remove(id) {
107
+ this._prevTasks
108
+ .get(id)
109
+ ?.dispose?.()
110
+ .catch(() => {
111
+ log(`Error disposing task: ${id}`);
112
+ });
113
+ this._prevTasks.get(id)?.clear?.();
114
+ this._requiresRerun.delete(id);
115
+ this._pendingTasks.delete(id);
116
+ this._completedTasks.delete(id);
117
+ }
118
+ }
119
+ //# sourceMappingURL=TaskRunner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TaskRunner.js","sourceRoot":"","sources":["../src/TaskRunner.ts"],"names":[],"mappings":"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","sourcesContent":["import PQueue from 'p-queue';\nimport type { Task, TaskOptions } from './types.js';\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nconst log = (...messages: unknown[]) => {\n // console.log(`TaskRunner:`, ...messages);\n};\n\n/**\n * TaskRunner class manages the state of Tasks.\n * This class is to be called by the api server.\n */\nexport class TaskRunner {\n private _queue: PQueue;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _pendingTasks: Map<string, Promise<any>>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _completedTasks: Map<string, any>;\n private _requiresRerun: Set<string>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _prevTasks: Map<string, Task<any>>;\n\n constructor() {\n this._queue = new PQueue({ concurrency: 10 });\n this._pendingTasks = new Map();\n this._completedTasks = new Map();\n this._requiresRerun = new Set();\n this._prevTasks = new Map();\n }\n\n /**\n * The add method enqueues a Task and returns its Promise.\n * If a Task with the same id has already been processed,\n * it returns either the result or the promise.\n * Rerun flag forces the Task to be run again.\n */\n add<TReturn>(task: Task<TReturn>, options?: TaskOptions): Promise<TReturn> {\n const { name, id, execute } = task;\n const { rerun, watch } = options || {};\n\n watch?.();\n\n if (!this._prevTasks.get(id)) {\n this._prevTasks.set(id, task);\n }\n\n // Check if we are already completed the task with that id.\n const action = this._completedTasks.get(id) as TReturn | undefined;\n if (action !== undefined && !rerun) {\n log(`Previous action found, returning: \"${name}:${id}\"`);\n return Promise.resolve(action);\n }\n\n // Check if we are already running the task with that id.\n const pending = this._pendingTasks.get(id) as Promise<TReturn> | undefined;\n if (pending !== undefined) {\n if (rerun) {\n this._requiresRerun.add(id);\n }\n log(`Previous action found, waiting: \"${name}:${id}\"`);\n return pending;\n }\n\n const resultPromise = (async () => {\n let taskResult: TReturn | undefined;\n let taskError: unknown;\n let completed = false;\n\n do {\n // Delete from rerun list (if present).\n this._requiresRerun.delete(id);\n try {\n // TODO: possibly remove cast once this issue is resolved\n // https://github.com/sindresorhus/p-queue/issues/175\n taskResult = (await this._queue.add(execute)) as TReturn | undefined;\n completed = true;\n } catch (err) {\n // Save the error--it will be thrown later if a rerun was not requested.\n taskError = err;\n }\n } while (this._requiresRerun.has(id));\n\n log(`Task finished: \"${name}:${id}\"`);\n\n // Delete from pending list.\n this._pendingTasks.delete(id);\n\n if (completed) {\n // Update finished Tasks.\n this._completedTasks.set(id, taskResult);\n // Resolve the promise.\n return taskResult as TReturn;\n }\n\n // If the task never completed successfully, throw the error from earlier.\n throw taskError;\n })();\n\n this._pendingTasks.set(id, resultPromise);\n this._completedTasks.delete(id);\n\n return resultPromise;\n }\n\n /**\n * The reset method removes a task from the TaskRunner\n * and then adds it again through a previous execute function.\n *\n * This is useful for when a task needs to be forcibly rerun.\n *\n * It is intented to be used by the api server\n * to rerun a task when it is stuck or it failed.\n */\n reset<TReturn>(id: string): Promise<TReturn> {\n this.remove(id);\n const prevTask = this._prevTasks.get(id);\n if (prevTask === undefined) {\n throw new Error(`Task not found: ${id}`);\n }\n return <Promise<TReturn>>this.add(prevTask, { rerun: true });\n }\n\n /**\n * The remove method disposes a task if available\n * and then removes it from the TaskRunner.\n *\n * This is useful when the state of a task needs to be cleared.\n *\n * It is intented to be used by the api server\n * to clear the TaskRunner's state before reloading the page.\n */\n remove(id: string): void {\n this._prevTasks\n .get(id)\n ?.dispose?.()\n .catch(() => {\n log(`Error disposing task: ${id}`);\n });\n this._prevTasks.get(id)?.clear?.();\n this._requiresRerun.delete(id);\n this._pendingTasks.delete(id);\n this._completedTasks.delete(id);\n }\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import type { Session } from './types.js';
2
+ export declare function addOverride({ session, packageName, importPath, filename, }: {
3
+ session: Session;
4
+ packageName: string;
5
+ importPath: string;
6
+ filename?: string;
7
+ }): Promise<void>;
@@ -0,0 +1,63 @@
1
+ import semver from 'semver';
2
+ import { createPackageOverrideTransform } from './common/createPackageOverrideTransform.js';
3
+ import { addExportsMapEntry, findResolveMapEntry, getExportsMap, PackageDefinitions, } from '@ms-cloudpack/package-utilities';
4
+ import { slash } from '@ms-cloudpack/path-string-parsing';
5
+ import { parseRequestInfo } from './parseRequestInfo.js';
6
+ import { readConfig, writeConfig } from '@ms-cloudpack/config';
7
+ export async function addOverride({ session, packageName, importPath, filename, }) {
8
+ let definition = undefined;
9
+ if (filename) {
10
+ const requestPath = slash(new URL(filename).pathname);
11
+ const { packageName: name, version } = parseRequestInfo(requestPath);
12
+ definition = { name, version };
13
+ }
14
+ const entry = findResolveMapEntry({
15
+ packageName,
16
+ resolveMap: session.resolveMap,
17
+ definition,
18
+ });
19
+ if (entry === undefined) {
20
+ console.error(`Could not find entry of package ${packageName} in resolve map`);
21
+ return;
22
+ }
23
+ const exports = (await getExportsMap(entry.path));
24
+ await addExportsMapEntry({
25
+ exports,
26
+ packagePath: entry.path,
27
+ importPath,
28
+ });
29
+ const packageOverride = {
30
+ name: packageName,
31
+ versionRequirement: entry.version,
32
+ overrides: {
33
+ exports,
34
+ },
35
+ };
36
+ // Parse config if it exists.
37
+ const cloudpackConfig = await readConfig(session.appPath);
38
+ // Add package override to config.
39
+ if (!cloudpackConfig.packageOverrides) {
40
+ cloudpackConfig.packageOverrides = [packageOverride];
41
+ }
42
+ else {
43
+ // Check if override already exists.
44
+ const existingOverride = cloudpackConfig.packageOverrides.find((o) => o.name === packageOverride.name && semver.satisfies(packageOverride.versionRequirement, o.versionRequirement));
45
+ if (existingOverride) {
46
+ // Update existing override.
47
+ existingOverride.overrides = packageOverride.overrides;
48
+ }
49
+ else {
50
+ // Add new override.
51
+ cloudpackConfig.packageOverrides.push(packageOverride);
52
+ }
53
+ }
54
+ // Write config.
55
+ await writeConfig(cloudpackConfig, session.appPath);
56
+ console.debug(`Added override for ${packageName} to cloudpack config`);
57
+ const override = {
58
+ packageOverrides: [packageOverride],
59
+ };
60
+ // Initialize package overrides.
61
+ PackageDefinitions.getInstance().registerTransform(createPackageOverrideTransform(override));
62
+ }
63
+ //# sourceMappingURL=addOverride.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"addOverride.js","sourceRoot":"","sources":["../src/addOverride.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,8BAA8B,EAAE,MAAM,4CAA4C,CAAC;AAC5F,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,aAAa,EACb,kBAAkB,GACnB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,KAAK,EAAE,MAAM,mCAAmC,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE/D,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,EAChC,OAAO,EACP,WAAW,EACX,UAAU,EACV,QAAQ,GAMT;IACC,IAAI,UAAU,GAAG,SAAS,CAAC;IAE3B,IAAI,QAAQ,EAAE;QACZ,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC;QACtD,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;QACrE,UAAU,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;KAChC;IAED,MAAM,KAAK,GAAG,mBAAmB,CAAC;QAChC,WAAW;QACX,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,UAAU;KACX,CAAC,CAAC;IAEH,IAAI,KAAK,KAAK,SAAS,EAAE;QACvB,OAAO,CAAC,KAAK,CAAC,mCAAmC,WAAW,iBAAiB,CAAC,CAAC;QAC/E,OAAO;KACR;IAED,MAAM,OAAO,GAAG,CAAC,MAAM,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAA2C,CAAC;IAE5F,MAAM,kBAAkB,CAAC;QACvB,OAAO;QACP,WAAW,EAAE,KAAK,CAAC,IAAI;QACvB,UAAU;KACX,CAAC,CAAC;IAEH,MAAM,eAAe,GAAoB;QACvC,IAAI,EAAE,WAAW;QACjB,kBAAkB,EAAE,KAAK,CAAC,OAAO;QACjC,SAAS,EAAE;YACT,OAAO;SACR;KACF,CAAC;IAEF,6BAA6B;IAC7B,MAAM,eAAe,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAE1D,kCAAkC;IAClC,IAAI,CAAC,eAAe,CAAC,gBAAgB,EAAE;QACrC,eAAe,CAAC,gBAAgB,GAAG,CAAC,eAAe,CAAC,CAAC;KACtD;SAAM;QACL,oCAAoC;QACpC,MAAM,gBAAgB,GAAG,eAAe,CAAC,gBAAgB,CAAC,IAAI,CAC5D,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,IAAI,KAAK,eAAe,CAAC,IAAI,IAAI,MAAM,CAAC,SAAS,CAAC,eAAe,CAAC,kBAAkB,EAAE,CAAC,CAAC,kBAAkB,CAAC,CAChH,CAAC;QAEF,IAAI,gBAAgB,EAAE;YACpB,4BAA4B;YAC5B,gBAAgB,CAAC,SAAS,GAAG,eAAe,CAAC,SAAS,CAAC;SACxD;aAAM;YACL,oBAAoB;YACpB,eAAe,CAAC,gBAAgB,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;SACxD;KACF;IAED,gBAAgB;IAChB,MAAM,WAAW,CAAC,eAAe,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IACpD,OAAO,CAAC,KAAK,CAAC,sBAAsB,WAAW,sBAAsB,CAAC,CAAC;IAEvE,MAAM,QAAQ,GAAoB;QAChC,gBAAgB,EAAE,CAAC,eAAe,CAAC;KACpC,CAAC;IAEF,gCAAgC;IAChC,kBAAkB,CAAC,WAAW,EAAE,CAAC,iBAAiB,CAAC,8BAA8B,CAAC,QAAQ,CAAC,CAAC,CAAC;AAC/F,CAAC","sourcesContent":["import semver from 'semver';\nimport { createPackageOverrideTransform } from './common/createPackageOverrideTransform.js';\nimport {\n addExportsMapEntry,\n findResolveMapEntry,\n getExportsMap,\n PackageDefinitions,\n} from '@ms-cloudpack/package-utilities';\nimport type { CloudpackConfig, Session, PackageOverride } from './types.js';\nimport { slash } from '@ms-cloudpack/path-string-parsing';\nimport { parseRequestInfo } from './parseRequestInfo.js';\nimport { readConfig, writeConfig } from '@ms-cloudpack/config';\n\nexport async function addOverride({\n session,\n packageName,\n importPath,\n filename,\n}: {\n session: Session;\n packageName: string;\n importPath: string;\n filename?: string;\n}) {\n let definition = undefined;\n\n if (filename) {\n const requestPath = slash(new URL(filename).pathname);\n const { packageName: name, version } = parseRequestInfo(requestPath);\n definition = { name, version };\n }\n\n const entry = findResolveMapEntry({\n packageName,\n resolveMap: session.resolveMap,\n definition,\n });\n\n if (entry === undefined) {\n console.error(`Could not find entry of package ${packageName} in resolve map`);\n return;\n }\n\n const exports = (await getExportsMap(entry.path)) as Record<string, Record<string, string>>;\n\n await addExportsMapEntry({\n exports,\n packagePath: entry.path,\n importPath,\n });\n\n const packageOverride: PackageOverride = {\n name: packageName,\n versionRequirement: entry.version,\n overrides: {\n exports,\n },\n };\n\n // Parse config if it exists.\n const cloudpackConfig = await readConfig(session.appPath);\n\n // Add package override to config.\n if (!cloudpackConfig.packageOverrides) {\n cloudpackConfig.packageOverrides = [packageOverride];\n } else {\n // Check if override already exists.\n const existingOverride = cloudpackConfig.packageOverrides.find(\n (o) =>\n o.name === packageOverride.name && semver.satisfies(packageOverride.versionRequirement, o.versionRequirement),\n );\n\n if (existingOverride) {\n // Update existing override.\n existingOverride.overrides = packageOverride.overrides;\n } else {\n // Add new override.\n cloudpackConfig.packageOverrides.push(packageOverride);\n }\n }\n\n // Write config.\n await writeConfig(cloudpackConfig, session.appPath);\n console.debug(`Added override for ${packageName} to cloudpack config`);\n\n const override: CloudpackConfig = {\n packageOverrides: [packageOverride],\n };\n\n // Initialize package overrides.\n PackageDefinitions.getInstance().registerTransform(createPackageOverrideTransform(override));\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import type { CloudpackConfig } from '../types.js';
2
+ import type { PackageDefinitionTransform } from '@ms-cloudpack/bundler-types';
3
+ /**
4
+ * Creates a transform to be registered with a PackageDefinitions registry for overriding package.json definitions.
5
+ */
6
+ export declare function createPackageOverrideTransform(config: CloudpackConfig): PackageDefinitionTransform;
@@ -0,0 +1,19 @@
1
+ import { findPackageOverride } from './findPackageOverride.js';
2
+ /**
3
+ * Creates a transform to be registered with a PackageDefinitions registry for overriding package.json definitions.
4
+ */
5
+ export function createPackageOverrideTransform(config) {
6
+ const { packageOverrides = [] } = config;
7
+ return (packageDefinition) => {
8
+ const { name = '', version = '' } = packageDefinition;
9
+ const packageOverride = findPackageOverride(packageOverrides, name, version);
10
+ if (packageOverride) {
11
+ return {
12
+ ...packageDefinition,
13
+ ...packageOverride.overrides,
14
+ };
15
+ }
16
+ return undefined;
17
+ };
18
+ }
19
+ //# sourceMappingURL=createPackageOverrideTransform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createPackageOverrideTransform.js","sourceRoot":"","sources":["../../src/common/createPackageOverrideTransform.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAE/D;;GAEG;AACH,MAAM,UAAU,8BAA8B,CAAC,MAAuB;IACpE,MAAM,EAAE,gBAAgB,GAAG,EAAE,EAAE,GAAG,MAAM,CAAC;IAEzC,OAAO,CAAC,iBAA8B,EAAE,EAAE;QACxC,MAAM,EAAE,IAAI,GAAG,EAAE,EAAE,OAAO,GAAG,EAAE,EAAE,GAAG,iBAAiB,CAAC;QACtD,MAAM,eAAe,GAAG,mBAAmB,CAAC,gBAAgB,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QAE7E,IAAI,eAAe,EAAE;YACnB,OAAO;gBACL,GAAG,iBAAiB;gBACpB,GAAG,eAAe,CAAC,SAAS;aAC7B,CAAC;SACH;QAED,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type { CloudpackConfig } from '../types.js';\nimport type { PackageJson, PackageDefinitionTransform } from '@ms-cloudpack/bundler-types';\nimport { findPackageOverride } from './findPackageOverride.js';\n\n/**\n * Creates a transform to be registered with a PackageDefinitions registry for overriding package.json definitions.\n */\nexport function createPackageOverrideTransform(config: CloudpackConfig): PackageDefinitionTransform {\n const { packageOverrides = [] } = config;\n\n return (packageDefinition: PackageJson) => {\n const { name = '', version = '' } = packageDefinition;\n const packageOverride = findPackageOverride(packageOverrides, name, version);\n\n if (packageOverride) {\n return {\n ...packageDefinition,\n ...packageOverride.overrides,\n };\n }\n\n return undefined;\n };\n}\n"]}
@@ -0,0 +1,5 @@
1
+ import type { PackageOverride } from '../types.js';
2
+ /**
3
+ * Given a set of package overrides, finds the overrides for the given package name and version.
4
+ */
5
+ export declare function findPackageOverride(packageOverrides: PackageOverride[], packageName: string, packageVersion: string): PackageOverride | undefined;
@@ -0,0 +1,8 @@
1
+ import semver from 'semver';
2
+ /**
3
+ * Given a set of package overrides, finds the overrides for the given package name and version.
4
+ */
5
+ export function findPackageOverride(packageOverrides, packageName, packageVersion) {
6
+ return packageOverrides.find((override) => override.name === packageName && semver.satisfies(packageVersion, override.versionRequirement));
7
+ }
8
+ //# sourceMappingURL=findPackageOverride.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findPackageOverride.js","sourceRoot":"","sources":["../../src/common/findPackageOverride.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAG5B;;GAEG;AACH,MAAM,UAAU,mBAAmB,CACjC,gBAAmC,EACnC,WAAmB,EACnB,cAAsB;IAEtB,OAAO,gBAAgB,CAAC,IAAI,CAC1B,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,KAAK,WAAW,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAC7G,CAAC;AACJ,CAAC","sourcesContent":["import semver from 'semver';\nimport type { PackageOverride } from '../types.js';\n\n/**\n * Given a set of package overrides, finds the overrides for the given package name and version.\n */\nexport function findPackageOverride(\n packageOverrides: PackageOverride[],\n packageName: string,\n packageVersion: string,\n): PackageOverride | undefined {\n return packageOverrides.find(\n (override) => override.name === packageName && semver.satisfies(packageVersion, override.versionRequirement),\n );\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export declare function getCachePath(): string;
2
+ export declare function getLocalCachePath(appPath?: string): string;
@@ -0,0 +1,9 @@
1
+ import os from 'os';
2
+ import path from 'path';
3
+ export function getCachePath() {
4
+ return path.join(os.homedir(), '.cloudpack');
5
+ }
6
+ export function getLocalCachePath(appPath) {
7
+ return path.join(appPath ?? process.cwd(), 'node_modules', '.cache', 'cloudpack');
8
+ }
9
+ //# sourceMappingURL=getCachePath.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getCachePath.js","sourceRoot":"","sources":["../../src/common/getCachePath.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,MAAM,UAAU,YAAY;IAC1B,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAgB;IAChD,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC;AACpF,CAAC","sourcesContent":["import os from 'os';\nimport path from 'path';\n\nexport function getCachePath() {\n return path.join(os.homedir(), '.cloudpack');\n}\n\nexport function getLocalCachePath(appPath?: string) {\n return path.join(appPath ?? process.cwd(), 'node_modules', '.cache', 'cloudpack');\n}\n"]}
package/lib/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { startApiServer } from './startApiServer.js';
2
+ export type { ApiServer, CloudpackConfig, DevServer, PackageOverride, Route, Session, SessionStats, Task, TaskDescription, TaskEndDescription, TaskError, TaskOptions, TaskStartDescription, } from './types.js';
package/lib/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { startApiServer } from './startApiServer.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC","sourcesContent":["export { startApiServer } from './startApiServer.js';\n\nexport type {\n ApiServer,\n CloudpackConfig,\n DevServer,\n PackageOverride,\n Route,\n Session,\n SessionStats,\n Task,\n TaskDescription,\n TaskEndDescription,\n TaskError,\n TaskOptions,\n TaskStartDescription,\n} from './types.js';\n"]}
@@ -0,0 +1,6 @@
1
+ export declare function parseRequestInfo(requestPath: string): {
2
+ packageName: string;
3
+ version: string;
4
+ bundled: boolean;
5
+ filePath: string;
6
+ };
@@ -0,0 +1,14 @@
1
+ export function parseRequestInfo(requestPath) {
2
+ const matches = requestPath.match(/[/]?(@[-_a-z-A-Z0-9.]+\/[-_a-z-A-Z0-9.]+|[a-zA-Z-0-9.]+)(@([-_a-zA-Z-0-9.]+))?(\/v([0-9.]+))?(\/(bundled|unbundled))?(\/[-_/a-zA-Z0-9.+]+)?/) || [];
3
+ const packageName = matches[1] || '';
4
+ const version = matches[3] || '';
5
+ const bundled = (matches.length > 8 && matches[7]) === 'bundled';
6
+ const filePath = (matches.length > 8 && matches[8]) || '';
7
+ return {
8
+ packageName,
9
+ version,
10
+ bundled,
11
+ filePath,
12
+ };
13
+ }
14
+ //# sourceMappingURL=parseRequestInfo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseRequestInfo.js","sourceRoot":"","sources":["../src/parseRequestInfo.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,gBAAgB,CAAC,WAAmB;IAClD,MAAM,OAAO,GACX,WAAW,CAAC,KAAK,CACf,6IAA6I,CAC9I,IAAI,EAAE,CAAC;IAEV,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACrC,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC;IACjE,MAAM,QAAQ,GAAG,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAE1D,OAAO;QACL,WAAW;QACX,OAAO;QACP,OAAO;QACP,QAAQ;KACT,CAAC;AACJ,CAAC","sourcesContent":["export function parseRequestInfo(requestPath: string) {\n const matches =\n requestPath.match(\n /[/]?(@[-_a-z-A-Z0-9.]+\\/[-_a-z-A-Z0-9.]+|[a-zA-Z-0-9.]+)(@([-_a-zA-Z-0-9.]+))?(\\/v([0-9.]+))?(\\/(bundled|unbundled))?(\\/[-_/a-zA-Z0-9.+]+)?/,\n ) || [];\n\n const packageName = matches[1] || '';\n const version = matches[3] || '';\n const bundled = (matches.length > 8 && matches[7]) === 'bundled';\n const filePath = (matches.length > 8 && matches[8]) || '';\n\n return {\n packageName,\n version,\n bundled,\n filePath,\n };\n}\n"]}
@@ -0,0 +1,21 @@
1
+ import type { ApiServer, CloudpackConfig, Session } from './types.js';
2
+ import type { Span } from '@ms-cloudpack/telemetry';
3
+ /**
4
+ * The api server handles the data pub/sub (for things like reporting build status)
5
+ * and hosting the dashboard verb backend (for executing actions like loading vscode,
6
+ * changing dependencies, connecting new projects to the running session, etc.)
7
+ *
8
+ * Each time `cloudpack start` is run, an api server will start up for that session. The
9
+ * hosted web page will have a "cloudpack-api-server" header which points
10
+ * to the websocket server.
11
+ *
12
+ * The bundle server will call the api server to publish build status updates.
13
+ *
14
+ * The app server will burn the api server url on the host html, so that client-side
15
+ * javascript can connect and subscribe to page changes.
16
+ */
17
+ export declare function startApiServer({ session, config, rootSpan, }: {
18
+ session: Session;
19
+ config: CloudpackConfig;
20
+ rootSpan: Span | undefined;
21
+ }): Promise<ApiServer>;
@@ -0,0 +1,301 @@
1
+ import os from 'os';
2
+ import getPort from 'get-port';
3
+ import path from 'path';
4
+ import http from 'http';
5
+ import https from 'https';
6
+ import WebSocket, { WebSocketServer } from 'ws';
7
+ import { createDataBus } from '@ms-cloudpack/data-bus';
8
+ import { execSync } from 'child_process';
9
+ import { TaskRunner } from './TaskRunner.js';
10
+ import { getCachePath } from './common/getCachePath.js';
11
+ import fsExtra from 'fs-extra';
12
+ import fs from 'fs';
13
+ import { startWatcher } from './startWatcher.js';
14
+ import { PackageDefinitions } from '@ms-cloudpack/package-utilities';
15
+ import { createPackageOverrideTransform } from './common/createPackageOverrideTransform.js';
16
+ import { configTemplate, getConfigPath, readConfigSync, writeConfigSync } from '@ms-cloudpack/config';
17
+ import { addOverride } from './addOverride.js';
18
+ import { validateOverride } from './validateOverride.js';
19
+ import { parseHttpsConfig } from '@ms-cloudpack/create-express-app';
20
+ /**
21
+ * The api server handles the data pub/sub (for things like reporting build status)
22
+ * and hosting the dashboard verb backend (for executing actions like loading vscode,
23
+ * changing dependencies, connecting new projects to the running session, etc.)
24
+ *
25
+ * Each time `cloudpack start` is run, an api server will start up for that session. The
26
+ * hosted web page will have a "cloudpack-api-server" header which points
27
+ * to the websocket server.
28
+ *
29
+ * The bundle server will call the api server to publish build status updates.
30
+ *
31
+ * The app server will burn the api server url on the host html, so that client-side
32
+ * javascript can connect and subscribe to page changes.
33
+ */
34
+ export async function startApiServer({ session, config, rootSpan, }) {
35
+ const host = config?.devServer?.domain || 'localhost';
36
+ const port = await getPort({ port: [9890, 9891, 9892, 9893] });
37
+ const bus = createDataBus();
38
+ const sessionStats = {
39
+ status: 'idle',
40
+ remainingTasks: 0,
41
+ totalTasks: 0,
42
+ totalErrors: 0,
43
+ totalWarnings: 0,
44
+ };
45
+ const allTasks = new Map();
46
+ const taskRunner = new TaskRunner();
47
+ function reportStart(taskStart) {
48
+ const previousResult = allTasks.get(taskStart.id);
49
+ const now = new Date().getTime();
50
+ if (!previousResult) {
51
+ sessionStats.totalTasks++;
52
+ }
53
+ if (previousResult?.status === 'complete') {
54
+ sessionStats.totalErrors -= previousResult.errors?.length || 0;
55
+ sessionStats.totalWarnings -= previousResult.warnings?.length || 0;
56
+ }
57
+ sessionStats.remainingTasks++;
58
+ sessionStats.status = 'pending';
59
+ allTasks.set(taskStart.id, {
60
+ ...taskStart,
61
+ status: 'pending',
62
+ startTime: now,
63
+ });
64
+ bus.publish([session.id, 'status'], sessionStats);
65
+ bus.publish([session.id, 'status-details'], { tasks: Array.from(allTasks.values()) });
66
+ }
67
+ function reportEnd(taskEnd) {
68
+ const previousResult = allTasks.get(taskEnd.id);
69
+ const now = new Date().getTime();
70
+ if (previousResult) {
71
+ sessionStats.remainingTasks--;
72
+ sessionStats.totalErrors += taskEnd.errors?.length || 0;
73
+ sessionStats.totalWarnings += taskEnd.warnings?.length || 0;
74
+ allTasks.set(taskEnd.id, {
75
+ ...previousResult,
76
+ ...taskEnd,
77
+ status: 'complete',
78
+ durationMilliseconds: now - previousResult.startTime,
79
+ lastUpdated: now,
80
+ });
81
+ if (sessionStats.remainingTasks === 0) {
82
+ sessionStats.status = 'idle';
83
+ }
84
+ bus.publish([session.id, 'status'], sessionStats);
85
+ bus.publish([session.id, 'status-details'], { tasks: Array.from(allTasks.values()) });
86
+ }
87
+ }
88
+ bus.publish([session.id, 'status'], sessionStats);
89
+ const addTask = (task, options) => {
90
+ if (task.watch) {
91
+ options = options || {};
92
+ options.watch = () => {
93
+ watcher.watch(task, { onSuccess: notifyReload });
94
+ };
95
+ }
96
+ return taskRunner.add({
97
+ ...task,
98
+ execute: async () => {
99
+ reportStart({
100
+ ...task.getStartDescription?.(),
101
+ id: task.id,
102
+ });
103
+ let taskResult;
104
+ try {
105
+ taskResult = await task.execute();
106
+ }
107
+ catch (err) {
108
+ // no-op
109
+ }
110
+ reportEnd({
111
+ ...task.getEndDescription?.(taskResult),
112
+ id: task.id,
113
+ });
114
+ return taskResult;
115
+ },
116
+ }, options);
117
+ };
118
+ const watcher = startWatcher(addTask);
119
+ // Create a node http(s) server.
120
+ let server;
121
+ const httpsConfig = config?.devServer?.https;
122
+ const protocol = httpsConfig ? 'wss' : 'ws';
123
+ if (httpsConfig) {
124
+ const { key, cert, passphrase } = parseHttpsConfig(httpsConfig);
125
+ if (!key || !cert) {
126
+ console.error('Invalid https config in cloudpack.config.js. Make sure key and cert are valid.');
127
+ throw new Error('Invalid https config.');
128
+ }
129
+ server = https
130
+ .createServer({
131
+ key,
132
+ cert,
133
+ passphrase,
134
+ }, () => {
135
+ /* no-op */
136
+ })
137
+ .listen(port, host);
138
+ }
139
+ else {
140
+ server = http.createServer().listen(port, host);
141
+ }
142
+ // Create a websocket server which listens for subscriptions and verb requests,
143
+ // and broadcasts the data to all connected clients.
144
+ const socketServer = new WebSocketServer({ server }, () => {
145
+ // console.log(`ApiServer: Started on port ${port}`);
146
+ }).on('connection', (socket) => {
147
+ const disposables = new Map();
148
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
149
+ function sendMessage(message) {
150
+ socket.send(JSON.stringify(message, null, 2));
151
+ }
152
+ socket.send(JSON.stringify({
153
+ type: 'sequence',
154
+ sequence: session.sequence,
155
+ }));
156
+ socket
157
+ .on('message', (rawData) => {
158
+ try {
159
+ const request = JSON.parse(rawData.toString());
160
+ switch (request.type) {
161
+ case 'subscribe': {
162
+ const { path: subscriptionPath, id } = request.data;
163
+ disposables.set(id, bus.subscribe(subscriptionPath,
164
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
165
+ (data) => sendMessage({ type: 'notify', id, path: subscriptionPath, data })));
166
+ break;
167
+ }
168
+ case 'unsubscribe': {
169
+ const id = request.data.id;
170
+ if (id) {
171
+ const dispose = disposables.get(id);
172
+ if (dispose) {
173
+ dispose();
174
+ disposables.delete(id);
175
+ }
176
+ }
177
+ break;
178
+ }
179
+ case 'openSource': {
180
+ const { rootPath, relativePath = '', line, column } = request.data;
181
+ const fullPath = path.join(rootPath, relativePath);
182
+ console.log(`Opening VSCode with path: ${fullPath}`);
183
+ execSync(`code --goto ${fullPath}:${line}:${column}`, { cwd: process.cwd() });
184
+ break;
185
+ }
186
+ case 'open': {
187
+ const { rootPath, relativePath = '' } = request.data;
188
+ const fullPath = path.join(rootPath, relativePath);
189
+ console.log(`Opening path: ${fullPath}`);
190
+ const openCommand = os.platform() == 'win32' ? 'start' : 'open';
191
+ execSync(`${openCommand} ${fullPath}`, { cwd: process.cwd() });
192
+ break;
193
+ }
194
+ case 'editConfig': {
195
+ const fullPath = path.resolve(getConfigPath());
196
+ // Check if file exists
197
+ if (!fs.existsSync(fullPath)) {
198
+ // Create file with default config
199
+ writeConfigSync(configTemplate);
200
+ }
201
+ console.log('Opening config file');
202
+ console.log(`Opening VSCode with path: ${fullPath}`);
203
+ execSync(`code --goto ${fullPath}:${1}`, { cwd: process.cwd() });
204
+ break;
205
+ }
206
+ case 'addOverride': {
207
+ // TODO: Send filename from request data to findResolveMapEntry when chromium Issue 1371551 is fixed.
208
+ const { packageName, importPath } = request.data;
209
+ (async () => {
210
+ await addOverride({ session, packageName, importPath });
211
+ session.incrementSessionVersion();
212
+ notifyReload();
213
+ })().catch(() => {
214
+ // no-op
215
+ });
216
+ break;
217
+ }
218
+ case 'validateOverride': {
219
+ // TODO: Get filename from request data when chromium Issue 1371551 is fixed.
220
+ const { requestId, data } = request;
221
+ const { packageName, importPath } = data;
222
+ (async () => {
223
+ const fixable = await validateOverride({ session, packageName, importPath });
224
+ sendMessage({ type: 'validateOverride', requestId, data: { fixable } });
225
+ })().catch(() => {
226
+ // no-op
227
+ });
228
+ break;
229
+ }
230
+ case 'restartAllTasks': {
231
+ console.log('ApiServer: Restarting all tasks');
232
+ // Clear entire local cache.
233
+ fsExtra.emptyDirSync(getCachePath());
234
+ // All tasks can be removed as notifyReload()
235
+ // will cause the client to add the tasks again.
236
+ allTasks.forEach((_, id) => {
237
+ taskRunner.remove(id);
238
+ });
239
+ // Parse config if it exists.
240
+ const cloudpackConfig = readConfigSync();
241
+ // Initialize package overrides.
242
+ PackageDefinitions.getInstance().registerTransform(createPackageOverrideTransform(cloudpackConfig));
243
+ // Update session version.
244
+ session.incrementSessionVersion();
245
+ notifyReload();
246
+ break;
247
+ }
248
+ case 'restartTask': {
249
+ const { id, inputPath } = request.data;
250
+ console.log(`ApiServer: Restarting task ${id}`);
251
+ taskRunner.remove(id);
252
+ session.incrementTargetVersion(inputPath);
253
+ notifyReload();
254
+ break;
255
+ }
256
+ case 'reportMetric': {
257
+ const { metric, value } = request.data;
258
+ console.debug(`ApiServer: Received metric: ${metric}, value: ${value}`);
259
+ rootSpan?.addEvent(metric, value);
260
+ break;
261
+ }
262
+ }
263
+ }
264
+ catch (err) {
265
+ console.error('ApiServer: error handling message');
266
+ console.error('Error:', err);
267
+ console.error('Message:', rawData.toString());
268
+ }
269
+ })
270
+ .on('close', () => {
271
+ for (const d of disposables.values()) {
272
+ d();
273
+ }
274
+ });
275
+ });
276
+ const notifyReload = () => {
277
+ session.sequence++;
278
+ socketServer.clients.forEach((socket) => {
279
+ if (socket.readyState === WebSocket.OPEN) {
280
+ socket.send(JSON.stringify({
281
+ type: 'reload',
282
+ data: { sessionId: session.id },
283
+ }));
284
+ }
285
+ });
286
+ };
287
+ return {
288
+ url: `${protocol}://${host}:${port}`,
289
+ port,
290
+ notifyReload,
291
+ addTask: (task, options) => {
292
+ return addTask(task, options);
293
+ },
294
+ close: () => new Promise((resolve, reject) => {
295
+ socketServer.clients.forEach((socket) => socket.terminate());
296
+ socketServer.close((err) => (err ? reject(err) : resolve()));
297
+ server.close();
298
+ }),
299
+ };
300
+ }
301
+ //# sourceMappingURL=startApiServer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startApiServer.js","sourceRoot":"","sources":["../src/startApiServer.ts"],"names":[],"mappings":"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","sourcesContent":["import os from 'os';\nimport getPort from 'get-port';\nimport path from 'path';\nimport http from 'http';\nimport https from 'https';\nimport WebSocket, { WebSocketServer } from 'ws';\nimport { createDataBus } from '@ms-cloudpack/data-bus';\nimport { execSync } from 'child_process';\nimport { TaskRunner } from './TaskRunner.js';\nimport { getCachePath } from './common/getCachePath.js';\nimport fsExtra from 'fs-extra';\nimport fs from 'fs';\nimport { startWatcher } from './startWatcher.js';\nimport type {\n ApiServer,\n CloudpackConfig,\n Session,\n SessionStats,\n TaskDescription,\n TaskEndDescription,\n TaskStartDescription,\n} from './types.js';\nimport { PackageDefinitions } from '@ms-cloudpack/package-utilities';\nimport { createPackageOverrideTransform } from './common/createPackageOverrideTransform.js';\nimport { configTemplate, getConfigPath, readConfigSync, writeConfigSync } from '@ms-cloudpack/config';\nimport { addOverride } from './addOverride.js';\nimport { validateOverride } from './validateOverride.js';\nimport { parseHttpsConfig } from '@ms-cloudpack/create-express-app';\nimport type { Span } from '@ms-cloudpack/telemetry';\n\n/**\n * The api server handles the data pub/sub (for things like reporting build status)\n * and hosting the dashboard verb backend (for executing actions like loading vscode,\n * changing dependencies, connecting new projects to the running session, etc.)\n *\n * Each time `cloudpack start` is run, an api server will start up for that session. The\n * hosted web page will have a \"cloudpack-api-server\" header which points\n * to the websocket server.\n *\n * The bundle server will call the api server to publish build status updates.\n *\n * The app server will burn the api server url on the host html, so that client-side\n * javascript can connect and subscribe to page changes.\n */\nexport async function startApiServer({\n session,\n config,\n rootSpan,\n}: {\n session: Session;\n config: CloudpackConfig;\n rootSpan: Span | undefined;\n}): Promise<ApiServer> {\n const host = config?.devServer?.domain || 'localhost';\n const port = await getPort({ port: [9890, 9891, 9892, 9893] });\n const bus = createDataBus();\n const sessionStats: SessionStats = {\n status: 'idle',\n\n remainingTasks: 0,\n totalTasks: 0,\n\n totalErrors: 0,\n totalWarnings: 0,\n };\n\n const allTasks: Map<string, TaskDescription> = new Map();\n const taskRunner = new TaskRunner();\n\n function reportStart(taskStart: TaskStartDescription): void {\n const previousResult = allTasks.get(taskStart.id);\n const now = new Date().getTime();\n\n if (!previousResult) {\n sessionStats.totalTasks++;\n }\n\n if (previousResult?.status === 'complete') {\n sessionStats.totalErrors -= previousResult.errors?.length || 0;\n sessionStats.totalWarnings -= previousResult.warnings?.length || 0;\n }\n\n sessionStats.remainingTasks++;\n sessionStats.status = 'pending';\n\n allTasks.set(taskStart.id, {\n ...taskStart,\n status: 'pending',\n startTime: now,\n });\n\n bus.publish([session.id, 'status'], sessionStats);\n bus.publish([session.id, 'status-details'], { tasks: Array.from(allTasks.values()) });\n }\n\n function reportEnd(taskEnd: TaskEndDescription): void {\n const previousResult = allTasks.get(taskEnd.id);\n const now = new Date().getTime();\n\n if (previousResult) {\n sessionStats.remainingTasks--;\n sessionStats.totalErrors += taskEnd.errors?.length || 0;\n sessionStats.totalWarnings += taskEnd.warnings?.length || 0;\n\n allTasks.set(taskEnd.id, {\n ...previousResult,\n ...taskEnd,\n status: 'complete',\n durationMilliseconds: now - previousResult.startTime,\n lastUpdated: now,\n });\n\n if (sessionStats.remainingTasks === 0) {\n sessionStats.status = 'idle';\n }\n\n bus.publish([session.id, 'status'], sessionStats);\n bus.publish([session.id, 'status-details'], { tasks: Array.from(allTasks.values()) });\n }\n }\n\n bus.publish([session.id, 'status'], sessionStats);\n\n const addTask: ApiServer['addTask'] = (task, options) => {\n if (task.watch) {\n options = options || {};\n options.watch = () => {\n watcher.watch(task, { onSuccess: notifyReload });\n };\n }\n\n return taskRunner.add(\n {\n ...task,\n execute: async () => {\n reportStart({\n ...task.getStartDescription?.(),\n id: task.id,\n });\n\n let taskResult;\n\n try {\n taskResult = await task.execute();\n } catch (err) {\n // no-op\n }\n\n reportEnd({\n ...task.getEndDescription?.(taskResult),\n id: task.id,\n } as TaskEndDescription);\n\n return taskResult;\n },\n },\n options,\n );\n };\n\n const watcher = startWatcher(addTask);\n\n // Create a node http(s) server.\n let server: http.Server | https.Server;\n const httpsConfig = config?.devServer?.https;\n const protocol = httpsConfig ? 'wss' : 'ws';\n\n if (httpsConfig) {\n const { key, cert, passphrase } = parseHttpsConfig(httpsConfig);\n\n if (!key || !cert) {\n console.error('Invalid https config in cloudpack.config.js. Make sure key and cert are valid.');\n\n throw new Error('Invalid https config.');\n }\n\n server = https\n .createServer(\n {\n key,\n cert,\n passphrase,\n },\n () => {\n /* no-op */\n },\n )\n .listen(port, host);\n } else {\n server = http.createServer().listen(port, host);\n }\n\n // Create a websocket server which listens for subscriptions and verb requests,\n // and broadcasts the data to all connected clients.\n const socketServer = new WebSocketServer({ server }, () => {\n // console.log(`ApiServer: Started on port ${port}`);\n }).on('connection', (socket: WebSocket) => {\n const disposables = new Map<number, () => void>();\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n function sendMessage(message: any) {\n socket.send(JSON.stringify(message, null, 2));\n }\n\n socket.send(\n JSON.stringify({\n type: 'sequence',\n sequence: session.sequence,\n }),\n );\n\n socket\n .on('message', (rawData) => {\n try {\n const request = JSON.parse(rawData.toString()) as {\n type: string;\n requestId?: string;\n data: Record<string, unknown>;\n };\n\n switch (request.type) {\n case 'subscribe': {\n const { path: subscriptionPath, id } = request.data;\n\n disposables.set(\n id as number,\n bus.subscribe(\n subscriptionPath as string[],\n // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment\n (data: any) => sendMessage({ type: 'notify', id, path: subscriptionPath, data }),\n ),\n );\n\n break;\n }\n\n case 'unsubscribe': {\n const id = request.data.id as number | undefined;\n if (id) {\n const dispose = disposables.get(id);\n\n if (dispose) {\n dispose();\n disposables.delete(id);\n }\n }\n\n break;\n }\n\n case 'openSource': {\n const { rootPath, relativePath = '', line, column } = request.data;\n const fullPath = path.join(rootPath as string, relativePath as string);\n\n console.log(`Opening VSCode with path: ${fullPath}`);\n execSync(`code --goto ${fullPath}:${line}:${column}`, { cwd: process.cwd() });\n break;\n }\n\n case 'open': {\n const { rootPath, relativePath = '' } = request.data;\n const fullPath = path.join(rootPath as string, relativePath as string);\n\n console.log(`Opening path: ${fullPath}`);\n const openCommand = os.platform() == 'win32' ? 'start' : 'open';\n\n execSync(`${openCommand} ${fullPath}`, { cwd: process.cwd() });\n break;\n }\n\n case 'editConfig': {\n const fullPath = path.resolve(getConfigPath());\n // Check if file exists\n if (!fs.existsSync(fullPath)) {\n // Create file with default config\n writeConfigSync(configTemplate);\n }\n console.log('Opening config file');\n console.log(`Opening VSCode with path: ${fullPath}`);\n execSync(`code --goto ${fullPath}:${1}`, { cwd: process.cwd() });\n break;\n }\n\n case 'addOverride': {\n // TODO: Send filename from request data to findResolveMapEntry when chromium Issue 1371551 is fixed.\n const { packageName, importPath } = request.data as Record<string, string>;\n (async () => {\n await addOverride({ session, packageName, importPath });\n session.incrementSessionVersion();\n notifyReload();\n })().catch(() => {\n // no-op\n });\n break;\n }\n\n case 'validateOverride': {\n // TODO: Get filename from request data when chromium Issue 1371551 is fixed.\n const { requestId, data } = request as { requestId: string; data: Record<string, string> };\n const { packageName, importPath } = data;\n (async () => {\n const fixable = await validateOverride({ session, packageName, importPath });\n sendMessage({ type: 'validateOverride', requestId, data: { fixable } });\n })().catch(() => {\n // no-op\n });\n break;\n }\n\n case 'restartAllTasks': {\n console.log('ApiServer: Restarting all tasks');\n\n // Clear entire local cache.\n fsExtra.emptyDirSync(getCachePath());\n\n // All tasks can be removed as notifyReload()\n // will cause the client to add the tasks again.\n allTasks.forEach((_, id) => {\n taskRunner.remove(id);\n });\n\n // Parse config if it exists.\n const cloudpackConfig = readConfigSync();\n\n // Initialize package overrides.\n PackageDefinitions.getInstance().registerTransform(createPackageOverrideTransform(cloudpackConfig));\n\n // Update session version.\n session.incrementSessionVersion();\n\n notifyReload();\n break;\n }\n\n case 'restartTask': {\n const { id, inputPath } = request.data as Record<string, string>;\n console.log(`ApiServer: Restarting task ${id}`);\n taskRunner.remove(id);\n session.incrementTargetVersion(inputPath);\n notifyReload();\n break;\n }\n\n case 'reportMetric': {\n const { metric, value } = request.data as { metric: string; value: number };\n console.debug(`ApiServer: Received metric: ${metric}, value: ${value}`);\n rootSpan?.addEvent(metric, value);\n break;\n }\n }\n } catch (err) {\n console.error('ApiServer: error handling message');\n console.error('Error:', err);\n console.error('Message:', rawData.toString());\n }\n })\n .on('close', () => {\n for (const d of disposables.values()) {\n d();\n }\n });\n });\n\n const notifyReload = () => {\n session.sequence++;\n socketServer.clients.forEach((socket: WebSocket) => {\n if (socket.readyState === WebSocket.OPEN) {\n socket.send(\n JSON.stringify({\n type: 'reload',\n data: { sessionId: session.id },\n }),\n );\n }\n });\n };\n\n return {\n url: `${protocol}://${host}:${port}`,\n port,\n\n notifyReload,\n\n addTask: (task, options) => {\n return addTask(task, options);\n },\n\n close: () =>\n new Promise<void>((resolve, reject) => {\n socketServer.clients.forEach((socket: WebSocket) => socket.terminate());\n socketServer.close((err) => (err ? reject(err) : resolve()));\n server.close();\n }),\n };\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import type { ApiServer, Task } from './types.js';
2
+ export declare function startWatcher(addTask: ApiServer['addTask']): {
3
+ watch: <TReturn>(task: Task<TReturn>, options?: {
4
+ onSuccess?: () => void;
5
+ onDispose?: () => void;
6
+ }) => void;
7
+ };
@@ -0,0 +1,51 @@
1
+ import chokidar from 'chokidar';
2
+ export function startWatcher(addTask) {
3
+ const watched = new Set();
4
+ return {
5
+ watch: (task, options) => {
6
+ const { id, name, dir } = task;
7
+ // Have a single watcher per bundle request.
8
+ if (watched.has(id) || !dir) {
9
+ return;
10
+ }
11
+ watched.add(id);
12
+ console.debug(`Initializing file watcher for ${name}`);
13
+ // TODO: This is a bit of a hack. We should be able to use the watch API, but it doesn't seem to work.
14
+ const watcher = chokidar.watch(dir).on('change', (event) => {
15
+ console.debug(`Detected change "${event}", rebuilding "${name}"`);
16
+ (async () => {
17
+ // Start tracking work.
18
+ let rebuilt = false;
19
+ try {
20
+ await addTask(task, { rerun: true });
21
+ rebuilt = true;
22
+ }
23
+ catch (err) {
24
+ console.error(`Error rebuilding "${name}":`, err?.stack || err);
25
+ }
26
+ const errors = task.getErrors?.();
27
+ if (rebuilt && !errors?.length) {
28
+ console.debug(`Notifying rebuild: ${name}`);
29
+ options?.onSuccess?.();
30
+ }
31
+ else if (errors) {
32
+ console.debug(`${name} ${errors.length} errors`);
33
+ }
34
+ })().catch(() => {
35
+ // no-op
36
+ });
37
+ });
38
+ task.dispose = async () => {
39
+ try {
40
+ await watcher.close();
41
+ }
42
+ catch {
43
+ // ignore
44
+ }
45
+ task.onDispose?.();
46
+ watched.delete(id);
47
+ };
48
+ },
49
+ };
50
+ }
51
+ //# sourceMappingURL=startWatcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startWatcher.js","sourceRoot":"","sources":["../src/startWatcher.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAC;AAGhC,MAAM,UAAU,YAAY,CAAC,OAA6B;IACxD,MAAM,OAAO,GAAgB,IAAI,GAAG,EAAE,CAAC;IAEvC,OAAO;QACL,KAAK,EAAE,CAAU,IAAmB,EAAE,OAA4D,EAAE,EAAE;YACpG,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;YAC/B,4CAA4C;YAC5C,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;gBAC3B,OAAO;aACR;YAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAEhB,OAAO,CAAC,KAAK,CAAC,iCAAiC,IAAI,EAAE,CAAC,CAAC;YAEvD,sGAAsG;YACtG,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAa,EAAE,EAAE;gBACjE,OAAO,CAAC,KAAK,CAAC,oBAAoB,KAAK,kBAAkB,IAAI,GAAG,CAAC,CAAC;gBAElE,CAAC,KAAK,IAAI,EAAE;oBACV,uBAAuB;oBACvB,IAAI,OAAO,GAAG,KAAK,CAAC;oBACpB,IAAI;wBACF,MAAM,OAAO,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;wBACrC,OAAO,GAAG,IAAI,CAAC;qBAChB;oBAAC,OAAO,GAAG,EAAE;wBACZ,OAAO,CAAC,KAAK,CAAC,qBAAqB,IAAI,IAAI,EAAG,GAAa,EAAE,KAAK,IAAI,GAAG,CAAC,CAAC;qBAC5E;oBACD,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;oBAClC,IAAI,OAAO,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE;wBAC9B,OAAO,CAAC,KAAK,CAAC,sBAAsB,IAAI,EAAE,CAAC,CAAC;wBAC5C,OAAO,EAAE,SAAS,EAAE,EAAE,CAAC;qBACxB;yBAAM,IAAI,MAAM,EAAE;wBACjB,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC,MAAM,SAAS,CAAC,CAAC;qBAClD;gBACH,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;oBACd,QAAQ;gBACV,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,OAAO,GAAG,KAAK,IAAI,EAAE;gBACxB,IAAI;oBACF,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;iBACvB;gBAAC,MAAM;oBACN,SAAS;iBACV;gBAED,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;gBAEnB,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACrB,CAAC,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import chokidar from 'chokidar';\nimport type { ApiServer, Task } from './types.js';\n\nexport function startWatcher(addTask: ApiServer['addTask']) {\n const watched: Set<string> = new Set();\n\n return {\n watch: <TReturn>(task: Task<TReturn>, options?: { onSuccess?: () => void; onDispose?: () => void }) => {\n const { id, name, dir } = task;\n // Have a single watcher per bundle request.\n if (watched.has(id) || !dir) {\n return;\n }\n\n watched.add(id);\n\n console.debug(`Initializing file watcher for ${name}`);\n\n // TODO: This is a bit of a hack. We should be able to use the watch API, but it doesn't seem to work.\n const watcher = chokidar.watch(dir).on('change', (event: string) => {\n console.debug(`Detected change \"${event}\", rebuilding \"${name}\"`);\n\n (async () => {\n // Start tracking work.\n let rebuilt = false;\n try {\n await addTask(task, { rerun: true });\n rebuilt = true;\n } catch (err) {\n console.error(`Error rebuilding \"${name}\":`, (err as Error)?.stack || err);\n }\n const errors = task.getErrors?.();\n if (rebuilt && !errors?.length) {\n console.debug(`Notifying rebuild: ${name}`);\n options?.onSuccess?.();\n } else if (errors) {\n console.debug(`${name} ${errors.length} errors`);\n }\n })().catch(() => {\n // no-op\n });\n });\n\n task.dispose = async () => {\n try {\n await watcher.close();\n } catch {\n // ignore\n }\n\n task.onDispose?.();\n\n watched.delete(id);\n };\n },\n };\n}\n"]}
@@ -0,0 +1,11 @@
1
+ // This file is read by tools that parse documentation comments conforming to the TSDoc standard.
2
+ // It should be published with your NPM package. It should not be tracked by Git.
3
+ {
4
+ "tsdocVersion": "0.12",
5
+ "toolPackages": [
6
+ {
7
+ "packageName": "@microsoft/api-extractor",
8
+ "packageVersion": "7.34.4"
9
+ }
10
+ ]
11
+ }
package/lib/types.d.ts ADDED
@@ -0,0 +1,234 @@
1
+ import type { ImportMap, ResolveMap } from '@ms-cloudpack/package-utilities';
2
+ import type { PackageJson } from '@ms-cloudpack/bundler-types';
3
+ import type { HttpsConfig } from '@ms-cloudpack/create-express-app';
4
+ export interface Session {
5
+ /**
6
+ * The session id, used for identifying
7
+ */
8
+ id: string;
9
+ /**
10
+ * The primary application path to monitor.
11
+ */
12
+ appPath: string;
13
+ /**
14
+ * The type of session controls how the code is served via start.
15
+ * Web apps require app server and asset hosting, while libraries
16
+ * only need a bundle service.
17
+ */
18
+ type: 'web-app' | 'library';
19
+ /**
20
+ * The mode of the session controls how the code is bundled. Library
21
+ * mode bundles each package in isolation, while development and
22
+ * production modes bundle the full app graph together. Library
23
+ * mode is fastest but packages must be complaint for loading in
24
+ * the browser.
25
+ */
26
+ mode: 'library' | 'development' | 'production';
27
+ /**
28
+ * Used to resolve packages in the dependency graph.
29
+ */
30
+ resolveMap: ResolveMap;
31
+ /**
32
+ * Reload sequence to ensure that the client is always
33
+ * up to date with the latest changes.
34
+ */
35
+ sequence: number;
36
+ /**
37
+ * Get session version.
38
+ * Used to force a hard refresh on the client.
39
+ */
40
+ getSessionVersion: () => number;
41
+ /**
42
+ * Used to increment the session version.
43
+ * If version is undefined, the version will be incremented by 1.
44
+ * If version is greater than the current version, the version will be set to the new value.
45
+ */
46
+ incrementSessionVersion: (version?: number) => void;
47
+ /**
48
+ * The name of the project folder.
49
+ * Used to differentiate between multiple projects.
50
+ */
51
+ projectName: string;
52
+ /**
53
+ * The version of each target.
54
+ * Used to force a client cache refresh on a single target.
55
+ */
56
+ targetVersions: Record<string, number>;
57
+ /**
58
+ * Increments the version for the given target.
59
+ */
60
+ incrementTargetVersion: (inputPath: string) => void;
61
+ /**
62
+ * The import map for the session.
63
+ */
64
+ getImportMap: () => ImportMap | undefined;
65
+ setImportMap: (newImportMap: ImportMap) => ImportMap;
66
+ }
67
+ export interface SessionStats {
68
+ status: 'idle' | 'pending';
69
+ remainingTasks: number;
70
+ totalTasks: number;
71
+ totalErrors: number;
72
+ totalWarnings: number;
73
+ }
74
+ export interface ApiServer {
75
+ url: string;
76
+ port: number;
77
+ notifyReload: () => void;
78
+ addTask: <TReturn>(task: Task<TReturn>, options?: TaskOptions) => Promise<TReturn>;
79
+ close: () => Promise<void>;
80
+ }
81
+ export interface TaskError {
82
+ message: string;
83
+ pluginName: string;
84
+ stack: string;
85
+ fileLocation: string;
86
+ lineNumber: number;
87
+ columnNumber: number;
88
+ }
89
+ export interface TaskStartDescription {
90
+ id: string;
91
+ name?: string;
92
+ inputPath?: string;
93
+ outputPath?: string;
94
+ }
95
+ export interface TaskEndDescription {
96
+ id: string;
97
+ errors?: TaskError[];
98
+ warnings?: TaskError[];
99
+ }
100
+ export interface TaskDescription extends TaskStartDescription, TaskEndDescription {
101
+ status: 'pending' | 'complete';
102
+ startTime: number;
103
+ durationMilliseconds?: number;
104
+ lastUpdated?: number;
105
+ }
106
+ export interface Task<TReturn> {
107
+ name: string;
108
+ id: string;
109
+ dir?: string;
110
+ execute: () => Promise<TReturn | undefined>;
111
+ clear?: () => void;
112
+ watch?: boolean;
113
+ dispose?: () => Promise<void>;
114
+ onDispose?: () => void;
115
+ getErrors?: () => TaskError[] | undefined;
116
+ getStartDescription?: () => TaskStartDescription;
117
+ getEndDescription?: (result: TReturn | undefined) => TaskEndDescription;
118
+ }
119
+ export interface TaskOptions {
120
+ rerun?: boolean;
121
+ watch?: () => void;
122
+ }
123
+ export interface PackageOverride {
124
+ name: string;
125
+ versionRequirement: string;
126
+ isInternal?: boolean;
127
+ overrides: PackageJson;
128
+ }
129
+ export interface Route {
130
+ /**
131
+ * The url path part to match. Start paths with a leading slash, e.g. `'/my-route'`. You can also
132
+ * use wildcards (`*`), e.g. `'/my-route/*'` to match all paths starting with `'/my-route'`.
133
+ */
134
+ match: string;
135
+ /**
136
+ * The path relative to the app root to static assets. If this is provided, renderScript/exportEntry
137
+ * will be ignored.
138
+ */
139
+ staticPath?: string;
140
+ /**
141
+ * Path to a .js or .html file used for rendering the route. If a script is provided, it must
142
+ * export a default function implementing `CreateHtmlFunction` defined in `@ms-cloudpack/cli`.
143
+ */
144
+ renderScript?: string;
145
+ /**
146
+ * The key in the exports map which represents the entry point app script for this route.
147
+ * Default is '.', meaning the main package export key.
148
+ */
149
+ exportEntry?: string;
150
+ }
151
+ export interface DevServer {
152
+ /**
153
+ * The domain name of the server.
154
+ */
155
+ domain?: string;
156
+ /**
157
+ * The ports to be used by the server.
158
+ * It can be a number or an array of numbers.
159
+ * If ports are provided, an error will be thrown if none are available.
160
+ * If no ports are provided, the server will attempt to find an available port.
161
+ */
162
+ port?: number | number[];
163
+ /**
164
+ * The config settings for the https server.
165
+ * Paths to files are accepted for 'ca', 'cert', 'key', and 'pfx' settings.
166
+ * Everything else is passed directly to the https.createServer() method.
167
+ * If not provided, the server will use http.
168
+ */
169
+ https?: HttpsConfig;
170
+ /**
171
+ * If provided, will override where the publicDir is located, relative to the app package folder.
172
+ * Defaults to `./public`. The same thing can be achieved with `routes`, using route: '*' and
173
+ * staticPath: './public', so the `publicPath` setting may be removed in the future.
174
+ */
175
+ publicPath?: string;
176
+ /**
177
+ * If provided, will register routes with the dev server to host various pages.
178
+ */
179
+ routes?: Route[];
180
+ }
181
+ export interface TelemetryConfig {
182
+ /**
183
+ * Application Insights instrumentation key.
184
+ */
185
+ instrumentationKey: string;
186
+ }
187
+ /**
188
+ * Per-application configuration for Cloudpack.
189
+ */
190
+ export interface CloudpackConfig {
191
+ /**
192
+ * An array of PackageOverride objects, used to provide overrides for packages without modifying their
193
+ * definitions directly. This is primarily used for providing exports maps for packages that don't have them.
194
+ */
195
+ packageOverrides?: PackageOverride[];
196
+ /**
197
+ * A set of options for configuring how the dev server works.
198
+ */
199
+ devServer?: DevServer;
200
+ /**
201
+ * A set of options for configuring telemetry
202
+ */
203
+ telemetry?: TelemetryConfig;
204
+ }
205
+ /**
206
+ * The input options for a createHtml custom server render function.
207
+ */
208
+ export type CreateHtmlOptions = {
209
+ session: Session;
210
+ route: Route;
211
+ baseUrl: string;
212
+ definition: PackageJson;
213
+ importMap: ImportMap;
214
+ overlayScript: string | undefined;
215
+ entryScript: string | undefined;
216
+ inlineScripts: string[];
217
+ };
218
+ /**
219
+ * The expected result of the createHtml custom server render function.
220
+ */
221
+ export type CreateHtmlResult = string | {
222
+ html: string;
223
+ statusCode: number;
224
+ };
225
+ /**
226
+ * A custom server render function that can be used to override the default HTML response.
227
+ */
228
+ export type CreateHtmlFunction = (options: CreateHtmlOptions) => Promise<CreateHtmlResult> | CreateHtmlResult;
229
+ /**
230
+ * The expected shape of the createHtml custom server render function script.
231
+ */
232
+ export type CreateHtmlScript = {
233
+ default: CreateHtmlFunction;
234
+ };
package/lib/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ImportMap, ResolveMap } from '@ms-cloudpack/package-utilities';\nimport type { PackageJson } from '@ms-cloudpack/bundler-types';\nimport type { HttpsConfig } from '@ms-cloudpack/create-express-app';\n\nexport interface Session {\n /**\n * The session id, used for identifying\n */\n id: string;\n\n /**\n * The primary application path to monitor.\n */\n appPath: string;\n\n /**\n * The type of session controls how the code is served via start.\n * Web apps require app server and asset hosting, while libraries\n * only need a bundle service.\n */\n type: 'web-app' | 'library';\n\n /**\n * The mode of the session controls how the code is bundled. Library\n * mode bundles each package in isolation, while development and\n * production modes bundle the full app graph together. Library\n * mode is fastest but packages must be complaint for loading in\n * the browser.\n */\n mode: 'library' | 'development' | 'production';\n\n /**\n * Used to resolve packages in the dependency graph.\n */\n resolveMap: ResolveMap;\n\n /**\n * Reload sequence to ensure that the client is always\n * up to date with the latest changes.\n */\n sequence: number;\n\n /**\n * Get session version.\n * Used to force a hard refresh on the client.\n */\n getSessionVersion: () => number;\n\n /**\n * Used to increment the session version.\n * If version is undefined, the version will be incremented by 1.\n * If version is greater than the current version, the version will be set to the new value.\n */\n incrementSessionVersion: (version?: number) => void;\n\n /**\n * The name of the project folder.\n * Used to differentiate between multiple projects.\n */\n projectName: string;\n\n /**\n * The version of each target.\n * Used to force a client cache refresh on a single target.\n */\n targetVersions: Record<string, number>;\n\n /**\n * Increments the version for the given target.\n */\n incrementTargetVersion: (inputPath: string) => void;\n\n /**\n * The import map for the session.\n */\n getImportMap: () => ImportMap | undefined;\n\n setImportMap: (newImportMap: ImportMap) => ImportMap;\n}\n\nexport interface SessionStats {\n status: 'idle' | 'pending';\n\n remainingTasks: number;\n totalTasks: number;\n\n totalErrors: number;\n totalWarnings: number;\n}\n\nexport interface ApiServer {\n url: string;\n port: number;\n\n notifyReload: () => void;\n\n addTask: <TReturn>(task: Task<TReturn>, options?: TaskOptions) => Promise<TReturn>;\n\n close: () => Promise<void>;\n}\n\nexport interface TaskError {\n message: string;\n pluginName: string;\n stack: string;\n fileLocation: string;\n lineNumber: number;\n columnNumber: number;\n}\n\nexport interface TaskStartDescription {\n id: string;\n name?: string;\n inputPath?: string;\n outputPath?: string;\n}\n\nexport interface TaskEndDescription {\n id: string;\n errors?: TaskError[];\n warnings?: TaskError[];\n}\n\nexport interface TaskDescription extends TaskStartDescription, TaskEndDescription {\n status: 'pending' | 'complete';\n startTime: number;\n durationMilliseconds?: number;\n lastUpdated?: number;\n}\n\nexport interface Task<TReturn> {\n name: string;\n id: string;\n dir?: string;\n execute: () => Promise<TReturn | undefined>;\n clear?: () => void;\n watch?: boolean;\n dispose?: () => Promise<void>;\n onDispose?: () => void;\n getErrors?: () => TaskError[] | undefined;\n getStartDescription?: () => TaskStartDescription;\n getEndDescription?: (result: TReturn | undefined) => TaskEndDescription;\n}\n\nexport interface TaskOptions {\n rerun?: boolean;\n watch?: () => void;\n}\n\nexport interface PackageOverride {\n name: string;\n versionRequirement: string;\n isInternal?: boolean;\n overrides: PackageJson;\n}\n\nexport interface Route {\n /**\n * The url path part to match. Start paths with a leading slash, e.g. `'/my-route'`. You can also\n * use wildcards (`*`), e.g. `'/my-route/*'` to match all paths starting with `'/my-route'`.\n */\n match: string;\n\n /**\n * The path relative to the app root to static assets. If this is provided, renderScript/exportEntry\n * will be ignored.\n */\n staticPath?: string;\n\n /**\n * Path to a .js or .html file used for rendering the route. If a script is provided, it must\n * export a default function implementing `CreateHtmlFunction` defined in `@ms-cloudpack/cli`.\n */\n renderScript?: string;\n\n /**\n * The key in the exports map which represents the entry point app script for this route.\n * Default is '.', meaning the main package export key.\n */\n exportEntry?: string;\n}\n\nexport interface DevServer {\n /**\n * The domain name of the server.\n */\n domain?: string;\n\n /**\n * The ports to be used by the server.\n * It can be a number or an array of numbers.\n * If ports are provided, an error will be thrown if none are available.\n * If no ports are provided, the server will attempt to find an available port.\n */\n port?: number | number[];\n\n /**\n * The config settings for the https server.\n * Paths to files are accepted for 'ca', 'cert', 'key', and 'pfx' settings.\n * Everything else is passed directly to the https.createServer() method.\n * If not provided, the server will use http.\n */\n https?: HttpsConfig;\n\n /**\n * If provided, will override where the publicDir is located, relative to the app package folder.\n * Defaults to `./public`. The same thing can be achieved with `routes`, using route: '*' and\n * staticPath: './public', so the `publicPath` setting may be removed in the future.\n */\n publicPath?: string;\n\n /**\n * If provided, will register routes with the dev server to host various pages.\n */\n routes?: Route[];\n}\n\nexport interface TelemetryConfig {\n /**\n * Application Insights instrumentation key.\n */\n instrumentationKey: string;\n}\n\n/**\n * Per-application configuration for Cloudpack.\n */\nexport interface CloudpackConfig {\n /**\n * An array of PackageOverride objects, used to provide overrides for packages without modifying their\n * definitions directly. This is primarily used for providing exports maps for packages that don't have them.\n */\n packageOverrides?: PackageOverride[];\n\n /**\n * A set of options for configuring how the dev server works.\n */\n devServer?: DevServer;\n\n /**\n * A set of options for configuring telemetry\n */\n telemetry?: TelemetryConfig;\n}\n\n/**\n * The input options for a createHtml custom server render function.\n */\nexport type CreateHtmlOptions = {\n session: Session;\n route: Route;\n baseUrl: string;\n definition: PackageJson;\n importMap: ImportMap;\n overlayScript: string | undefined;\n entryScript: string | undefined;\n inlineScripts: string[];\n};\n\n/**\n * The expected result of the createHtml custom server render function.\n */\nexport type CreateHtmlResult =\n | string\n | {\n html: string;\n statusCode: number;\n };\n\n/**\n * A custom server render function that can be used to override the default HTML response.\n */\nexport type CreateHtmlFunction = (options: CreateHtmlOptions) => Promise<CreateHtmlResult> | CreateHtmlResult;\n\n/**\n * The expected shape of the createHtml custom server render function script.\n */\nexport type CreateHtmlScript = { default: CreateHtmlFunction };\n"]}
@@ -0,0 +1,7 @@
1
+ import type { Session } from './types.js';
2
+ export declare function validateOverride({ session, packageName, importPath, filename, }: {
3
+ session: Session;
4
+ packageName: string;
5
+ importPath: string;
6
+ filename?: string;
7
+ }): Promise<boolean>;
@@ -0,0 +1,28 @@
1
+ import { findResolveMapEntry, findFileInPackage } from '@ms-cloudpack/package-utilities';
2
+ import { slash } from '@ms-cloudpack/path-string-parsing';
3
+ import { parseRequestInfo } from './parseRequestInfo.js';
4
+ export async function validateOverride({ session, packageName, importPath, filename, }) {
5
+ let definition = undefined;
6
+ if (filename) {
7
+ const requestPath = slash(new URL(filename).pathname);
8
+ const { packageName: name, version } = parseRequestInfo(requestPath);
9
+ definition = { name, version };
10
+ }
11
+ const entry = findResolveMapEntry({
12
+ packageName,
13
+ resolveMap: session.resolveMap,
14
+ definition,
15
+ });
16
+ if (entry === undefined) {
17
+ return false;
18
+ }
19
+ const { filePath } = await findFileInPackage({
20
+ packagePath: entry.path,
21
+ filePath: importPath,
22
+ });
23
+ if (filePath) {
24
+ return true;
25
+ }
26
+ return false;
27
+ }
28
+ //# sourceMappingURL=validateOverride.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateOverride.js","sourceRoot":"","sources":["../src/validateOverride.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACzF,OAAO,EAAE,KAAK,EAAE,MAAM,mCAAmC,CAAC;AAE1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,EACrC,OAAO,EACP,WAAW,EACX,UAAU,EACV,QAAQ,GAMT;IACC,IAAI,UAAU,GAAG,SAAS,CAAC;IAE3B,IAAI,QAAQ,EAAE;QACZ,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC;QACtD,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;QACrE,UAAU,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;KAChC;IAED,MAAM,KAAK,GAAG,mBAAmB,CAAC;QAChC,WAAW;QACX,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,UAAU;KACX,CAAC,CAAC;IAEH,IAAI,KAAK,KAAK,SAAS,EAAE;QACvB,OAAO,KAAK,CAAC;KACd;IAED,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,iBAAiB,CAAC;QAC3C,WAAW,EAAE,KAAK,CAAC,IAAI;QACvB,QAAQ,EAAE,UAAU;KACrB,CAAC,CAAC;IAEH,IAAI,QAAQ,EAAE;QACZ,OAAO,IAAI,CAAC;KACb;IACD,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["import { findResolveMapEntry, findFileInPackage } from '@ms-cloudpack/package-utilities';\nimport { slash } from '@ms-cloudpack/path-string-parsing';\nimport type { Session } from './types.js';\nimport { parseRequestInfo } from './parseRequestInfo.js';\n\nexport async function validateOverride({\n session,\n packageName,\n importPath,\n filename,\n}: {\n session: Session;\n packageName: string;\n importPath: string;\n filename?: string;\n}) {\n let definition = undefined;\n\n if (filename) {\n const requestPath = slash(new URL(filename).pathname);\n const { packageName: name, version } = parseRequestInfo(requestPath);\n definition = { name, version };\n }\n\n const entry = findResolveMapEntry({\n packageName,\n resolveMap: session.resolveMap,\n definition,\n });\n\n if (entry === undefined) {\n return false;\n }\n\n const { filePath } = await findFileInPackage({\n packagePath: entry.path,\n filePath: importPath,\n });\n\n if (filePath) {\n return true;\n }\n return false;\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@ms-cloudpack/api-server",
3
+ "version": "0.2.0",
4
+ "description": "An implementation of the API server that does interacts with a task scheduler.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "types": "./lib/index.d.ts",
8
+ "sideEffects": false,
9
+ "exports": {
10
+ ".": {
11
+ "types": "./lib/index.d.ts",
12
+ "import": "./lib/index.js"
13
+ }
14
+ },
15
+ "dependencies": {
16
+ "@ms-cloudpack/bundler-types": "^0.11.0",
17
+ "@ms-cloudpack/config": "^0.2.0",
18
+ "@ms-cloudpack/create-express-app": "^1.3.0",
19
+ "@ms-cloudpack/data-bus": "^0.1.1",
20
+ "@ms-cloudpack/package-utilities": "^2.3.10",
21
+ "@ms-cloudpack/path-string-parsing": "^1.0.1",
22
+ "@ms-cloudpack/telemetry": "^0.2.2",
23
+ "chokidar": "^3.5.3",
24
+ "fs-extra": "^11.0.0",
25
+ "get-port": "^6.1.2",
26
+ "p-queue": "^7.3.0",
27
+ "semver": "^7.3.7",
28
+ "ws": "^8.9.0"
29
+ },
30
+ "devDependencies": {
31
+ "@ms-cloudpack/eslint-config-base": "*",
32
+ "@ms-cloudpack/scripts": "*",
33
+ "@ms-cloudpack/test-utilities": "*",
34
+ "@types/ws": "8.5.4"
35
+ },
36
+ "scripts": {
37
+ "api:update": "cloudpack-scripts api-update",
38
+ "api": "cloudpack-scripts api",
39
+ "build:watch": "cloudpack-scripts build-watch",
40
+ "build": "cloudpack-scripts build",
41
+ "lint:update": "cloudpack-scripts lint-update",
42
+ "lint": "cloudpack-scripts lint",
43
+ "test:update": "cloudpack-scripts test-update",
44
+ "test:watch": "cloudpack-scripts test-watch",
45
+ "test": "cloudpack-scripts test"
46
+ },
47
+ "files": [
48
+ "lib/**/!(*.test.*)"
49
+ ]
50
+ }