@mcpher/gas-fakes 2.3.18 → 2.5.2

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 (89) hide show
  1. package/README.md +15 -32
  2. package/package.json +1 -2
  3. package/src/cli/app.js +30 -2
  4. package/src/cli/server.js +32 -0
  5. package/src/cli/setup.js +24 -0
  6. package/src/cli/togas.js +176 -0
  7. package/src/index.js +2 -0
  8. package/src/services/common/fakeui.js +45 -0
  9. package/src/services/content/app.js +3 -0
  10. package/src/services/content/contentservice.js +14 -0
  11. package/src/services/content/textoutput.js +45 -0
  12. package/src/services/documentapp/fakedocumentapp.js +1 -1
  13. package/src/services/enums/contentenums.js +15 -0
  14. package/src/services/enums/htmlenums.js +13 -0
  15. package/src/services/enums/scriptenums.js +6 -0
  16. package/src/services/formapp/fakeformapp.js +5 -0
  17. package/src/services/html/app.js +9 -0
  18. package/src/services/html/consumerworker.js +129 -0
  19. package/src/services/html/googlescriptrun.js +91 -0
  20. package/src/services/html/htmloutput.js +127 -0
  21. package/src/services/html/htmloutputmetatag.js +14 -0
  22. package/src/services/html/htmlservice.js +94 -0
  23. package/src/services/html/htmltemplate.js +63 -0
  24. package/src/services/html/serverworker.js +135 -0
  25. package/src/services/html/webapp.js +266 -0
  26. package/src/services/html/worker.js +63 -0
  27. package/src/services/libhandlerapp/fakelibrary.js +2 -2
  28. package/src/services/scriptapp/app.js +44 -0
  29. package/src/services/scriptapp/fakeauthorizationinfo.js +22 -0
  30. package/src/services/slidesapp/fakeslidesapp.js +5 -0
  31. package/src/services/spreadsheetapp/fakebooleancondition.js +14 -2
  32. package/src/services/spreadsheetapp/fakegradientcondition.js +1 -1
  33. package/src/services/spreadsheetapp/fakeovergridimage.js +25 -0
  34. package/src/services/spreadsheetapp/fakesheet.js +23 -1
  35. package/src/services/spreadsheetapp/fakespreadsheet.js +68 -11
  36. package/src/services/spreadsheetapp/fakespreadsheetapp.js +70 -9
  37. package/src/services/stores/fakestores.js +7 -0
  38. package/src/support/auth.js +2 -0
  39. package/src/support/proxies.js +1 -1
  40. package/src/support/sxauth.js +20 -12
  41. package/src/support/utils.js +480 -200
  42. package/src/support/workersync/sxhtml.js +8 -0
  43. package/src/support/workersync/synchronizer.js +8 -1
  44. package/src/support/workersync/worker.js +5 -0
  45. package/api-docs/kdrive_api.json +0 -69958
  46. package/appsscript.json +0 -102
  47. package/gf_agent/README.md +0 -101
  48. package/gf_agent/SKILL.md +0 -484
  49. package/gf_agent/documentation.md +0 -105
  50. package/gf_agent/gf-agent-contributor/SKILL.md +0 -56
  51. package/gf_agent/index.md +0 -21
  52. package/gf_agent/knowledge/00-execution-context.md +0 -5
  53. package/gf_agent/knowledge/01-drive.md +0 -12
  54. package/gf_agent/knowledge/02-syntax.md +0 -14
  55. package/gf_agent/knowledge/03-auth.md +0 -15
  56. package/gf_agent/knowledge/04-advanced.md +0 -46
  57. package/gf_agent/knowledge/05-sheets-forms.md +0 -27
  58. package/gf_agent/knowledge/06-jdbc-cloudsql.md +0 -21
  59. package/gf_agent/knowledge/07-jdbc-auth-details.md +0 -30
  60. package/gf_agent/knowledge/08-docs-limitations.md +0 -4
  61. package/gf_agent/knowledge/09-orchestrator-pattern.md +0 -55
  62. package/gf_agent/knowledge/10-sandbox-security.md +0 -62
  63. package/gf_agent/knowledge/11-chart-builder-limitations.md +0 -15
  64. package/gf_agent/knowledge/12-gmail-eventual-consistency.md +0 -13
  65. package/gf_agent/knowledge/13-advanced-services-discovery.md +0 -29
  66. package/gf_agent/knowledge/14-utilities-parity.md +0 -13
  67. package/gf_agent/knowledge/15-logging-efficiency.md +0 -15
  68. package/gf_agent/knowledge/README.md +0 -16
  69. package/gf_agent/scripts/SKILL.template.md +0 -63
  70. package/gf_agent/scripts/builder.js +0 -118
  71. package/gf_agent/skills/base.md +0 -156
  72. package/gf_agent/skills/cache.md +0 -20
  73. package/gf_agent/skills/calendar.md +0 -780
  74. package/gf_agent/skills/charts.md +0 -127
  75. package/gf_agent/skills/document.md +0 -6752
  76. package/gf_agent/skills/drive.md +0 -423
  77. package/gf_agent/skills/forms.md +0 -4036
  78. package/gf_agent/skills/gmail.md +0 -576
  79. package/gf_agent/skills/jdbc.md +0 -3101
  80. package/gf_agent/skills/lock.md +0 -20
  81. package/gf_agent/skills/properties.md +0 -19
  82. package/gf_agent/skills/script.md +0 -50
  83. package/gf_agent/skills/slides.md +0 -5054
  84. package/gf_agent/skills/spreadsheet.md +0 -56075
  85. package/gf_agent/skills/urlfetch.md +0 -28
  86. package/gf_agent/skills/utilities.md +0 -50
  87. package/gf_agent/skills/xml.md +0 -270
  88. package/skills-lock.json +0 -10
  89. package/src/services/documentapp/fakeui.js +0 -27
@@ -0,0 +1,129 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { workerData } from 'worker_threads';
4
+
5
+ const { mainScriptPath, funcName, args, isTemplate, templateString, env } = workerData;
6
+ const control = new Int32Array(workerData.controlBuf);
7
+ const dataView = new Uint8Array(workerData.dataBuf);
8
+ const textEncoder = new TextEncoder();
9
+
10
+ // Initialize the Apps Script environment
11
+ if (env) {
12
+ Object.assign(process.env, env);
13
+ }
14
+ globalThis.__gasFakesMainScriptPath = mainScriptPath;
15
+ await import('../../../main.js');
16
+
17
+ // Bootstrap Auth completely via standard initialization
18
+ import { Syncit } from '../../support/syncit.js';
19
+
20
+ // Trigger a fresh authentication flow to ensure Auth.getUserId() and others are populated.
21
+ // We specify the platforms from environment or default to google.
22
+ const platforms = process.env.GF_PLATFORM_AUTH ? process.env.GF_PLATFORM_AUTH.split(',') : ['google'];
23
+ Syncit.fxInit({ platformAuth: platforms });
24
+
25
+ const CONTROL_INDICES = {
26
+ STATUS: 0,
27
+ DATA_SIZE: 1,
28
+ IS_ERROR: 2
29
+ };
30
+
31
+ async function run() {
32
+ try {
33
+
34
+
35
+ // Dynamically load the user's module
36
+ const userModule = await import(mainScriptPath);
37
+
38
+ // Expose all exports to globalThis for legacy patterns
39
+ Object.keys(userModule).forEach(key => {
40
+ globalThis[key] = userModule[key];
41
+ });
42
+
43
+ let result;
44
+
45
+ if (isTemplate) {
46
+ // Evaluate template
47
+ // We pass the userModule context to allow access to functions like Include
48
+ result = templateString.replace(/<\?!=?\s*([\s\S]+?)\s*\?>/g, (match, expression) => {
49
+ try {
50
+ // We use the userModule exports as the scope for template expressions
51
+ const func = new Function(...Object.keys(userModule), `return ${expression}`);
52
+ const exprResult = func(...Object.values(userModule));
53
+
54
+ if (exprResult && typeof exprResult.getContent === 'function') {
55
+ return exprResult.getContent();
56
+ }
57
+ return typeof exprResult !== 'undefined' ? exprResult : '';
58
+ } catch (e) {
59
+ console.error(`gas-fakes template evaluation error for scriptlet '${expression}':`, e.message);
60
+ return match;
61
+ }
62
+ });
63
+ } else {
64
+ // Run function
65
+ const func = userModule[funcName] || globalThis[funcName];
66
+ if (typeof func !== 'function') {
67
+ throw new Error(`google.script.run: function "${funcName}" is not defined.`);
68
+ }
69
+
70
+ // Re-hydrate doPost event object
71
+ if (funcName === 'doPost' && args && args[0] && args[0].postData) {
72
+ args[0].postData.getDataAsString = function() { return this.contents; };
73
+ }
74
+
75
+ const rawResult = await func(...(args || []));
76
+
77
+ // Serialize output if it's a FakeHtmlOutput or FakeTextOutput
78
+ if (rawResult && typeof rawResult.getContent === 'function') {
79
+ result = {
80
+ __isHtmlOutput: !!rawResult.__isHtmlOutput,
81
+ __isTextOutput: !!rawResult.__isTextOutput,
82
+ __framingType: rawResult.__framingType || null,
83
+ content: rawResult.getContent(),
84
+ title: typeof rawResult.getTitle === 'function' ? rawResult.getTitle() : '',
85
+ width: typeof rawResult.getWidth === 'function' ? rawResult.getWidth() : null,
86
+ height: typeof rawResult.getHeight === 'function' ? rawResult.getHeight() : null,
87
+ mimeType: typeof rawResult.getMimeType === 'function' ? rawResult.getMimeType() : null
88
+ };
89
+ } else {
90
+ result = typeof rawResult === 'undefined' ? undefined : JSON.parse(JSON.stringify(rawResult));
91
+ }
92
+ }
93
+
94
+ writeResult(result);
95
+ } catch (error) {
96
+ console.error('[gas-fakes worker error]', error);
97
+ writeError(error);
98
+ } finally {
99
+ Atomics.store(control, CONTROL_INDICES.STATUS, 0);
100
+ Atomics.notify(control, 0);
101
+ }
102
+ }
103
+
104
+ function writeResult(result) {
105
+ const resultString = JSON.stringify(result === undefined ? null : result);
106
+ const encodedResult = textEncoder.encode(resultString);
107
+
108
+ if (encodedResult.length > dataView.buffer.byteLength) {
109
+ throw new Error('Result exceeds shared buffer size');
110
+ }
111
+
112
+ dataView.set(encodedResult);
113
+ Atomics.store(control, CONTROL_INDICES.DATA_SIZE, encodedResult.length);
114
+ Atomics.store(control, CONTROL_INDICES.IS_ERROR, 0);
115
+ }
116
+
117
+ function writeError(error) {
118
+ const message = error?.message || (typeof error === 'string' ? error : JSON.stringify(error) || 'Unknown error');
119
+ const stack = error?.stack || new Error().stack;
120
+ console.error('[gas-fakes worker error details]:', message, stack);
121
+ const errorString = JSON.stringify({ message, stack });
122
+ const encodedError = textEncoder.encode(errorString);
123
+
124
+ dataView.set(encodedError);
125
+ Atomics.store(control, CONTROL_INDICES.DATA_SIZE, encodedError.length);
126
+ Atomics.store(control, CONTROL_INDICES.IS_ERROR, 1);
127
+ }
128
+
129
+ run();
@@ -0,0 +1,91 @@
1
+
2
+ import { ServerWorkerContext } from './serverworker.js';
3
+
4
+ /**
5
+ * Emulates the client-side google.script.run API.
6
+ * In gas-fakes, this runs in the same Node process.
7
+ */
8
+ export class FakeGoogleScriptRun {
9
+ constructor(handlers = {}) {
10
+ this._successHandler = handlers.successHandler;
11
+ this._failureHandler = handlers.failureHandler;
12
+ this._userObject = handlers.userObject;
13
+ this._serverFunctions = handlers.serverFunctions || null;
14
+
15
+ return new Proxy(this, {
16
+ get: (target, prop) => {
17
+ // Return existing properties (like withSuccessHandler)
18
+ if (prop in target) {
19
+ return target[prop];
20
+ }
21
+
22
+ // Methods for chaining handlers
23
+ if (prop === 'withSuccessHandler') {
24
+ return (handler) => new FakeGoogleScriptRun({ ...target._getHandlers(), successHandler: handler });
25
+ }
26
+ if (prop === 'withFailureHandler') {
27
+ return (handler) => new FakeGoogleScriptRun({ ...target._getHandlers(), failureHandler: handler });
28
+ }
29
+ if (prop === 'withUserObject') {
30
+ return (obj) => new FakeGoogleScriptRun({ ...target._getHandlers(), userObject: obj });
31
+ }
32
+
33
+ // Special method to register functions manually if they aren't global
34
+ // This is extremely useful for modular tests or explicit wiring
35
+ if (prop === '__registerServerFunctions') {
36
+ return (funcs) => {
37
+ this._serverFunctions = { ...this._serverFunctions, ...funcs };
38
+ return this;
39
+ }
40
+ }
41
+
42
+ // Otherwise, it's a server-side function call
43
+ return (...args) => {
44
+ // Asynchronous execution simulation
45
+ setTimeout(() => {
46
+ try {
47
+ if (prop.endsWith('_') || prop.startsWith('__')) {
48
+ throw new Error(`google.script.run: function "${prop}" is private and cannot be called from the client.`);
49
+ }
50
+
51
+ // In Apps Script, parameters are passed by value (JSON serialized)
52
+ const serializedArgs = JSON.parse(JSON.stringify(args));
53
+
54
+ let result;
55
+
56
+ // Fallback for modular tests that explicitly registered functions
57
+ if (this._serverFunctions && typeof this._serverFunctions[prop] === 'function') {
58
+ result = this._serverFunctions[prop](...serializedArgs);
59
+ } else {
60
+ // Main stateless execution path via Synchronous Worker Threads
61
+ const ctx = new ServerWorkerContext();
62
+ result = ctx.runFunction(prop, serializedArgs);
63
+ }
64
+
65
+ // Result is also serialized back
66
+ const serializedResult = typeof result === 'undefined' ? undefined : JSON.parse(JSON.stringify(result));
67
+
68
+ if (this._successHandler) {
69
+ this._successHandler(serializedResult, this._userObject);
70
+ }
71
+ } catch (err) {
72
+ if (this._failureHandler) {
73
+ this._failureHandler(err, this._userObject);
74
+ }
75
+ }
76
+ }, 0);
77
+ };
78
+ }
79
+ });
80
+ }
81
+
82
+ _getHandlers() {
83
+ return {
84
+ successHandler: this._successHandler,
85
+ failureHandler: this._failureHandler,
86
+ userObject: this._userObject,
87
+ serverFunctions: this._serverFunctions
88
+ };
89
+ }
90
+ }
91
+
@@ -0,0 +1,127 @@
1
+ import { FakeHtmlTemplate } from './htmltemplate.js';
2
+ import { FakeHtmlOutputMetaTag } from './htmloutputmetatag.js';
3
+
4
+ export class FakeHtmlOutput {
5
+ constructor(content = '') {
6
+ this._content = content;
7
+ this._title = '';
8
+ this._width = 600;
9
+ this._height = 450;
10
+ this._metaData = {};
11
+ this._faviconUrl = '';
12
+ this._xFrameOptionsMode = null;
13
+ this._sandboxMode = null;
14
+ this.__isHtmlOutput = true;
15
+ }
16
+
17
+ getContent() {
18
+ return this._content
19
+ .replace(/<script([^>]*)>([\s\S]*?)<\/script>/gi, (match, attrs, body) => {
20
+ if (attrs.includes('src=')) return match;
21
+ if (body.includes('//# sourceURL=')) return match;
22
+ const sourceUrl = `\n//# sourceURL=__gas_fakes_dynamic_script_${Date.now()}.js`;
23
+ return `<script${attrs}>${body}${sourceUrl}</script>`;
24
+ })
25
+ .replace(/<style([^>]*)>([\s\S]*?)<\/style>/gi, (match, attrs, body) => {
26
+ if (body.includes('/*# sourceURL=')) return match;
27
+ const sourceUrl = `\n/*# sourceURL=__gas_fakes_dynamic_style_${Date.now()}.css */`;
28
+ return `<style${attrs}>${body}${sourceUrl}</style>`;
29
+ });
30
+ }
31
+
32
+ setContent(content) {
33
+ this._content = content;
34
+ return this;
35
+ }
36
+
37
+ append(content) {
38
+ this._content += content;
39
+ return this;
40
+ }
41
+
42
+ appendUntrusted(content) {
43
+ this._content += String(content)
44
+ .replace(/&/g, '&amp;')
45
+ .replace(/</g, '&lt;')
46
+ .replace(/>/g, '&gt;')
47
+ .replace(/"/g, '&quot;')
48
+ .replace(/'/g, '&#39;');
49
+ return this;
50
+ }
51
+
52
+ clear() {
53
+ this._content = '';
54
+ return this;
55
+ }
56
+
57
+ setTitle(title) {
58
+ this._title = title;
59
+ return this;
60
+ }
61
+
62
+ getTitle() {
63
+ return this._title;
64
+ }
65
+
66
+ setWidth(width) {
67
+ this._width = width;
68
+ return this;
69
+ }
70
+
71
+ getWidth() {
72
+ return this._width;
73
+ }
74
+
75
+ setHeight(height) {
76
+ this._height = height;
77
+ return this;
78
+ }
79
+
80
+ getHeight() {
81
+ return this._height;
82
+ }
83
+
84
+ setFaviconUrl(iconUrl) {
85
+ this._faviconUrl = iconUrl;
86
+ return this;
87
+ }
88
+
89
+ getFaviconUrl() {
90
+ return this._faviconUrl;
91
+ }
92
+
93
+ addMetaTag(name, content) {
94
+ this._metaData[name] = content;
95
+ return this;
96
+ }
97
+
98
+ getMetaTags() {
99
+ return Object.keys(this._metaData).map(name => new FakeHtmlOutputMetaTag(name, this._metaData[name]));
100
+ }
101
+
102
+ setXFrameOptionsMode(mode) {
103
+ this._xFrameOptionsMode = mode;
104
+ return this;
105
+ }
106
+
107
+ setSandboxMode(mode) {
108
+ this._sandboxMode = mode;
109
+ return this;
110
+ }
111
+
112
+ asTemplate() {
113
+ return new FakeHtmlTemplate(this._content);
114
+ }
115
+
116
+ getBlob() {
117
+ return globalThis.Utilities.newBlob(this._content, 'text/html', (this._title || 'output') + '.html');
118
+ }
119
+
120
+ getAs(contentType) {
121
+ // In live GAS, getAs performs server-side conversion (e.g. to PDF).
122
+ // Locally, we just return a blob with the new mimeType for parity checking.
123
+ const b = this.getBlob();
124
+ b.setContentType(contentType);
125
+ return b;
126
+ }
127
+ }
@@ -0,0 +1,14 @@
1
+ export class FakeHtmlOutputMetaTag {
2
+ constructor(name, content) {
3
+ this._name = name;
4
+ this._content = content;
5
+ }
6
+
7
+ getContent() {
8
+ return this._content;
9
+ }
10
+
11
+ getName() {
12
+ return this._name;
13
+ }
14
+ }
@@ -0,0 +1,94 @@
1
+
2
+ import { FakeHtmlOutput } from './htmloutput.js';
3
+ import { FakeHtmlTemplate } from './htmltemplate.js';
4
+ import { FakeGoogleScriptRun } from './googlescriptrun.js';
5
+ import { HtmlEnums } from '../enums/htmlenums.js';
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+
9
+ import { startServer } from './webapp.js';
10
+
11
+ // Initialize google.script globals
12
+ if (typeof globalThis.google === 'undefined') {
13
+ globalThis.google = {
14
+ script: {
15
+ run: new FakeGoogleScriptRun(),
16
+ host: {
17
+ close: () => {},
18
+ setHeight: () => {},
19
+ setWidth: () => {},
20
+ origin: ''
21
+ },
22
+ history: {
23
+ push: () => {},
24
+ replace: () => {},
25
+ setChangeHandler: () => {}
26
+ }
27
+ }
28
+ };
29
+ }
30
+
31
+ export class FakeHtmlService {
32
+ constructor() {
33
+ this.SandboxMode = HtmlEnums.SandboxMode;
34
+ this.XFrameOptionsMode = HtmlEnums.XFrameOptionsMode;
35
+ }
36
+
37
+ _readLocalFile(filename) {
38
+ // In live Apps Script, `HtmlService.createHtmlOutputFromFile('Index')`
39
+ // implicitly looks for an `Index.html` file in the project.
40
+ let targetFile = filename;
41
+ if (!targetFile.endsWith('.html')) {
42
+ targetFile += '.html';
43
+ }
44
+
45
+ // Resolve relative to the consumer's main script
46
+ let mainScriptPath = process.argv[1];
47
+
48
+ // In gas-fakes serve mode, we use worker threads. The worker passes the main script path via workerData.
49
+ if (globalThis.__gasFakesMainScriptPath) {
50
+ mainScriptPath = globalThis.__gasFakesMainScriptPath;
51
+ }
52
+
53
+ if (!mainScriptPath || mainScriptPath.endsWith('node')) {
54
+ throw new Error("Could not determine project root. Ensure process.argv[1] is set or __gasFakesMainScriptPath is defined.");
55
+ }
56
+
57
+ const projectDir = path.dirname(mainScriptPath);
58
+ const fullPath = path.resolve(projectDir, targetFile);
59
+
60
+ if (!fs.existsSync(fullPath)) {
61
+ throw new Error(`No HTML file named ${filename} was found.`);
62
+ }
63
+
64
+ return fs.readFileSync(fullPath, 'utf8');
65
+ }
66
+
67
+ createHtmlOutput(html = '') {
68
+ return new FakeHtmlOutput(html);
69
+ }
70
+
71
+ createHtmlOutputFromFile(filename) {
72
+ const content = this._readLocalFile(filename);
73
+ return new FakeHtmlOutput(content);
74
+ }
75
+
76
+ createTemplate(html = '') {
77
+ return new FakeHtmlTemplate(html);
78
+ }
79
+
80
+ createTemplateFromFile(filename) {
81
+ const content = this._readLocalFile(filename);
82
+ return new FakeHtmlTemplate(content);
83
+ }
84
+
85
+ getUserAgent() {
86
+ return 'Node.js gas-fakes emulator';
87
+ }
88
+
89
+ __startWebApp(port = 3000) {
90
+ return startServer(port);
91
+ }
92
+ }
93
+
94
+ export const newFakeHtmlService = () => new FakeHtmlService();
@@ -0,0 +1,63 @@
1
+
2
+ import { FakeHtmlOutput } from './htmloutput.js';
3
+ import { ServerWorkerContext } from './serverworker.js';
4
+
5
+ export class FakeHtmlTemplate {
6
+ constructor(content = '') {
7
+ this._content = content;
8
+
9
+ return new Proxy(this, {
10
+ get: (target, prop, receiver) => {
11
+ if (prop in target) {
12
+ return Reflect.get(target, prop, receiver);
13
+ }
14
+ return target[prop];
15
+ },
16
+ set: (target, prop, value, receiver) => {
17
+ return Reflect.set(target, prop, value, receiver);
18
+ }
19
+ });
20
+ }
21
+
22
+ evaluate() {
23
+ let evaluatedContent = this._content;
24
+
25
+ let workerResult = null;
26
+ try {
27
+ const ctx = new ServerWorkerContext();
28
+ workerResult = ctx.evaluateTemplate(this._content);
29
+ } catch (e) {
30
+ console.error(e);
31
+ throw e;
32
+ }
33
+
34
+ if (workerResult) {
35
+ evaluatedContent = workerResult;
36
+ }
37
+
38
+ // Final pass for explicitly set template properties
39
+ evaluatedContent = evaluatedContent.replace(/<\?=\s*([^?]+)\s*\?>/g, (match, varName) => {
40
+ const trimmedVarName = varName.trim();
41
+ if (typeof this[trimmedVarName] !== 'undefined') {
42
+ return this[trimmedVarName];
43
+ }
44
+ return match;
45
+ });
46
+
47
+ return new FakeHtmlOutput(evaluatedContent);
48
+ }
49
+
50
+ getRawContent() {
51
+ return this._content;
52
+ }
53
+
54
+ getCode() {
55
+ return `// Compiled template\n(function() { let output = ""; ${this._content.split('\n').map(line => `output += ${JSON.stringify(line)} + "\\n";`).join('\n')} return output; })()`;
56
+ }
57
+
58
+ getCodeWithComments() {
59
+ return `// Compiled template with comments\n(function() { let output = ""; ${this._content.split('\n').map(line => `// ${line}\noutput += ${JSON.stringify(line)} + "\\n";`).join('\n')} return output; })()`;
60
+ }
61
+ }
62
+
63
+
@@ -0,0 +1,135 @@
1
+ import { Auth } from '../../support/auth.js';
2
+ import { Worker } from 'worker_threads';
3
+ import { fileURLToPath } from 'url';
4
+ import path from 'path';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+ const workerPath = path.join(__dirname, 'consumerworker.js');
9
+
10
+ const CONTROL_INDICES = {
11
+ STATUS: 0,
12
+ DATA_SIZE: 1,
13
+ IS_ERROR: 2
14
+ };
15
+
16
+ export class ServerWorkerContext {
17
+ constructor(scriptPath = null) {
18
+ this._mainScriptPath = scriptPath || globalThis.__gasFakesMainScriptPath || process.argv[1];
19
+
20
+ // Fallback for test runners or dynamic imports where process.argv[1] is node itself or undefined
21
+ if (!this._mainScriptPath || this._mainScriptPath.endsWith('node') || this._mainScriptPath.endsWith('gas-fakes.js') || this._mainScriptPath.endsWith('gas-fakes')) {
22
+ const stack = new Error().stack;
23
+ const match = stack.match(/at file:\/\/(.*\.js)/);
24
+ if (match && match[1]) {
25
+ // We want to find the entry point, not this file itself
26
+ const lines = stack.split('\n');
27
+ for (let i = lines.length - 1; i >= 0; i--) {
28
+ const m = lines[i].match(/at (?:async )?file:\/\/(.*\.js)/);
29
+ if (m && m[1] && !m[1].includes('serverworker.js')) {
30
+ this._mainScriptPath = m[1];
31
+ break;
32
+ }
33
+ }
34
+ }
35
+ }
36
+
37
+
38
+ // Create shared buffers for synchronous communication
39
+ // 3 control Int32s
40
+ this._controlBuf = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 3);
41
+ this._control = new Int32Array(this._controlBuf);
42
+
43
+ // 1MB data buffer (should be enough for template substitutions and function returns)
44
+ this._dataBuf = new SharedArrayBuffer(1024 * 1024);
45
+ this._dataView = new Uint8Array(this._dataBuf);
46
+ this._textDecoder = new TextDecoder();
47
+ }
48
+
49
+ _executeSyncWorker(workerDataPayload) {
50
+ if (!this._mainScriptPath) {
51
+ throw new Error("Could not determine main script path. Ensure process.argv[1] is set.");
52
+ }
53
+
54
+ // Set lock to busy (1)
55
+ Atomics.store(this._control, CONTROL_INDICES.STATUS, 1);
56
+
57
+ const worker = new Worker(workerPath, {
58
+ workerData: {
59
+ ...workerDataPayload,
60
+ mainScriptPath: globalThis.__gasFakesMainScriptPath || this._mainScriptPath,
61
+ controlBuf: this._controlBuf,
62
+ dataBuf: this._dataBuf,
63
+ env: process.env // Pass current environment
64
+ },
65
+ stdout: true,
66
+ stderr: true
67
+ });
68
+
69
+ worker.stdout.pipe(process.stdout);
70
+ worker.stderr.pipe(process.stderr);
71
+
72
+ // Handle unexpected worker crashes
73
+ worker.on('error', (err) => {
74
+ console.error("Consumer worker crashed:", err);
75
+ Atomics.store(this._control, CONTROL_INDICES.STATUS, 0);
76
+ Atomics.notify(this._control, 0);
77
+ });
78
+
79
+ worker.on('exit', (code) => {
80
+ if (code !== 0) {
81
+ Atomics.store(this._control, CONTROL_INDICES.STATUS, 0);
82
+ Atomics.notify(this._control, 0);
83
+ }
84
+ });
85
+
86
+ // Block main thread until worker finishes (sets status to 0)
87
+ Atomics.wait(this._control, CONTROL_INDICES.STATUS, 1);
88
+
89
+ // Read result
90
+ const hasError = Atomics.load(this._control, CONTROL_INDICES.IS_ERROR) === 1;
91
+ const resultSize = Atomics.load(this._control, CONTROL_INDICES.DATA_SIZE);
92
+
93
+ const resultBytes = this._dataView.slice(0, resultSize);
94
+ const resultString = this._textDecoder.decode(resultBytes);
95
+
96
+ const resultData = JSON.parse(resultString);
97
+
98
+ if (hasError) {
99
+ // If the error was thrown as a string, its stack will be artificial (generated in writeError)
100
+ // and won't contain the actual message. Let's make sure the message is always visible.
101
+ const errorMsg = resultData.message || 'Unknown error in worker';
102
+ const errorStack = resultData.stack || '';
103
+
104
+ const err = new Error(errorMsg);
105
+ // Only replace the stack if it contains useful information, otherwise use the standard one
106
+ if (errorStack && !errorStack.startsWith('Error\n at writeError')) {
107
+ err.stack = errorStack;
108
+ }
109
+ throw err;
110
+ }
111
+
112
+ return resultData;
113
+ }
114
+
115
+ /**
116
+ * Synchronously evaluates a template string in a fresh consumer instance.
117
+ */
118
+ evaluateTemplate(templateString) {
119
+ return this._executeSyncWorker({
120
+ isTemplate: true,
121
+ templateString
122
+ });
123
+ }
124
+
125
+ /**
126
+ * Synchronously executes a function in a fresh consumer instance.
127
+ */
128
+ runFunction(funcName, args) {
129
+ return this._executeSyncWorker({
130
+ isTemplate: false,
131
+ funcName,
132
+ args
133
+ });
134
+ }
135
+ }