@tsplc/build 0.2.9 → 0.2.11

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/package.json CHANGED
@@ -15,7 +15,7 @@
15
15
  "url": "git+https://github.com/livingforjesus/tsplc.git",
16
16
  "directory": "packages/build"
17
17
  },
18
- "version": "0.2.9",
18
+ "version": "0.2.11",
19
19
  "type": "module",
20
20
  "exports": {
21
21
  ".": {
@@ -43,8 +43,8 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "zod": "4.3.6",
46
- "@tsplc/config": "0.2.9",
47
- "@tsplc/typegen": "0.2.9"
46
+ "@tsplc/config": "0.2.11",
47
+ "@tsplc/typegen": "0.2.11"
48
48
  },
49
49
  "bin": {
50
50
  "tsplc-build": "./dist/cli.js"
@@ -186,16 +186,16 @@ def run_online(engine, request, prepared=None):
186
186
  with io.open(progress_path, 'a', encoding='utf-8') as output:
187
187
  output.write(u'{0}Z {1}\n'.format(time.strftime('%Y-%m-%dT%H:%M:%S', time.gmtime()), stage))
188
188
  progress('Starting; processor count {0}'.format(Environment.ProcessorCount))
189
- # A download starts from the template; online changes reuse the downloaded project, which has its controller and tree.
190
- if prepared is not None and (request['operation'] == 'download' or
191
- os.path.normcase(prepared.path) != os.path.normcase(request['project'])):
189
+ previous = request.get('baseline')
190
+ # Operations reuse the downloaded project, which has its controller and tree; a download without one starts from the template.
191
+ if prepared is not None and os.path.normcase(prepared.path) != os.path.normcase(request['project']):
192
192
  prepared.close()
193
193
  prepared = None
194
- if request['operation'] == 'download':
195
- project, pinned, generated = open_template(engine, request, request['project'], request.get('devices'), request['caches'], progress)
196
- elif prepared is not None:
194
+ if prepared is not None:
197
195
  progress('Using the project opened ahead')
198
196
  project, pinned, generated = prepared, {}, None
197
+ elif request['operation'] == 'download' and previous is None:
198
+ project, pinned, generated = open_template(engine, request, request['project'], request.get('devices'), request['caches'], progress)
199
199
  else:
200
200
  progress('Opening project')
201
201
  project = engine.projects.open(request['project'], primary=True)
@@ -214,7 +214,6 @@ def run_online(engine, request, prepared=None):
214
214
  raise RuntimeError('Initial controller user requires an explicit download and credentials')
215
215
  online_device.set_credentials_for_initial_user(*credentials)
216
216
  progress('Controller connection configured')
217
- previous = request.get('baseline')
218
217
  if request['operation'] != 'download':
219
218
  online_app = engine.online.create_online_application(application)
220
219
  core = core_application(online_app)
@@ -238,8 +237,8 @@ def run_online(engine, request, prepared=None):
238
237
  'state': state, 'values': {expression: online_app.read_value(expression) for expression in request.get('read', [])}}
239
238
  finally:
240
239
  core.Logout()
241
- # A download starts from the template; an online change writes only what changed since the baseline.
242
- held = previous['objects'] if request['operation'] == 'online-change' else None
240
+ # Only what changed since the baseline is written; a template gets every object.
241
+ held = previous['objects'] if previous is not None else None
243
242
  gone = reconcile(application, held, request['objects']) if held is not None else []
244
243
  import_objects(engine, application, request, held, progress)
245
244
  for obj in gone:
@@ -196,12 +196,23 @@ class OnlineLifecycleTests(unittest.TestCase):
196
196
  self.engine.projects.open.assert_not_called()
197
197
  self.project.close.assert_called_once()
198
198
 
199
- def test_a_download_or_another_project_closes_the_one_opened_ahead(self):
199
+ def test_another_project_than_the_one_opened_ahead_closes_it(self):
200
200
  stale = SimpleNamespace(path='Other.project', close=Mock())
201
201
  codesys_online.run_online(self.engine, self.request, stale)
202
202
  stale.close.assert_called_once()
203
203
  codesys_online.open_template.assert_called_once()
204
204
 
205
+ def test_download_with_a_baseline_imports_only_changes_into_the_project_opened_ahead_and_rebuilds(self):
206
+ self.request.update(baseline={
207
+ 'applicationGuid': self.app.guid, 'controllerIds': self.ids, 'objects': [],
208
+ })
209
+ self.project.path = 'Application.project'
210
+ codesys_online.run_online(self.engine, self.request, self.project)
211
+ codesys_online.open_template.assert_not_called()
212
+ self.engine.projects.open.assert_not_called()
213
+ self.assertIs(codesys_online.import_objects.call_args.args[3], self.request['baseline']['objects'])
214
+ self.assertEqual(self.events, ['import', 'clean', 'compile', 'generate', 'online-wrapper'])
215
+
205
216
  def test_full_download_rejects_library_errors_reported_after_transfer(self):
206
217
  def report_library_error(core):
207
218
  self.messages.append(SimpleNamespace(text="Could not open library '#VisuShared'."))
@@ -140,24 +140,26 @@ export const runControllerSession = async (
140
140
  const resultPath = resolve(root, "result.json");
141
141
  await rm(resultPath, { force: true });
142
142
  const templateHash = sha256(await readFile(config.template));
143
- // A different controller or device tree needs a fresh download from the template.
143
+ // A different image, template, controller or device tree needs a fresh download from the template.
144
144
  const target = controllerTarget(config, config.online);
145
+ const expected = { imageId: options.imageId, templateHash, target };
146
+ // A download reuses the baseline when it still fits, and imports only what changed since.
145
147
  const previous =
146
148
  operation === "download"
147
- ? undefined
148
- : await readBaseline(root, { imageId: options.imageId, templateHash, target });
149
+ ? await readBaseline(root, expected).catch(() => undefined)
150
+ : await readBaseline(root, expected);
149
151
  const baseline = previous?.baseline;
150
152
  if (options.initializeUser && operation !== "download") {
151
153
  throw new Error("Initial user creation requires an explicit download");
152
154
  }
153
155
  const opened = worker?.prepared;
154
156
  const prepared =
155
- operation === "online-change" && opened?.baseline === baseline?.generation ? opened : undefined;
157
+ operation !== "inspect" && opened?.baseline === baseline?.generation ? opened : undefined;
156
158
  const generation = prepared?.generation ?? randomUUID();
157
159
  const candidate = resolve(root, "generations", generation);
158
160
  const project = resolve(candidate, "project", "Application.project");
159
161
  await mkdir(resolve(candidate, "project"), { recursive: true });
160
- // A download saves the template here, with its controller and device tree.
162
+ // Opens a copy of the baseline unless the worker opened one ahead; a download without a baseline saves the template here.
161
163
  if (previous && !prepared) {
162
164
  await cp(previous.project, resolve(candidate, "project"), {
163
165
  recursive: true,
@@ -183,17 +185,18 @@ export const runControllerSession = async (
183
185
  const sources = operation === "inspect" ? undefined : await loadSources(config);
184
186
  const request = {
185
187
  libraryReferences: config.libraryReferences,
186
- // A download starts from the template; an online change keeps the downloaded project's tree.
187
- ...(operation === "download" && {
188
- template: await toWindowsPath(config.template, config.codesys.wine),
189
- templateHash,
190
- // Kept across jobs: generating a device tree takes minutes, and the same inputs make the same one.
191
- caches: await toWindowsPath(root, config.codesys.wine),
192
- ...(config.deviceTree && {
193
- deviceTree: await writeDeviceTree(config.deviceTree, jobDirectory),
194
- devices: await toWindowsPath(jobDirectory, config.codesys.wine),
188
+ // Only a download without a baseline starts from the template; the others keep the downloaded project's tree.
189
+ ...(operation === "download" &&
190
+ !previous && {
191
+ template: await toWindowsPath(config.template, config.codesys.wine),
192
+ templateHash,
193
+ // Kept across jobs: generating a device tree takes minutes, and the same inputs make the same one.
194
+ caches: await toWindowsPath(root, config.codesys.wine),
195
+ ...(config.deviceTree && {
196
+ deviceTree: await writeDeviceTree(config.deviceTree, jobDirectory),
197
+ devices: await toWindowsPath(jobDirectory, config.codesys.wine),
198
+ }),
195
199
  }),
196
- }),
197
200
  libraries: await Promise.all(
198
201
  (config.libraries ?? []).map(async (library) => ({
199
202
  ...library,