@remotion/studio-server 4.0.437 → 4.0.439

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 (35) hide show
  1. package/dist/client-render-queue.js +3 -3
  2. package/dist/codemods/apply-visual-control.js +8 -1
  3. package/dist/codemods/format-inline-content.d.ts +21 -0
  4. package/dist/codemods/format-inline-content.js +108 -0
  5. package/dist/codemods/read-visual-control-values.d.ts +7 -0
  6. package/dist/codemods/read-visual-control-values.js +166 -0
  7. package/dist/codemods/update-default-props.d.ts +4 -1
  8. package/dist/codemods/update-default-props.js +51 -22
  9. package/dist/file-watcher.d.ts +24 -7
  10. package/dist/file-watcher.js +148 -29
  11. package/dist/index.d.ts +5 -2
  12. package/dist/index.js +3 -0
  13. package/dist/preview-server/api-routes.js +8 -2
  14. package/dist/preview-server/default-props-watchers.js +12 -17
  15. package/dist/preview-server/file-existence-watchers.js +3 -3
  16. package/dist/preview-server/hot-middleware/index.js +1 -15
  17. package/dist/preview-server/live-events.d.ts +6 -1
  18. package/dist/preview-server/live-events.js +12 -2
  19. package/dist/preview-server/routes/apply-codemod.js +2 -1
  20. package/dist/preview-server/routes/apply-visual-control-change.js +103 -5
  21. package/dist/preview-server/routes/can-update-default-props.d.ts +10 -3
  22. package/dist/preview-server/routes/can-update-default-props.js +138 -13
  23. package/dist/preview-server/routes/can-update-sequence-props.js +4 -0
  24. package/dist/preview-server/routes/log-update.d.ts +8 -0
  25. package/dist/preview-server/routes/log-update.js +60 -27
  26. package/dist/preview-server/routes/save-sequence-props.js +47 -5
  27. package/dist/preview-server/routes/update-default-props.js +41 -4
  28. package/dist/preview-server/sequence-props-watchers.js +4 -9
  29. package/dist/preview-server/start-server.js +15 -1
  30. package/dist/preview-server/undo-stack.d.ts +24 -4
  31. package/dist/preview-server/undo-stack.js +75 -11
  32. package/dist/preview-server/watch-ignore-next-change.d.ts +3 -0
  33. package/dist/preview-server/watch-ignore-next-change.js +12 -0
  34. package/dist/start-studio.js +3 -0
  35. package/package.json +6 -6
@@ -26,10 +26,10 @@ const addCompletedClientRender = ({ render, remotionRoot, }) => {
26
26
  const filePath = (0, resolve_output_path_1.resolveOutputPath)(remotionRoot, render.outName);
27
27
  const { unwatch } = (0, file_watcher_1.installFileWatcher)({
28
28
  file: filePath,
29
- onChange: (type) => {
30
- if (type === 'created' || type === 'deleted') {
29
+ onChange: (event) => {
30
+ if (event.type === 'created' || event.type === 'deleted') {
31
31
  updateCompletedClientRender(render.id, {
32
- deletedOutputLocation: type === 'deleted',
32
+ deletedOutputLocation: event.type === 'deleted',
33
33
  });
34
34
  }
35
35
  },
@@ -68,7 +68,14 @@ const applyVisualControl = ({ file, transformation, changesMade, }) => {
68
68
  if (change.id !== str) {
69
69
  continue;
70
70
  }
71
- const parsed = (0, parse_ast_1.parseAst)(`a = ${(0, studio_shared_1.stringifyDefaultProps)({ props: JSON.parse(change.newValueSerialized), enumPaths: change.enumPaths })}`).program.body[0].expression.right;
71
+ let parsed;
72
+ if (change.newValueIsUndefined) {
73
+ parsed = (0, parse_ast_1.parseAst)('a = undefined').program
74
+ .body[0].expression.right;
75
+ }
76
+ else {
77
+ parsed = (0, parse_ast_1.parseAst)(`a = ${(0, studio_shared_1.stringifyDefaultProps)({ props: JSON.parse(change.newValueSerialized), enumPaths: change.enumPaths })}`).program.body[0].expression.right;
78
+ }
72
79
  node.arguments[1] = parsed;
73
80
  changesMade.push({
74
81
  description: `Applied visual control ${change.id}`,
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Instead of running prettier on the entire file (which is slow),
3
+ * format only a small snippet of inline content (e.g. stringified defaultProps).
4
+ *
5
+ * @param inlineContent - The raw content to format (e.g. stringified props)
6
+ * @param linePrefix - Everything from the start of the line to where
7
+ * inlineContent will appear (used to calculate column offset and indentation)
8
+ * @param endOfLine - Prettier endOfLine option
9
+ *
10
+ * We wrap the content in `const __x__ = CONTENT;` and adjust printWidth
11
+ * so prettier makes the same line-breaking decisions as if the content
12
+ * were at its actual column position in the file.
13
+ */
14
+ export declare const formatInlineContent: ({ inlineContent, linePrefix, endOfLine, }: {
15
+ inlineContent: string;
16
+ linePrefix: string;
17
+ endOfLine: "auto" | "lf";
18
+ }) => Promise<{
19
+ formatted: string;
20
+ didFormat: boolean;
21
+ }>;
@@ -0,0 +1,108 @@
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.formatInlineContent = void 0;
37
+ /**
38
+ * Instead of running prettier on the entire file (which is slow),
39
+ * format only a small snippet of inline content (e.g. stringified defaultProps).
40
+ *
41
+ * @param inlineContent - The raw content to format (e.g. stringified props)
42
+ * @param linePrefix - Everything from the start of the line to where
43
+ * inlineContent will appear (used to calculate column offset and indentation)
44
+ * @param endOfLine - Prettier endOfLine option
45
+ *
46
+ * We wrap the content in `const __x__ = CONTENT;` and adjust printWidth
47
+ * so prettier makes the same line-breaking decisions as if the content
48
+ * were at its actual column position in the file.
49
+ */
50
+ const formatInlineContent = async ({ inlineContent, linePrefix, endOfLine, }) => {
51
+ var _a;
52
+ var _b, _c, _d;
53
+ let prettier = null;
54
+ try {
55
+ prettier = await Promise.resolve().then(() => __importStar(require('prettier')));
56
+ }
57
+ catch (_e) {
58
+ return { formatted: inlineContent, didFormat: false };
59
+ }
60
+ const { format, resolveConfig, resolveConfigFile } = prettier;
61
+ const configFilePath = await resolveConfigFile();
62
+ if (!configFilePath) {
63
+ return { formatted: inlineContent, didFormat: false };
64
+ }
65
+ const prettierConfig = await resolveConfig(configFilePath);
66
+ if (!prettierConfig) {
67
+ return { formatted: inlineContent, didFormat: false };
68
+ }
69
+ const tabWidth = (_b = prettierConfig.tabWidth) !== null && _b !== void 0 ? _b : 2;
70
+ const baseIndent = (_c = (_a = linePrefix.match(/^(\s*)/)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _c !== void 0 ? _c : '';
71
+ // Calculate visual column offset (tabs expand to tabWidth columns)
72
+ const columnOffset = [...linePrefix].reduce((col, ch) => (ch === '\t' ? col + tabWidth : col + 1), 0);
73
+ // Adjust printWidth so the wrapper prefix occupies the same visual
74
+ // width as the actual file prefix, ensuring identical line breaks.
75
+ const configPrintWidth = (_d = prettierConfig.printWidth) !== null && _d !== void 0 ? _d : 80;
76
+ const wrapperPrefix = 'const __x__ = ';
77
+ const effectivePrintWidth = Math.max(configPrintWidth - columnOffset + wrapperPrefix.length, 20);
78
+ const wrappedSource = `${wrapperPrefix}${inlineContent};\n`;
79
+ const formattedWrapped = await format(wrappedSource, {
80
+ ...prettierConfig,
81
+ printWidth: effectivePrintWidth,
82
+ filepath: 'test.tsx',
83
+ plugins: [],
84
+ endOfLine,
85
+ });
86
+ // Extract the formatted value from the wrapper
87
+ const withoutSemicolon = formattedWrapped.replace(/;\s*$/, '');
88
+ let formattedProps;
89
+ if (withoutSemicolon.startsWith(wrapperPrefix)) {
90
+ formattedProps = withoutSemicolon.slice(wrapperPrefix.length);
91
+ }
92
+ else {
93
+ // Prettier broke the line after `=` — extract and dedent one level
94
+ const lines = withoutSemicolon.split('\n').slice(1);
95
+ const useTabs = prettierConfig.useTabs;
96
+ const oneIndent = useTabs ? '\t' : ' '.repeat(tabWidth);
97
+ formattedProps = lines
98
+ .map((l) => (l.startsWith(oneIndent) ? l.slice(oneIndent.length) : l))
99
+ .join('\n');
100
+ }
101
+ // Add base indentation to all lines except the first
102
+ const indentedProps = formattedProps
103
+ .split('\n')
104
+ .map((line, i) => i === 0 ? line : line.length > 0 ? baseIndent + line : line)
105
+ .join('\n');
106
+ return { formatted: indentedProps, didFormat: true };
107
+ };
108
+ exports.formatInlineContent = formatInlineContent;
@@ -0,0 +1,7 @@
1
+ import type { File } from '@babel/types';
2
+ export type VisualControlValueFromCode = {
3
+ id: string;
4
+ value: unknown;
5
+ isUndefined: boolean;
6
+ };
7
+ export declare const readVisualControlValues: (file: File) => VisualControlValueFromCode[];
@@ -0,0 +1,166 @@
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.readVisualControlValues = void 0;
37
+ const recast = __importStar(require("recast"));
38
+ const getStringValue = (node) => {
39
+ if (node.type === 'StringLiteral') {
40
+ return node.value;
41
+ }
42
+ if (node.type === 'TemplateLiteral') {
43
+ const tl = node;
44
+ if (tl.expressions.length > 0) {
45
+ return null;
46
+ }
47
+ return tl.quasis[0].value.raw;
48
+ }
49
+ return null;
50
+ };
51
+ const evaluateExpression = (node) => {
52
+ switch (node.type) {
53
+ case 'NumericLiteral':
54
+ return node.value;
55
+ case 'StringLiteral':
56
+ return node.value;
57
+ case 'BooleanLiteral':
58
+ return node.value;
59
+ case 'NullLiteral':
60
+ return null;
61
+ case 'Identifier': {
62
+ const { name } = node;
63
+ if (name === 'undefined') {
64
+ return undefined;
65
+ }
66
+ throw new Error(`Cannot evaluate identifier: ${name}`);
67
+ }
68
+ case 'UnaryExpression': {
69
+ const unary = node;
70
+ if (unary.operator === '-') {
71
+ const arg = evaluateExpression(unary.argument);
72
+ if (typeof arg === 'number') {
73
+ return -arg;
74
+ }
75
+ }
76
+ if (unary.operator === '+') {
77
+ const arg = evaluateExpression(unary.argument);
78
+ if (typeof arg === 'number') {
79
+ return arg;
80
+ }
81
+ }
82
+ throw new Error(`Cannot evaluate unary: ${unary.operator}`);
83
+ }
84
+ case 'ObjectExpression': {
85
+ const obj = {};
86
+ const { properties } = node;
87
+ for (const prop of properties) {
88
+ if (prop.type !== 'ObjectProperty') {
89
+ continue;
90
+ }
91
+ let key = null;
92
+ if (prop.key.type === 'Identifier') {
93
+ key = prop.key.name;
94
+ }
95
+ else if (prop.key.type === 'StringLiteral') {
96
+ key = prop.key.value;
97
+ }
98
+ else if (prop.key.type === 'NumericLiteral') {
99
+ key = String(prop.key.value);
100
+ }
101
+ if (key !== null) {
102
+ obj[key] = evaluateExpression(prop.value);
103
+ }
104
+ }
105
+ return obj;
106
+ }
107
+ case 'ArrayExpression': {
108
+ const { elements } = node;
109
+ return elements.map((el) => {
110
+ if (el === null) {
111
+ return null;
112
+ }
113
+ return evaluateExpression(el);
114
+ });
115
+ }
116
+ case 'TemplateLiteral': {
117
+ const tl = node;
118
+ if (tl.expressions.length === 0) {
119
+ return tl.quasis[0].value.cooked;
120
+ }
121
+ throw new Error('Cannot evaluate template literal with expressions');
122
+ }
123
+ default:
124
+ throw new Error(`Cannot evaluate AST node: ${node.type}`);
125
+ }
126
+ };
127
+ const readVisualControlValues = (file) => {
128
+ const values = [];
129
+ recast.types.visit(file.program, {
130
+ visitCallExpression(path) {
131
+ const { node } = path;
132
+ if (node.callee.type !== 'Identifier' ||
133
+ node.callee.name !== 'visualControl') {
134
+ return this.traverse(path);
135
+ }
136
+ const firstArg = node.arguments[0];
137
+ const id = getStringValue(firstArg);
138
+ if (id === null) {
139
+ return this.traverse(path);
140
+ }
141
+ if (node.arguments.length < 2) {
142
+ return this.traverse(path);
143
+ }
144
+ const valueNode = node.arguments[1];
145
+ try {
146
+ if (valueNode.type === 'Identifier' &&
147
+ valueNode.name === 'undefined') {
148
+ values.push({ id, value: null, isUndefined: true });
149
+ }
150
+ else {
151
+ values.push({
152
+ id,
153
+ value: evaluateExpression(valueNode),
154
+ isUndefined: false,
155
+ });
156
+ }
157
+ }
158
+ catch (_a) {
159
+ // Skip values we can't evaluate statically
160
+ }
161
+ return this.traverse(path);
162
+ },
163
+ });
164
+ return values;
165
+ };
166
+ exports.readVisualControlValues = readVisualControlValues;
@@ -4,4 +4,7 @@ export declare const updateDefaultProps: ({ input, compositionId, newDefaultProp
4
4
  compositionId: string;
5
5
  newDefaultProps: Record<string, unknown>;
6
6
  enumPaths: EnumPath[];
7
- }) => Promise<Promise<Promise<Promise<string>>>>;
7
+ }) => Promise<{
8
+ output: string;
9
+ formatted: boolean;
10
+ }>;
@@ -36,9 +36,37 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.updateDefaultProps = void 0;
37
37
  const studio_shared_1 = require("@remotion/studio-shared");
38
38
  const recast = __importStar(require("recast"));
39
+ const format_inline_content_1 = require("./format-inline-content");
39
40
  const parse_ast_1 = require("./parse-ast");
41
+ // Recast uses tabWidth=4 for column counting, so columns don't
42
+ // correspond to character indices when tabs are present.
43
+ // This converts a recast loc (line/column) to a character offset.
44
+ const RECAST_TAB_WIDTH = 4;
45
+ const recastLocToOffset = (input, loc) => {
46
+ const lines = input.split('\n');
47
+ let offset = 0;
48
+ for (let i = 0; i < loc.line - 1; i++) {
49
+ offset += lines[i].length + 1;
50
+ }
51
+ // Convert recast's tab-expanded column to character index
52
+ const line = lines[loc.line - 1];
53
+ let col = 0;
54
+ for (let i = 0; i < line.length; i++) {
55
+ if (col >= loc.column) {
56
+ return offset + i;
57
+ }
58
+ col += line[i] === '\t' ? RECAST_TAB_WIDTH : 1;
59
+ }
60
+ return offset + line.length;
61
+ };
40
62
  const updateDefaultProps = async ({ input, compositionId, newDefaultProps, enumPaths, }) => {
41
63
  const ast = (0, parse_ast_1.parseAst)(input);
64
+ const stringified = (0, studio_shared_1.stringifyDefaultProps)({
65
+ props: newDefaultProps,
66
+ enumPaths,
67
+ });
68
+ let replaceStart;
69
+ let replaceEnd;
42
70
  recast.types.visit(ast, {
43
71
  visitJSXElement(path) {
44
72
  var _a, _b;
@@ -110,33 +138,34 @@ const updateDefaultProps = async ({ input, compositionId, newDefaultProps, enumP
110
138
  defaultPropsValue.type !== 'TSAsExpression') {
111
139
  throw new Error(`\`defaultProps\` prop must be a hardcoded value in the <Composition/> tag with the ID "${compositionId}".`);
112
140
  }
113
- defaultPropsAttr.value.expression = recast.types.builders.identifier((0, studio_shared_1.stringifyDefaultProps)({ props: newDefaultProps, enumPaths }));
141
+ // Capture source positions for direct string replacement
142
+ // instead of modifying the AST and serializing (avoids recast artifacts)
143
+ const valueLoc = defaultPropsAttr.value.loc;
144
+ if (!valueLoc) {
145
+ throw new Error('Could not determine source location of defaultProps');
146
+ }
147
+ replaceStart = recastLocToOffset(input, valueLoc.start);
148
+ replaceEnd = recastLocToOffset(input, valueLoc.end);
114
149
  this.traverse(path); // Continue traversing the AST
115
150
  },
116
151
  });
117
- let prettier = null;
118
- try {
119
- prettier = await Promise.resolve().then(() => __importStar(require('prettier')));
120
- }
121
- catch (_a) {
122
- throw new Error('Prettier cannot be found in the current project.');
123
- }
124
- const { format, resolveConfig, resolveConfigFile } = prettier;
125
- const configFilePath = await resolveConfigFile();
126
- if (!configFilePath) {
127
- throw new Error('The Prettier config file was not found');
128
- }
129
- const prettierConfig = await resolveConfig(configFilePath);
130
- if (!prettierConfig) {
131
- throw new Error('The Prettier config file was not found. For this feature, the "prettier" package must be installed and a .prettierrc file must exist.');
152
+ if (replaceStart === undefined || replaceEnd === undefined) {
153
+ throw new Error(`Could not find defaultProps for composition "${compositionId}"`);
132
154
  }
133
- const finalfile = (0, parse_ast_1.serializeAst)(ast);
134
- const prettified = await format(finalfile, {
135
- ...prettierConfig,
136
- filepath: 'test.tsx',
137
- plugins: [],
155
+ // linePrefix includes the JSX container opening brace
156
+ const lineStart = input.lastIndexOf('\n', replaceStart) + 1;
157
+ const linePrefix = input.substring(lineStart, replaceStart + 1);
158
+ const { formatted, didFormat } = await (0, format_inline_content_1.formatInlineContent)({
159
+ inlineContent: stringified,
160
+ linePrefix,
138
161
  endOfLine: 'auto',
139
162
  });
140
- return prettified;
163
+ // Replace the JSX expression container in the original input
164
+ const output = input.substring(0, replaceStart) +
165
+ '{' +
166
+ formatted +
167
+ '}' +
168
+ input.substring(replaceEnd);
169
+ return { output, formatted: didFormat };
141
170
  };
142
171
  exports.updateDefaultProps = updateDefaultProps;
@@ -1,9 +1,26 @@
1
- type FileChangeType = 'created' | 'deleted' | 'changed';
2
- export declare const installFileWatcher: ({ file, onChange, }: {
3
- file: string;
4
- onChange: (type: FileChangeType) => void;
5
- }) => {
6
- exists: boolean;
7
- unwatch: () => void;
1
+ export type FileChangeEvent = {
2
+ type: 'created';
3
+ content: string;
4
+ } | {
5
+ type: 'deleted';
6
+ } | {
7
+ type: 'changed';
8
+ content: string;
8
9
  };
10
+ type OnChange = (event: FileChangeEvent) => void;
11
+ export type FileWatcherRegistry = {
12
+ installFileWatcher: (options: {
13
+ file: string;
14
+ onChange: OnChange;
15
+ }) => {
16
+ exists: boolean;
17
+ unwatch: () => void;
18
+ };
19
+ writeFileAndNotifyFileWatchers: (file: string, content: string) => void;
20
+ };
21
+ export declare const createFileWatcherRegistry: () => FileWatcherRegistry;
22
+ export declare const setFileWatcherRegistry: (registry: FileWatcherRegistry) => () => void;
23
+ export declare const getFileWatcherRegistry: () => FileWatcherRegistry;
24
+ export declare const installFileWatcher: FileWatcherRegistry['installFileWatcher'];
25
+ export declare const writeFileAndNotifyFileWatchers: FileWatcherRegistry['writeFileAndNotifyFileWatchers'];
9
26
  export {};
@@ -1,40 +1,159 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
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
+ })();
5
35
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.installFileWatcher = void 0;
7
- const node_fs_1 = __importDefault(require("node:fs"));
8
- const installFileWatcher = ({ file, onChange, }) => {
9
- const existedAtBeginning = node_fs_1.default.existsSync(file);
10
- let existedBefore = existedAtBeginning;
11
- const listener = () => {
12
- const existsNow = node_fs_1.default.existsSync(file);
13
- if (!existedBefore && existsNow) {
14
- onChange('created');
15
- existedBefore = true;
16
- return;
36
+ exports.writeFileAndNotifyFileWatchers = exports.installFileWatcher = exports.getFileWatcherRegistry = exports.setFileWatcherRegistry = exports.createFileWatcherRegistry = void 0;
37
+ const node_fs_1 = __importStar(require("node:fs"));
38
+ const createFileWatcherRegistry = () => {
39
+ const sharedWatchers = new Map();
40
+ const _installFileWatcher = ({ file, onChange, }) => {
41
+ const existing = sharedWatchers.get(file);
42
+ if (existing) {
43
+ existing.subscribers.add(onChange);
44
+ return {
45
+ exists: existing.existedBefore,
46
+ unwatch: () => {
47
+ existing.subscribers.delete(onChange);
48
+ if (existing.subscribers.size === 0) {
49
+ existing.unwatch();
50
+ sharedWatchers.delete(file);
51
+ }
52
+ },
53
+ };
17
54
  }
18
- if (existedBefore && !existsNow) {
19
- onChange('deleted');
20
- existedBefore = false;
21
- return;
22
- }
23
- if (existsNow) {
24
- if (typeof Deno !== 'undefined') {
25
- // Deno always goes here, even if the file has not changed.
26
- // Don't support this for now.
55
+ const existedAtBeginning = node_fs_1.default.existsSync(file);
56
+ const shared = {
57
+ subscribers: new Set([onChange]),
58
+ existedBefore: existedAtBeginning,
59
+ lastKnownContent: null,
60
+ unwatch: () => {
61
+ node_fs_1.default.unwatchFile(file, listener);
62
+ },
63
+ };
64
+ const listener = () => {
65
+ const existsNow = node_fs_1.default.existsSync(file);
66
+ let event = null;
67
+ if (!shared.existedBefore && existsNow) {
68
+ const content = (0, node_fs_1.readFileSync)(file, 'utf-8');
69
+ shared.existedBefore = true;
70
+ shared.lastKnownContent = content;
71
+ event = { type: 'created', content };
72
+ }
73
+ else if (shared.existedBefore && !existsNow) {
74
+ shared.existedBefore = false;
75
+ shared.lastKnownContent = null;
76
+ event = { type: 'deleted' };
77
+ }
78
+ else if (existsNow) {
79
+ if (typeof Deno !== 'undefined') {
80
+ return;
81
+ }
82
+ const content = (0, node_fs_1.readFileSync)(file, 'utf-8');
83
+ if (shared.lastKnownContent !== null &&
84
+ content === shared.lastKnownContent) {
85
+ return;
86
+ }
87
+ shared.lastKnownContent = content;
88
+ event = { type: 'changed', content };
89
+ }
90
+ if (!event) {
27
91
  return;
28
92
  }
29
- onChange('changed');
93
+ for (const subscriber of shared.subscribers) {
94
+ subscriber(event);
95
+ }
96
+ };
97
+ node_fs_1.default.watchFile(file, { interval: 100 }, listener);
98
+ sharedWatchers.set(file, shared);
99
+ return {
100
+ exists: existedAtBeginning,
101
+ unwatch: () => {
102
+ shared.subscribers.delete(onChange);
103
+ if (shared.subscribers.size === 0) {
104
+ shared.unwatch();
105
+ sharedWatchers.delete(file);
106
+ }
107
+ },
108
+ };
109
+ };
110
+ const _writeFileAndNotifyFileWatchers = (file, content) => {
111
+ (0, node_fs_1.writeFileSync)(file, content);
112
+ const shared = sharedWatchers.get(file);
113
+ if (!shared) {
114
+ return;
115
+ }
116
+ shared.lastKnownContent = content;
117
+ shared.existedBefore = true;
118
+ for (const subscriber of shared.subscribers) {
119
+ subscriber({ type: 'changed', content });
30
120
  }
31
121
  };
32
- node_fs_1.default.watchFile(file, { interval: 100 }, listener);
33
122
  return {
34
- exists: existedAtBeginning,
35
- unwatch: () => {
36
- node_fs_1.default.unwatchFile(file, listener);
37
- },
123
+ installFileWatcher: _installFileWatcher,
124
+ writeFileAndNotifyFileWatchers: _writeFileAndNotifyFileWatchers,
38
125
  };
39
126
  };
127
+ exports.createFileWatcherRegistry = createFileWatcherRegistry;
128
+ let currentRegistry = null;
129
+ const setFileWatcherRegistry = (registry) => {
130
+ currentRegistry = registry;
131
+ return () => {
132
+ currentRegistry = null;
133
+ };
134
+ };
135
+ exports.setFileWatcherRegistry = setFileWatcherRegistry;
136
+ const getFileWatcherRegistry = () => {
137
+ if (!currentRegistry) {
138
+ throw new Error('File watcher registry not initialized');
139
+ }
140
+ return currentRegistry;
141
+ };
142
+ exports.getFileWatcherRegistry = getFileWatcherRegistry;
143
+ const installFileWatcher = (options) => {
144
+ if (!currentRegistry) {
145
+ return {
146
+ exists: false,
147
+ unwatch: () => { },
148
+ };
149
+ }
150
+ return (0, exports.getFileWatcherRegistry)().installFileWatcher(options);
151
+ };
40
152
  exports.installFileWatcher = installFileWatcher;
153
+ const writeFileAndNotifyFileWatchers = (file, content) => {
154
+ if (!currentRegistry) {
155
+ return;
156
+ }
157
+ (0, exports.getFileWatcherRegistry)().writeFileAndNotifyFileWatchers(file, content);
158
+ };
159
+ exports.writeFileAndNotifyFileWatchers = writeFileAndNotifyFileWatchers;