@guanjinke/artifact-client 1.5.1 → 1.6.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.
Files changed (72) hide show
  1. package/README.md +30 -1
  2. package/dist/cli/setup.d.ts.map +1 -1
  3. package/dist/cli/setup.js +6 -5
  4. package/dist/cli/setup.js.map +1 -1
  5. package/dist/cli/stored-client.d.ts +24 -0
  6. package/dist/cli/stored-client.d.ts.map +1 -0
  7. package/dist/cli/stored-client.js +59 -0
  8. package/dist/cli/stored-client.js.map +1 -0
  9. package/dist/cli/web.d.ts +23 -0
  10. package/dist/cli/web.d.ts.map +1 -0
  11. package/dist/cli/web.js +99 -0
  12. package/dist/cli/web.js.map +1 -0
  13. package/dist/environment/builder.d.ts.map +1 -1
  14. package/dist/environment/builder.js +21 -10
  15. package/dist/environment/builder.js.map +1 -1
  16. package/dist/environment/catalog.d.ts.map +1 -1
  17. package/dist/environment/catalog.js +18 -0
  18. package/dist/environment/catalog.js.map +1 -1
  19. package/dist/environment/config.d.ts.map +1 -1
  20. package/dist/environment/config.js +3 -1
  21. package/dist/environment/config.js.map +1 -1
  22. package/dist/environment/manager.d.ts +9 -0
  23. package/dist/environment/manager.d.ts.map +1 -1
  24. package/dist/environment/manager.js +32 -3
  25. package/dist/environment/manager.js.map +1 -1
  26. package/dist/environment/plan.d.ts +36 -0
  27. package/dist/environment/plan.d.ts.map +1 -1
  28. package/dist/environment/plan.js +52 -9
  29. package/dist/environment/plan.js.map +1 -1
  30. package/dist/environment/types.d.ts +7 -2
  31. package/dist/environment/types.d.ts.map +1 -1
  32. package/dist/environment/update.d.ts +2 -0
  33. package/dist/environment/update.d.ts.map +1 -1
  34. package/dist/environment/update.js +19 -10
  35. package/dist/environment/update.js.map +1 -1
  36. package/dist/index.d.ts +7 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +12 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/web/api.d.ts +23 -0
  41. package/dist/web/api.d.ts.map +1 -0
  42. package/dist/web/api.js +273 -0
  43. package/dist/web/api.js.map +1 -0
  44. package/dist/web/assets.d.ts +26 -0
  45. package/dist/web/assets.d.ts.map +1 -0
  46. package/dist/web/assets.js +87 -0
  47. package/dist/web/assets.js.map +1 -0
  48. package/dist/web/http.d.ts +41 -0
  49. package/dist/web/http.d.ts.map +1 -0
  50. package/dist/web/http.js +167 -0
  51. package/dist/web/http.js.map +1 -0
  52. package/dist/web/jobs.d.ts +69 -0
  53. package/dist/web/jobs.d.ts.map +1 -0
  54. package/dist/web/jobs.js +110 -0
  55. package/dist/web/jobs.js.map +1 -0
  56. package/dist/web/providers.d.ts +9 -0
  57. package/dist/web/providers.d.ts.map +1 -0
  58. package/dist/web/providers.js +17 -0
  59. package/dist/web/providers.js.map +1 -0
  60. package/dist/web/server.d.ts +41 -0
  61. package/dist/web/server.d.ts.map +1 -0
  62. package/dist/web/server.js +134 -0
  63. package/dist/web/server.js.map +1 -0
  64. package/dist/web/static/app.js +59 -0
  65. package/dist/web/static/favicon.svg +12 -0
  66. package/dist/web/static/index.html +15 -0
  67. package/dist/web/static/styles.css +1263 -0
  68. package/dist/web/types.d.ts +52 -0
  69. package/dist/web/types.d.ts.map +1 -0
  70. package/dist/web/types.js +3 -0
  71. package/dist/web/types.js.map +1 -0
  72. package/package.json +9 -2
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.assetFiles = exports.assetRoutes = void 0;
37
+ exports.defaultAssetDirectory = defaultAssetDirectory;
38
+ exports.loadAsset = loadAsset;
39
+ exports.verifyAssets = verifyAssets;
40
+ const fs = __importStar(require("fs/promises"));
41
+ const path = __importStar(require("path"));
42
+ exports.assetRoutes = {
43
+ '/': { file: 'index.html', contentType: 'text/html; charset=utf-8' },
44
+ '/index.html': { file: 'index.html', contentType: 'text/html; charset=utf-8' },
45
+ '/app.js': { file: 'app.js', contentType: 'text/javascript; charset=utf-8' },
46
+ '/styles.css': { file: 'styles.css', contentType: 'text/css; charset=utf-8' },
47
+ // The page declares this icon, so it must be served or every load logs a 404.
48
+ '/favicon.svg': { file: 'favicon.svg', contentType: 'image/svg+xml' },
49
+ };
50
+ /** Every file a complete `dist/web/static` build contains. */
51
+ exports.assetFiles = ['index.html', 'app.js', 'styles.css', 'favicon.svg'];
52
+ /** `dist/web/static` next to the compiled server. */
53
+ function defaultAssetDirectory() {
54
+ return path.join(__dirname, 'static');
55
+ }
56
+ async function loadAsset(directory, route) {
57
+ const asset = exports.assetRoutes[route];
58
+ if (!asset)
59
+ return undefined;
60
+ try {
61
+ return { body: await fs.readFile(path.join(directory, asset.file)), contentType: asset.contentType };
62
+ }
63
+ catch (error) {
64
+ if (error.code === 'ENOENT')
65
+ return undefined;
66
+ throw error;
67
+ }
68
+ }
69
+ /**
70
+ * Fail fast with an actionable message when the frontend was never built.
71
+ *
72
+ * `tsc` alone does not produce the bundle, so a server started from a
73
+ * partially built tree would otherwise serve 404s for its own page.
74
+ */
75
+ async function verifyAssets(directory) {
76
+ for (const file of exports.assetFiles) {
77
+ try {
78
+ const stat = await fs.stat(path.join(directory, file));
79
+ if (!stat.isFile())
80
+ throw new Error('not a file');
81
+ }
82
+ catch {
83
+ throw new Error(`Web interface asset missing: ${path.join(directory, file)}. Run "npm run build" to build the web bundle.`);
84
+ }
85
+ }
86
+ }
87
+ //# sourceMappingURL=assets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assets.js","sourceRoot":"","sources":["../../src/web/assets.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuBA,sDAEC;AAED,8BASC;AAQD,oCASC;AArDD,gDAAkC;AAClC,2CAA6B;AAShB,QAAA,WAAW,GAA+B;IACrD,GAAG,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,0BAA0B,EAAE;IACpE,aAAa,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,0BAA0B,EAAE;IAC9E,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,gCAAgC,EAAE;IAC5E,aAAa,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,yBAAyB,EAAE;IAC7E,8EAA8E;IAC9E,cAAc,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,WAAW,EAAE,eAAe,EAAE;CACtE,CAAC;AAEF,8DAA8D;AACjD,QAAA,UAAU,GAAG,CAAC,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,CAAU,CAAC;AAEzF,qDAAqD;AACrD,SAAgB,qBAAqB;IACnC,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AACxC,CAAC;AAEM,KAAK,UAAU,SAAS,CAAC,SAAiB,EAAE,KAAa;IAC9D,MAAM,KAAK,GAAG,mBAAW,CAAC,KAAK,CAAC,CAAC;IACjC,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,IAAI,CAAC;QACH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC;IACvG,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACzE,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,YAAY,CAAC,SAAiB;IAClD,KAAK,MAAM,IAAI,IAAI,kBAAU,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;YACvD,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC;QACpD,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,gCAAgC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,gDAAgD,CAAC,CAAC;QAC9H,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { IncomingMessage, ServerResponse } from 'http';
2
+ /** A failure that should reach the caller as an HTTP status rather than a 500. */
3
+ export declare class HttpError extends Error {
4
+ readonly status: number;
5
+ readonly allow?: string | undefined;
6
+ constructor(status: number, message: string, allow?: string | undefined);
7
+ }
8
+ /** Response headers shared by every reply; no CORS header is ever sent. */
9
+ export declare function applySecurityHeaders(response: ServerResponse): void;
10
+ export declare function sendJson(response: ServerResponse, status: number, payload: unknown, allow?: string): void;
11
+ export declare function sendText(response: ServerResponse, status: number, text: string, contentType: string, extra?: Record<string, string>): void;
12
+ /** First value of a possibly repeated header, or undefined. */
13
+ export declare function header(request: IncomingMessage, name: string): string | undefined;
14
+ /**
15
+ * Read a JSON object body.
16
+ *
17
+ * Only `application/json` is accepted, so a browser form (which can be posted
18
+ * cross-origin without a preflight) can never reach a state-changing handler.
19
+ * An oversized body is drained rather than aborted, so the caller still
20
+ * receives the 413 instead of a reset connection.
21
+ */
22
+ export declare function readJson(request: IncomingMessage, limit: number): Promise<Record<string, unknown>>;
23
+ export declare function requireString(body: Record<string, unknown>, field: string): string;
24
+ export declare function optionalString(body: Record<string, unknown>, field: string): string | undefined;
25
+ export declare function requireEnum<T extends string>(body: Record<string, unknown>, field: string, allowed: readonly T[]): T;
26
+ export declare function optionalEnum<T extends string>(body: Record<string, unknown>, field: string, allowed: readonly T[]): T | undefined;
27
+ export declare function requireOptionalInteger(body: Record<string, unknown>, field: string, min: number, max: number): number | undefined;
28
+ export declare function optionalBoolean(body: Record<string, unknown>, field: string): boolean | undefined;
29
+ /** A `string[]` field that also accepts a single whitespace/comma separated string. */
30
+ export declare function requireStringList(body: Record<string, unknown>, field: string): string[];
31
+ /**
32
+ * A `string[]` field that is passed through unsplit.
33
+ *
34
+ * Module *specs* must reach `parseModuleSpecs` verbatim: a spec carrying a local
35
+ * source (`dashboard=D:\my build\out.dll`) is split on commas only there, so a
36
+ * Windows path may contain spaces.
37
+ */
38
+ export declare function requireRawStringList(body: Record<string, unknown>, field: string): string[];
39
+ /** Same separator handling as the CLI's `--modules` values for plain module names. */
40
+ export declare function splitList(value: string): string[];
41
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/web/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAEvD,kFAAkF;AAClF,qBAAa,SAAU,SAAQ,KAAK;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM;IAAmB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM;gBAAxD,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAW,KAAK,CAAC,EAAE,MAAM,YAAA;CAC9E;AAED,2EAA2E;AAC3E,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAInE;AAED,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CASzG;AAED,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAK1I;AAED,+DAA+D;AAC/D,wBAAgB,MAAM,CAAC,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGjF;AAED;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAkBxG;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAIlF;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK/F;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,CAIpH;AAED,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,GAAG,SAAS,CAGjI;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKjI;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAKjG;AAED,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAQxF;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAK3F;AAED,sFAAsF;AACtF,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAEjD"}
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HttpError = void 0;
4
+ exports.applySecurityHeaders = applySecurityHeaders;
5
+ exports.sendJson = sendJson;
6
+ exports.sendText = sendText;
7
+ exports.header = header;
8
+ exports.readJson = readJson;
9
+ exports.requireString = requireString;
10
+ exports.optionalString = optionalString;
11
+ exports.requireEnum = requireEnum;
12
+ exports.optionalEnum = optionalEnum;
13
+ exports.requireOptionalInteger = requireOptionalInteger;
14
+ exports.optionalBoolean = optionalBoolean;
15
+ exports.requireStringList = requireStringList;
16
+ exports.requireRawStringList = requireRawStringList;
17
+ exports.splitList = splitList;
18
+ /** A failure that should reach the caller as an HTTP status rather than a 500. */
19
+ class HttpError extends Error {
20
+ status;
21
+ allow;
22
+ constructor(status, message, allow) {
23
+ super(message);
24
+ this.status = status;
25
+ this.allow = allow;
26
+ }
27
+ }
28
+ exports.HttpError = HttpError;
29
+ /** Response headers shared by every reply; no CORS header is ever sent. */
30
+ function applySecurityHeaders(response) {
31
+ response.setHeader('X-Content-Type-Options', 'nosniff');
32
+ response.setHeader('Referrer-Policy', 'no-referrer');
33
+ response.setHeader('Cache-Control', 'no-store');
34
+ }
35
+ function sendJson(response, status, payload, allow) {
36
+ const body = Buffer.from(JSON.stringify(payload, null, 2) + '\n', 'utf8');
37
+ applySecurityHeaders(response);
38
+ response.writeHead(status, {
39
+ 'Content-Type': 'application/json; charset=utf-8',
40
+ 'Content-Length': body.length,
41
+ ...(allow ? { Allow: allow } : {}),
42
+ });
43
+ response.end(body);
44
+ }
45
+ function sendText(response, status, text, contentType, extra) {
46
+ const body = Buffer.from(text, 'utf8');
47
+ applySecurityHeaders(response);
48
+ response.writeHead(status, { 'Content-Type': contentType, 'Content-Length': body.length, ...extra });
49
+ response.end(body);
50
+ }
51
+ /** First value of a possibly repeated header, or undefined. */
52
+ function header(request, name) {
53
+ const value = request.headers[name];
54
+ return Array.isArray(value) ? value[0] : value;
55
+ }
56
+ /**
57
+ * Read a JSON object body.
58
+ *
59
+ * Only `application/json` is accepted, so a browser form (which can be posted
60
+ * cross-origin without a preflight) can never reach a state-changing handler.
61
+ * An oversized body is drained rather than aborted, so the caller still
62
+ * receives the 413 instead of a reset connection.
63
+ */
64
+ async function readJson(request, limit) {
65
+ const contentType = header(request, 'content-type') ?? '';
66
+ if (!contentType.toLowerCase().startsWith('application/json'))
67
+ throw new HttpError(415, 'Expected Content-Type: application/json.');
68
+ const chunks = [];
69
+ let size = 0;
70
+ let oversized = false;
71
+ for await (const chunk of request) {
72
+ const buffer = chunk;
73
+ size += buffer.length;
74
+ if (size > limit) {
75
+ oversized = true;
76
+ continue;
77
+ }
78
+ chunks.push(buffer);
79
+ }
80
+ if (oversized)
81
+ throw new HttpError(413, `Request body exceeds ${limit} bytes.`);
82
+ if (!size)
83
+ throw new HttpError(400, 'Expected a JSON object body.');
84
+ let parsed;
85
+ try {
86
+ parsed = JSON.parse(Buffer.concat(chunks).toString('utf8'));
87
+ }
88
+ catch {
89
+ throw new HttpError(400, 'Request body is not valid JSON.');
90
+ }
91
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
92
+ throw new HttpError(400, 'Expected a JSON object body.');
93
+ return parsed;
94
+ }
95
+ function requireString(body, field) {
96
+ const value = body[field];
97
+ if (typeof value !== 'string' || value.trim() === '')
98
+ throw new HttpError(400, `"${field}" is required and must be a non-empty string.`);
99
+ return value.trim();
100
+ }
101
+ function optionalString(body, field) {
102
+ const value = body[field];
103
+ if (value === undefined || value === null)
104
+ return undefined;
105
+ if (typeof value !== 'string')
106
+ throw new HttpError(400, `"${field}" must be a string.`);
107
+ return value;
108
+ }
109
+ function requireEnum(body, field, allowed) {
110
+ const value = body[field];
111
+ if (typeof value !== 'string' || !allowed.includes(value))
112
+ throw new HttpError(400, `"${field}" must be one of: ${allowed.join(', ')}.`);
113
+ return value;
114
+ }
115
+ function optionalEnum(body, field, allowed) {
116
+ if (body[field] === undefined || body[field] === null)
117
+ return undefined;
118
+ return requireEnum(body, field, allowed);
119
+ }
120
+ function requireOptionalInteger(body, field, min, max) {
121
+ const value = body[field];
122
+ if (value === undefined || value === null)
123
+ return undefined;
124
+ if (typeof value !== 'number' || !Number.isInteger(value) || value < min || value > max)
125
+ throw new HttpError(400, `"${field}" must be an integer between ${min} and ${max}.`);
126
+ return value;
127
+ }
128
+ function optionalBoolean(body, field) {
129
+ const value = body[field];
130
+ if (value === undefined || value === null)
131
+ return undefined;
132
+ if (typeof value !== 'boolean')
133
+ throw new HttpError(400, `"${field}" must be a boolean.`);
134
+ return value;
135
+ }
136
+ /** A `string[]` field that also accepts a single whitespace/comma separated string. */
137
+ function requireStringList(body, field) {
138
+ const value = body[field];
139
+ if (typeof value === 'string')
140
+ return splitList(value);
141
+ if (Array.isArray(value)) {
142
+ if (!value.every(entry => typeof entry === 'string'))
143
+ throw new HttpError(400, `"${field}" must contain strings only.`);
144
+ return value.flatMap(entry => splitList(entry));
145
+ }
146
+ throw new HttpError(400, `"${field}" is required and must be a string or an array of strings.`);
147
+ }
148
+ /**
149
+ * A `string[]` field that is passed through unsplit.
150
+ *
151
+ * Module *specs* must reach `parseModuleSpecs` verbatim: a spec carrying a local
152
+ * source (`dashboard=D:\my build\out.dll`) is split on commas only there, so a
153
+ * Windows path may contain spaces.
154
+ */
155
+ function requireRawStringList(body, field) {
156
+ const value = body[field];
157
+ if (typeof value === 'string')
158
+ return [value];
159
+ if (Array.isArray(value) && value.every(entry => typeof entry === 'string'))
160
+ return value;
161
+ throw new HttpError(400, `"${field}" is required and must be a string or an array of strings.`);
162
+ }
163
+ /** Same separator handling as the CLI's `--modules` values for plain module names. */
164
+ function splitList(value) {
165
+ return value.split(/[\s,]+/).map(entry => entry.trim()).filter(Boolean);
166
+ }
167
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/web/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ import { Provider } from './types';
2
+ export type JobKind = 'create' | 'delete' | 'update' | 'lifecycle';
3
+ export type JobStatus = 'queued' | 'running' | 'succeeded' | 'failed';
4
+ export interface ProgressLine {
5
+ at: string;
6
+ message: string;
7
+ }
8
+ /** A job as returned by the HTTP API: never the live object, always a copy. */
9
+ export interface JobSnapshot {
10
+ id: string;
11
+ kind: JobKind;
12
+ /** The environment name the job acts on (the new name for `create`). */
13
+ target: string;
14
+ provider?: Provider;
15
+ dryRun?: boolean;
16
+ status: JobStatus;
17
+ createdAt: string;
18
+ startedAt?: string;
19
+ finishedAt?: string;
20
+ progress: ProgressLine[];
21
+ /** True when old progress lines were dropped to bound memory. */
22
+ progressTruncated?: boolean;
23
+ result?: unknown;
24
+ error?: string;
25
+ }
26
+ export interface JobInput {
27
+ kind: JobKind;
28
+ target: string;
29
+ provider?: Provider;
30
+ dryRun?: boolean;
31
+ }
32
+ /**
33
+ * In-memory registry of long-running operations.
34
+ *
35
+ * Creating an environment downloads and assembles artifacts for minutes, so the
36
+ * HTTP API answers with a job id and the page polls it. Jobs are deliberately
37
+ * not persisted: they describe the work of this server process only, while the
38
+ * durable result (the environment registry and `<root>/environment.lock.json`)
39
+ * is written by the environment manager itself.
40
+ *
41
+ * Mutating jobs for the same environment are serialized here. `update-env`
42
+ * documents that it does not take the per-environment lifecycle lock, so a web
43
+ * update must never race a web start/stop/restart for the same environment.
44
+ */
45
+ export declare class JobStore {
46
+ private readonly limit;
47
+ private readonly progressLimit;
48
+ private readonly jobs;
49
+ private readonly tails;
50
+ constructor(limit?: number, progressLimit?: number);
51
+ /**
52
+ * Queue `action` and return its snapshot immediately.
53
+ *
54
+ * The action receives a progress sink; its rejection is recorded on the job
55
+ * instead of propagating, because there is no request left to fail.
56
+ */
57
+ run<T>(input: JobInput, action: (progress: (line: string) => void) => Promise<T>): JobSnapshot;
58
+ get(id: string): JobSnapshot | undefined;
59
+ /** Newest first. */
60
+ list(): JobSnapshot[];
61
+ /** Resolves once no job is queued or running; used for a graceful shutdown. */
62
+ whenIdle(): Promise<void>;
63
+ busy(): boolean;
64
+ private append;
65
+ private snapshot;
66
+ /** Drop the oldest finished jobs first; a running job is never discarded. */
67
+ private prune;
68
+ }
69
+ //# sourceMappingURL=jobs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jobs.d.ts","sourceRoot":"","sources":["../../src/web/jobs.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;AACnE,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;AAEtE,MAAM,WAAW,YAAY;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AAE7D,+EAA+E;AAC/E,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,CAAC;IACd,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,SAAS,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,iEAAiE;IACjE,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,QAAQ;IAAG,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE;AAMlG;;;;;;;;;;;;GAYG;AACH,qBAAa,QAAQ;IAGP,OAAO,CAAC,QAAQ,CAAC,KAAK;IAAO,OAAO,CAAC,QAAQ,CAAC,aAAa;IAFvE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA0B;IAC/C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoC;gBAC7B,KAAK,SAAK,EAAmB,aAAa,SAAM;IAE7E;;;;;OAKG;IACH,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,WAAW;IAoC9F,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAKxC,oBAAoB;IACpB,IAAI,IAAI,WAAW,EAAE;IAIrB,+EAA+E;IACzE,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAI/B,IAAI,IAAI,OAAO;IAIf,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,QAAQ;IAIhB,6EAA6E;IAC7E,OAAO,CAAC,KAAK;CAOd"}
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JobStore = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const message = (error) => (error instanceof Error ? error.message : String(error));
6
+ /**
7
+ * In-memory registry of long-running operations.
8
+ *
9
+ * Creating an environment downloads and assembles artifacts for minutes, so the
10
+ * HTTP API answers with a job id and the page polls it. Jobs are deliberately
11
+ * not persisted: they describe the work of this server process only, while the
12
+ * durable result (the environment registry and `<root>/environment.lock.json`)
13
+ * is written by the environment manager itself.
14
+ *
15
+ * Mutating jobs for the same environment are serialized here. `update-env`
16
+ * documents that it does not take the per-environment lifecycle lock, so a web
17
+ * update must never race a web start/stop/restart for the same environment.
18
+ */
19
+ class JobStore {
20
+ limit;
21
+ progressLimit;
22
+ jobs = new Map();
23
+ tails = new Map();
24
+ constructor(limit = 50, progressLimit = 400) {
25
+ this.limit = limit;
26
+ this.progressLimit = progressLimit;
27
+ }
28
+ /**
29
+ * Queue `action` and return its snapshot immediately.
30
+ *
31
+ * The action receives a progress sink; its rejection is recorded on the job
32
+ * instead of propagating, because there is no request left to fail.
33
+ */
34
+ run(input, action) {
35
+ const key = input.target.toLowerCase();
36
+ const job = {
37
+ id: (0, crypto_1.randomUUID)(),
38
+ kind: input.kind,
39
+ target: input.target,
40
+ ...(input.provider !== undefined ? { provider: input.provider } : {}),
41
+ ...(input.dryRun !== undefined ? { dryRun: input.dryRun } : {}),
42
+ status: 'queued',
43
+ createdAt: new Date().toISOString(),
44
+ progress: [],
45
+ progressTruncated: false,
46
+ };
47
+ this.jobs.set(job.id, job);
48
+ const previous = this.tails.get(key);
49
+ if (previous)
50
+ this.append(job, `Queued behind another operation on ${input.target}; mutations of one environment are serialized.`);
51
+ const started = (previous ?? Promise.resolve()).then(async () => {
52
+ job.status = 'running';
53
+ job.startedAt = new Date().toISOString();
54
+ try {
55
+ job.result = await action(line => this.append(job, line));
56
+ job.status = 'succeeded';
57
+ }
58
+ catch (error) {
59
+ job.error = message(error);
60
+ job.status = 'failed';
61
+ }
62
+ finally {
63
+ job.finishedAt = new Date().toISOString();
64
+ this.prune();
65
+ }
66
+ });
67
+ this.tails.set(key, started);
68
+ void started.then(() => { if (this.tails.get(key) === started)
69
+ this.tails.delete(key); });
70
+ this.prune();
71
+ return this.snapshot(job);
72
+ }
73
+ get(id) {
74
+ const job = this.jobs.get(id);
75
+ return job ? this.snapshot(job) : undefined;
76
+ }
77
+ /** Newest first. */
78
+ list() {
79
+ return [...this.jobs.values()].reverse().map(job => this.snapshot(job));
80
+ }
81
+ /** Resolves once no job is queued or running; used for a graceful shutdown. */
82
+ async whenIdle() {
83
+ while (this.busy())
84
+ await new Promise(resolve => setTimeout(resolve, 200));
85
+ }
86
+ busy() {
87
+ return [...this.jobs.values()].some(job => job.status === 'queued' || job.status === 'running');
88
+ }
89
+ append(job, line) {
90
+ if (job.progress.length >= this.progressLimit) {
91
+ job.progress.shift();
92
+ job.progressTruncated = true;
93
+ }
94
+ job.progress.push({ at: new Date().toISOString(), message: line });
95
+ }
96
+ snapshot(job) {
97
+ return { ...job, progress: job.progress.map(line => ({ ...line })), ...(job.progressTruncated ? { progressTruncated: true } : {}) };
98
+ }
99
+ /** Drop the oldest finished jobs first; a running job is never discarded. */
100
+ prune() {
101
+ while (this.jobs.size > this.limit) {
102
+ const oldest = [...this.jobs.values()].find(candidate => candidate.status === 'succeeded' || candidate.status === 'failed');
103
+ if (!oldest)
104
+ return;
105
+ this.jobs.delete(oldest.id);
106
+ }
107
+ }
108
+ }
109
+ exports.JobStore = JobStore;
110
+ //# sourceMappingURL=jobs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jobs.js","sourceRoot":"","sources":["../../src/web/jobs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { WebProviders } from './types';
2
+ /**
3
+ * The web server's provider access.
4
+ *
5
+ * Credentials come from the same profile as the CLI (`afm az login` /
6
+ * `afm oss login`); the browser never sees or supplies them.
7
+ */
8
+ export declare function storedProviders(): WebProviders;
9
+ //# sourceMappingURL=providers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../src/web/providers.ts"],"names":[],"mappings":"AAEA,OAAO,EAAY,YAAY,EAAE,MAAM,SAAS,CAAC;AAEjD;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,YAAY,CAK9C"}
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.storedProviders = storedProviders;
4
+ const stored_client_1 = require("../cli/stored-client");
5
+ /**
6
+ * The web server's provider access.
7
+ *
8
+ * Credentials come from the same profile as the CLI (`afm az login` /
9
+ * `afm oss login`); the browser never sees or supplies them.
10
+ */
11
+ function storedProviders() {
12
+ return {
13
+ available: stored_client_1.storedProviderAvailability,
14
+ client(provider) { return (0, stored_client_1.createStoredClient)(provider); },
15
+ };
16
+ }
17
+ //# sourceMappingURL=providers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers.js","sourceRoot":"","sources":["../../src/web/providers.ts"],"names":[],"mappings":";;AAUA,0CAKC;AAfD,wDAAsF;AAItF;;;;;GAKG;AACH,SAAgB,eAAe;IAC7B,OAAO;QACL,SAAS,EAAE,0CAA0B;QACrC,MAAM,CAAC,QAAkB,IAAuB,OAAO,IAAA,kCAAkB,EAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;KACvF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { JobStore } from './jobs';
2
+ import { WebManager, WebProviders, WebRegistry, WebStateInfo, WebUpdater } from './types';
3
+ export interface WebServerOptions {
4
+ /** Secret required by every `/api` request. At least 16 characters. */
5
+ token: string;
6
+ /** 0 (default) lets the OS choose a free port. */
7
+ port?: number;
8
+ /** Loopback address to bind; the default is the only value the CLI exposes. */
9
+ host?: string;
10
+ assetsDirectory?: string;
11
+ manager?: WebManager;
12
+ updater?: WebUpdater;
13
+ registry?: WebRegistry;
14
+ providers?: WebProviders;
15
+ state?: () => WebStateInfo;
16
+ jobs?: JobStore;
17
+ maxBodyBytes?: number;
18
+ resolveRoot?: (name: string, requested: string | undefined, dryRun: boolean) => Promise<string>;
19
+ }
20
+ export interface RunningWebServer {
21
+ host: string;
22
+ port: number;
23
+ /** Page URL without the token. */
24
+ url: string;
25
+ /** Page URL with the token in the fragment, so it never reaches the server log. */
26
+ tokenUrl: string;
27
+ jobs: JobStore;
28
+ close(): Promise<void>;
29
+ }
30
+ /** The token lives in the URL fragment: fragments are not sent in requests. */
31
+ export declare function tokenizedUrl(url: string, token: string): string;
32
+ /**
33
+ * Start the local web interface.
34
+ *
35
+ * It binds loopback only, requires the token for every API call, rejects any
36
+ * request whose Host header is not one of its own loopback names (DNS
37
+ * rebinding) and never sends a CORS header, so a page on another origin can
38
+ * neither read a reply nor send the custom token header.
39
+ */
40
+ export declare function startWebServer(options: WebServerOptions): Promise<RunningWebServer>;
41
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAElC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1F,MAAM,WAAW,gBAAgB;IAC/B,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,YAAY,CAAC;IAC3B,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACjG;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,mFAAmF;IACnF,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAID,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAuDzF"}