@wpmoo/toolkit 0.9.2 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,25 +23,26 @@ WPMoo Toolkit is an independent project and is not affiliated with, endorsed by,
23
23
  - Optionally copy project-local Agent Skills from `wpmoo-org/odoo-skills` into generated environments.
24
24
  - Use either a guided terminal cockpit or direct CLI commands for the same lifecycle tasks.
25
25
 
26
- ## Requirements
26
+ ## Prerequisites
27
27
 
28
- - Node.js `>=20.17`
28
+ - Node.js `20.17+`
29
29
  - Git
30
- - Docker and Docker Compose for generated environment runtime commands
31
- - GitHub CLI (`gh`) is optional. Use it for repository discovery, repository creation, and deeper diagnostics.
30
+ - Docker + Docker Compose for generated environment runtime commands
31
+ - For GitHub-connected setup, install and authenticate GitHub CLI:
32
+
33
+ ```bash
34
+ brew install gh
35
+ gh auth login
36
+ ```
37
+
38
+ GitHub CLI (`gh`) is optional.
39
+ WPMoo uses `gh` to inspect source/dev repositories and to create missing repos during setup. It also uses repository inspection to detect existing non-empty dev repositories and avoid overwriting them; if you do not want GitHub checks, keep setup local-only.
32
40
 
33
41
  The wizard currently offers Odoo `19.0`, `18.0`, `17.0`, and `16.0`. Generated
34
42
  environments now use the compact compose layout (`compose.yaml` with
35
43
  `compose/<env>.yaml` overlays). Legacy root-level
36
44
  `docker-compose_<version>.yml` layouts are still supported for compatibility.
37
45
 
38
- Set up GitHub CLI only when you want WPMoo to discover your personal account and organizations or create missing repositories from the interactive wizard:
39
-
40
- ```bash
41
- brew install gh
42
- gh auth login
43
- ```
44
-
45
46
  ## Quick Start
46
47
 
47
48
  Run the guided wizard from a workspace directory:
package/dist/cli.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { realpathSync } from 'node:fs';
3
+ import { rm, rename } from 'node:fs/promises';
3
4
  import { basename, relative, resolve } from 'node:path';
4
5
  import { fileURLToPath, pathToFileURL } from 'node:url';
5
6
  import { commandFromArgs, isHelpRequested, isVersionRequested, optionsFromArgs, parseArgs, stripInternalFlags, } from './args.js';
@@ -21,7 +22,9 @@ import { inferGitHubOwner, inferRepoPath, normalizeRepositoryUrl } from './repo-
21
22
  import { addModuleRepo, listModuleRepos, removeModuleRepo } from './repo-actions.js';
22
23
  import { renderSafeResetPreview, safeResetEnvironment } from './safe-reset.js';
23
24
  import { listSources, renderSourceList, sourceListJson, sourceSyncJson, syncSources, } from './source-actions.js';
24
- import { checkGitHubRepositories, createGitHubRepositories, manualCreateCommands, repositoryPreflightAvailable, } from './repository-preflight.js';
25
+ import { backupTargetPath, expectedTargetConfirmation, inspectEnvironmentTarget, renderExistingEnvironmentSummary, renderForeignEnvironmentTargetWarning, } from './environment-target-preflight.js';
26
+ import { getGitHubPrerequisiteStatus, renderGitHubPrerequisiteGuidance, } from './github-prerequisites.js';
27
+ import { checkGitHubRepositories, createGitHubRepositories, manualCreateCommands, } from './repository-preflight.js';
25
28
  import { scaffold } from './scaffold.js';
26
29
  import { confirmPrompt, introPrompt, isPromptCancel, notePrompt, outroPrompt, selectPrompt, textPrompt } from './prompts/index.js';
27
30
  import { renderBanner } from './templates.js';
@@ -223,6 +226,99 @@ async function selectCockpitCommandFromMenu() {
223
226
  }
224
227
  return selection.command;
225
228
  }
229
+ async function resolveEnvironmentTargetFromPrompts(product, cancelAction) {
230
+ const defaultTarget = `./${product}_dev`;
231
+ while (true) {
232
+ const target = resolve(asString(await textPrompt({
233
+ message: 'Environment folder',
234
+ placeholder: defaultTarget,
235
+ defaultValue: defaultTarget,
236
+ initialValue: defaultTarget,
237
+ }), defaultTarget, cancelAction));
238
+ const state = await inspectEnvironmentTarget(target);
239
+ if (state.kind === 'missing_target') {
240
+ return { kind: 'create', target };
241
+ }
242
+ if (state.kind === 'foreign_target') {
243
+ notePrompt(renderForeignEnvironmentTargetWarning(state), 'Environment folder exists');
244
+ const action = await selectPrompt({
245
+ message: 'What do you want to do?',
246
+ options: [
247
+ { value: 'choose-another-folder', label: 'Choose another folder' },
248
+ { value: 'cancel', label: 'Cancel' },
249
+ ],
250
+ initialValue: 'choose-another-folder',
251
+ });
252
+ handleCancel(action, cancelAction);
253
+ if (action === 'choose-another-folder') {
254
+ continue;
255
+ }
256
+ return { kind: 'cancelled' };
257
+ }
258
+ notePrompt(renderExistingEnvironmentSummary(state), 'Existing environment');
259
+ const action = await selectPrompt({
260
+ message: 'This environment folder already exists. What do you want to do?',
261
+ options: [
262
+ { value: 'update-existing', label: 'Update existing environment' },
263
+ { value: 'reinstall-environment', label: 'Reinstall environment from backup' },
264
+ { value: 'delete-environment', label: 'Delete environment' },
265
+ { value: 'cancel', label: 'Cancel' },
266
+ ],
267
+ initialValue: 'update-existing',
268
+ });
269
+ handleCancel(action, cancelAction);
270
+ if (action === 'update-existing') {
271
+ await safeResetEnvironment({ target, stage: true });
272
+ return { kind: 'updated', target };
273
+ }
274
+ if (action === 'reinstall-environment') {
275
+ const backup = backupTargetPath(target);
276
+ await rename(target, backup);
277
+ notePrompt(`Existing environment moved to:\n${backup}`, 'Environment backup');
278
+ return { kind: 'create', target };
279
+ }
280
+ if (action === 'delete-environment') {
281
+ const expectedName = basename(target);
282
+ const confirmation = await textPrompt({
283
+ message: `Type ${expectedName} to confirm deletion`,
284
+ validate: (value) => (expectedTargetConfirmation(target, value) ? undefined : `Type ${expectedName} exactly to confirm.`),
285
+ });
286
+ handleCancel(confirmation, cancelAction);
287
+ if (!expectedTargetConfirmation(target, String(confirmation))) {
288
+ throw new Error(`Deletion confirmation did not match ${expectedName}.`);
289
+ }
290
+ await rm(target, { recursive: true, force: true });
291
+ return { kind: 'deleted', target };
292
+ }
293
+ return { kind: 'cancelled' };
294
+ }
295
+ }
296
+ async function promptGitHubPrerequisites(cancelAction) {
297
+ while (true) {
298
+ const status = await getGitHubPrerequisiteStatus();
299
+ if (status.status === 'ready') {
300
+ return true;
301
+ }
302
+ notePrompt(renderGitHubPrerequisiteGuidance(status), 'GitHub prerequisites');
303
+ const action = await selectPrompt({
304
+ message: 'GitHub repository prerequisites',
305
+ options: [
306
+ { value: 'retry', label: 'Retry prerequisite check' },
307
+ { value: 'continue-local-only', label: 'Continue local-only' },
308
+ { value: 'cancel', label: 'Cancel' },
309
+ ],
310
+ initialValue: 'retry',
311
+ });
312
+ handleCancel(action, cancelAction);
313
+ if (action === 'retry') {
314
+ continue;
315
+ }
316
+ if (action === 'continue-local-only') {
317
+ return false;
318
+ }
319
+ throw new Error('GitHub prerequisites were not completed.');
320
+ }
321
+ }
226
322
  async function optionsFromPrompts(showIntro = true, cancelAction = 'exit') {
227
323
  if (showIntro) {
228
324
  introPrompt('Create Odoo dev environment');
@@ -232,13 +328,11 @@ async function optionsFromPrompts(showIntro = true, cancelAction = 'exit') {
232
328
  placeholder: 'odoo_sample_module',
233
329
  validate: (value) => (value.trim() ? undefined : 'Enter a product/module slug.'),
234
330
  }), 'odoo_sample_module', cancelAction);
235
- const defaultTarget = `./${product}_dev`;
236
- const target = resolve(asString(await textPrompt({
237
- message: 'Environment folder',
238
- placeholder: defaultTarget,
239
- defaultValue: defaultTarget,
240
- initialValue: defaultTarget,
241
- }), defaultTarget, cancelAction));
331
+ const targetResult = await resolveEnvironmentTargetFromPrompts(product, cancelAction);
332
+ if (targetResult.kind !== 'create') {
333
+ return targetResult;
334
+ }
335
+ const { target } = targetResult;
242
336
  const connectGitHub = await selectPrompt({
243
337
  message: 'Connect this environment to Git/GitHub now?',
244
338
  options: [
@@ -249,7 +343,8 @@ async function optionsFromPrompts(showIntro = true, cancelAction = 'exit') {
249
343
  });
250
344
  handleCancel(connectGitHub, cancelAction);
251
345
  let selectedGitHubOwner;
252
- if (connectGitHub) {
346
+ const useGitHub = Boolean(connectGitHub) && (await promptGitHubPrerequisites(cancelAction));
347
+ if (useGitHub) {
253
348
  notePrompt(renderRepositorySetupNote(product), 'Repository setup');
254
349
  selectedGitHubOwner = await selectDefaultGitHubOwner(cancelAction);
255
350
  }
@@ -272,23 +367,26 @@ async function optionsFromPrompts(showIntro = true, cancelAction = 'exit') {
272
367
  handleCancel(installAgentSkills, cancelAction);
273
368
  return Boolean(installAgentSkills);
274
369
  }
275
- if (!connectGitHub) {
370
+ if (!useGitHub) {
276
371
  const installAgentSkills = await promptInstallAgentSkills();
277
372
  return {
278
- product,
279
- odooVersion,
280
- engine: 'compose',
281
- devRepo: basename(target),
282
- devRepoUrl: target,
283
- sourceRepos: [],
284
- target,
285
- dryRun: false,
286
- initEmptyRepos: false,
287
- stage: false,
288
- agentSkillsTemplateUrl: installAgentSkills ? defaultAgentSkillsTemplateUrl : undefined,
289
- createMissingRepos: false,
290
- repoVisibility: 'private',
291
- skipSubmodules: true,
373
+ kind: 'create',
374
+ options: {
375
+ product,
376
+ odooVersion,
377
+ engine: 'compose',
378
+ devRepo: basename(target),
379
+ devRepoUrl: target,
380
+ sourceRepos: [],
381
+ target,
382
+ dryRun: false,
383
+ initEmptyRepos: false,
384
+ stage: false,
385
+ agentSkillsTemplateUrl: installAgentSkills ? defaultAgentSkillsTemplateUrl : undefined,
386
+ createMissingRepos: false,
387
+ repoVisibility: 'private',
388
+ skipSubmodules: true,
389
+ },
292
390
  };
293
391
  }
294
392
  const detectedDevRepoUrl = await getOriginUrl(realGit, target);
@@ -343,19 +441,22 @@ async function optionsFromPrompts(showIntro = true, cancelAction = 'exit') {
343
441
  });
344
442
  handleCancel(initEmpty, cancelAction);
345
443
  return {
346
- product,
347
- odooVersion,
348
- engine: 'compose',
349
- devRepo: inferRepoPath(devRepoUrl),
350
- devRepoUrl,
351
- sourceRepos,
352
- target,
353
- dryRun: false,
354
- initEmptyRepos: Boolean(initEmpty),
355
- stage: true,
356
- agentSkillsTemplateUrl: Boolean(installAgentSkills) ? defaultAgentSkillsTemplateUrl : undefined,
357
- createMissingRepos: false,
358
- repoVisibility: 'private',
444
+ kind: 'create',
445
+ options: {
446
+ product,
447
+ odooVersion,
448
+ engine: 'compose',
449
+ devRepo: inferRepoPath(devRepoUrl),
450
+ devRepoUrl,
451
+ sourceRepos,
452
+ target,
453
+ dryRun: false,
454
+ initEmptyRepos: Boolean(initEmpty),
455
+ stage: true,
456
+ agentSkillsTemplateUrl: Boolean(installAgentSkills) ? defaultAgentSkillsTemplateUrl : undefined,
457
+ createMissingRepos: false,
458
+ repoVisibility: 'private',
459
+ },
359
460
  };
360
461
  }
361
462
  async function addRepoOptionsFromArgs(argv) {
@@ -403,9 +504,10 @@ async function addRepoOptionsFromPrompts(showIntro = true, cancelAction = 'exit'
403
504
  };
404
505
  }
405
506
  async function ensureAddRepoGitHubRepository(options, cancelAction = 'exit') {
406
- if (!(await repositoryPreflightAvailable())) {
507
+ const prerequisiteStatus = await getGitHubPrerequisiteStatus();
508
+ if (prerequisiteStatus.status !== 'ready') {
407
509
  notePrompt([
408
- 'GitHub CLI (`gh`) is not available or not authenticated.',
510
+ renderGitHubPrerequisiteGuidance(prerequisiteStatus),
409
511
  'The source repo will be used as-is. If it does not exist, create it first or authenticate gh.',
410
512
  ].join('\n'), 'Repository check skipped');
411
513
  return;
@@ -711,23 +813,29 @@ async function ensureGitHubRepositories(options, interactive) {
711
813
  if (!interactive && !options.createMissingRepos) {
712
814
  return;
713
815
  }
714
- if (!(await repositoryPreflightAvailable())) {
715
- const message = [
716
- 'GitHub CLI (`gh`) is not available or not authenticated.',
717
- 'Install and authenticate it to auto-create missing GitHub repositories:',
718
- '',
719
- 'brew install gh',
720
- 'gh auth login',
721
- ].join('\n');
722
- if (options.createMissingRepos) {
816
+ const prerequisiteStatus = await getGitHubPrerequisiteStatus();
817
+ if (prerequisiteStatus.status !== 'ready') {
818
+ const message = renderGitHubPrerequisiteGuidance(prerequisiteStatus);
819
+ if (options.createMissingRepos || !interactive) {
723
820
  throw new Error(message);
724
821
  }
725
- if (interactive) {
726
- notePrompt(message, 'Repository check skipped');
727
- }
822
+ notePrompt(message, 'GitHub prerequisites');
728
823
  return;
729
824
  }
730
- const { accessible, inaccessible: missing } = await checkGitHubRepositories(options);
825
+ const { accessible, inaccessible: missing, blocked } = await checkGitHubRepositories(options);
826
+ if (blocked.length > 0) {
827
+ const blockedList = blocked
828
+ .map((repository) => `- ${repository.label}: ${repository.slug}`)
829
+ .join('\n');
830
+ notePrompt([
831
+ 'These dev environment repositories already contain files and cannot be used automatically:',
832
+ '',
833
+ blockedList,
834
+ '',
835
+ 'Choose another dev repository, empty the existing repository, or cancel and handle it manually.',
836
+ ].join('\n'), 'Repository check blocked');
837
+ throw new Error(`Dev environment repository is non-empty or could not be verified: ${blocked.map((repo) => repo.slug).join(', ')}`);
838
+ }
731
839
  if (interactive && accessible.length > 0) {
732
840
  notePrompt([
733
841
  'These GitHub repositories already exist and are accessible:',
@@ -773,6 +881,50 @@ async function ensureGitHubRepositories(options, interactive) {
773
881
  handleCancel(visibility, 'exit');
774
882
  await createGitHubRepositories(missing, visibility);
775
883
  }
884
+ async function ensureNonInteractiveCreateTarget(options) {
885
+ if (options.dryRun) {
886
+ return;
887
+ }
888
+ const state = await inspectEnvironmentTarget(options.target);
889
+ if (state.kind === 'missing_target') {
890
+ return;
891
+ }
892
+ if (state.kind === 'existing_environment') {
893
+ throw new Error([
894
+ `Target already contains a WPMoo environment: ${options.target}`,
895
+ 'Run `wpmoo reset` to refresh it, or choose another --target.',
896
+ ].join('\n'));
897
+ }
898
+ throw new Error(renderForeignEnvironmentTargetWarning(state));
899
+ }
900
+ async function finishCreateFlow(result, cwd, interactive) {
901
+ if (result.kind === 'cancelled') {
902
+ outroPrompt('Create flow cancelled.');
903
+ return;
904
+ }
905
+ if (result.kind === 'updated') {
906
+ outroPrompt(`Updated existing Odoo dev overlay in ${result.target}.`);
907
+ return;
908
+ }
909
+ if (result.kind === 'deleted') {
910
+ outroPrompt(`Deleted Odoo dev overlay in ${result.target}.`);
911
+ return;
912
+ }
913
+ const { options } = result;
914
+ await ensureGitHubRepositories(options, interactive);
915
+ const scaffoldResult = await scaffold(options);
916
+ if (options.dryRun) {
917
+ console.log('Dry run: planned files');
918
+ for (const file of scaffoldResult.plannedFiles)
919
+ console.log(`- ${file}`);
920
+ console.log('Dry run: planned commands');
921
+ for (const command of scaffoldResult.plannedCommands)
922
+ console.log(`- ${command}`);
923
+ return;
924
+ }
925
+ notePrompt(renderPostCreateGuidance(options.target, cwd), 'Next steps');
926
+ outroPrompt(`Created Odoo dev overlay in ${options.target}. Review staged changes, then commit.`);
927
+ }
776
928
  async function runCockpitCommand(command, cwd) {
777
929
  if (command.id === 'exit') {
778
930
  return 'exit';
@@ -856,11 +1008,7 @@ export async function runCli(cliArgv = process.argv.slice(2), cwd = process.cwd(
856
1008
  const detection = await detectDevelopmentEnvironment(cwd);
857
1009
  if (!detection.isEnvironment) {
858
1010
  await showStartup(argv, skipUpdateCheck);
859
- const resolvedOptions = await optionsFromPrompts();
860
- await ensureGitHubRepositories(resolvedOptions, true);
861
- await scaffold(resolvedOptions);
862
- notePrompt(renderPostCreateGuidance(resolvedOptions.target, cwd), 'Next steps');
863
- outroPrompt(`Created Odoo dev overlay in ${resolvedOptions.target}. Review staged changes, then commit.`);
1011
+ await finishCreateFlow(await optionsFromPrompts(), cwd, true);
864
1012
  return;
865
1013
  }
866
1014
  let lastStatus = 'Last: Ready';
@@ -1003,20 +1151,12 @@ export async function runCli(cliArgv = process.argv.slice(2), cwd = process.cwd(
1003
1151
  else {
1004
1152
  await showStartup(argv, skipUpdateCheck);
1005
1153
  }
1006
- const resolvedOptions = options ?? (await optionsFromPrompts());
1007
- await ensureGitHubRepositories(resolvedOptions, options === undefined);
1008
- const result = await scaffold(resolvedOptions);
1009
- if (resolvedOptions.dryRun) {
1010
- console.log('Dry run: planned files');
1011
- for (const file of result.plannedFiles)
1012
- console.log(`- ${file}`);
1013
- console.log('Dry run: planned commands');
1014
- for (const command of result.plannedCommands)
1015
- console.log(`- ${command}`);
1154
+ if (options) {
1155
+ await ensureNonInteractiveCreateTarget(options);
1156
+ await finishCreateFlow({ kind: 'create', options }, cwd, false);
1016
1157
  return;
1017
1158
  }
1018
- notePrompt(renderPostCreateGuidance(resolvedOptions.target, cwd), 'Next steps');
1019
- outroPrompt(`Created Odoo dev overlay in ${resolvedOptions.target}. Review staged changes, then commit.`);
1159
+ await finishCreateFlow(await optionsFromPrompts(), cwd, true);
1020
1160
  }
1021
1161
  export function isCliEntrypoint(metaUrl, argvPath = process.argv[1]) {
1022
1162
  if (!argvPath)
@@ -0,0 +1,48 @@
1
+ import { access } from 'node:fs/promises';
2
+ import { basename } from 'node:path';
3
+ import { markerPath, readEnvironmentMetadata } from './environment.js';
4
+ function pad2(value) {
5
+ return value.toString().padStart(2, '0');
6
+ }
7
+ function formatBackupTimestamp(date) {
8
+ return [
9
+ `${date.getUTCFullYear()}${pad2(date.getUTCMonth() + 1)}${pad2(date.getUTCDate())}`,
10
+ `${pad2(date.getUTCHours())}${pad2(date.getUTCMinutes())}${pad2(date.getUTCSeconds())}`,
11
+ ].join('-');
12
+ }
13
+ async function pathExists(path) {
14
+ try {
15
+ await access(path);
16
+ return true;
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
22
+ export async function inspectEnvironmentTarget(target) {
23
+ if (!(await pathExists(target))) {
24
+ return { kind: 'missing_target', target };
25
+ }
26
+ const metadata = await readEnvironmentMetadata(target);
27
+ if (metadata) {
28
+ return { kind: 'existing_environment', target, metadata };
29
+ }
30
+ return { kind: 'foreign_target', target };
31
+ }
32
+ export function renderExistingEnvironmentSummary(state) {
33
+ return [
34
+ `Existing WPMoo environment detected at ${state.target}`,
35
+ `- Product: ${state.metadata.product}`,
36
+ `- Odoo version: ${state.metadata.odooVersion}`,
37
+ `- Source repos: ${state.metadata.sourceRepos.length}`,
38
+ ].join('\n');
39
+ }
40
+ export function renderForeignEnvironmentTargetWarning(state) {
41
+ return `Target already exists: ${state.target}\nIt does not contain a WPMoo environment marker at ${markerPath}.`;
42
+ }
43
+ export function expectedTargetConfirmation(target, input) {
44
+ return basename(target) === input;
45
+ }
46
+ export function backupTargetPath(target, date = new Date()) {
47
+ return `${target}.backup-${formatBackupTimestamp(date)}`;
48
+ }
@@ -0,0 +1,22 @@
1
+ import { isGitHubAuthenticated, isGitHubCliAvailable, realGitHub } from './github.js';
2
+ export async function getGitHubPrerequisiteStatus(runner = realGitHub) {
3
+ if (!(await isGitHubCliAvailable(runner))) {
4
+ return { status: 'missing', reason: 'gh-missing' };
5
+ }
6
+ if (!(await isGitHubAuthenticated(runner))) {
7
+ return { status: 'unauthenticated', reason: 'gh-unauthenticated' };
8
+ }
9
+ return { status: 'ready' };
10
+ }
11
+ export function renderGitHubPrerequisiteGuidance(status) {
12
+ if (status.status === 'ready') {
13
+ return '';
14
+ }
15
+ return [
16
+ 'GitHub CLI (`gh`) is not available or not authenticated.',
17
+ 'Install and authenticate it to auto-create missing GitHub repositories:',
18
+ '',
19
+ 'brew install gh',
20
+ 'gh auth login',
21
+ ].join('\n');
22
+ }
@@ -16,19 +16,53 @@ export function repositoryRequirements(options) {
16
16
  export async function findInaccessibleGitHubRepositories(options, runner = realGitHub) {
17
17
  return (await checkGitHubRepositories(options, runner)).inaccessible;
18
18
  }
19
+ export async function getGitHubRepositorySize(runner, slug) {
20
+ const result = await runner.run(['api', `repos/${slug}`, '--jq', '.size']);
21
+ const rawSize = result.stdout.trim();
22
+ if (!rawSize) {
23
+ throw new Error(`Unable to parse repository size for ${slug}`);
24
+ }
25
+ const size = Number(rawSize);
26
+ if (!Number.isFinite(size)) {
27
+ throw new Error(`Unable to parse repository size for ${slug}`);
28
+ }
29
+ return size;
30
+ }
31
+ export async function inspectGitHubRepository(runner, repository) {
32
+ try {
33
+ const size = await getGitHubRepositorySize(runner, repository.slug);
34
+ return { status: size === 0 ? 'empty' : 'non-empty', repository };
35
+ }
36
+ catch {
37
+ return { status: 'unknown', repository };
38
+ }
39
+ }
19
40
  export async function checkGitHubRepositories(options, runner = realGitHub) {
20
41
  const accessible = [];
21
42
  const inaccessible = [];
43
+ const blocked = [];
22
44
  for (const requirement of repositoryRequirements(options)) {
23
45
  const status = await getGitHubRepositoryStatus(runner, requirement.url);
24
46
  if (status.status === 'accessible') {
25
- accessible.push({ ...requirement, slug: status.slug });
47
+ const repository = { ...requirement, slug: status.slug };
48
+ if (requirement.url === options.devRepoUrl) {
49
+ const inspection = await inspectGitHubRepository(runner, repository);
50
+ if (inspection.status === 'empty') {
51
+ accessible.push(repository);
52
+ }
53
+ else {
54
+ blocked.push(repository);
55
+ }
56
+ }
57
+ else {
58
+ accessible.push(repository);
59
+ }
26
60
  }
27
61
  if (status.status === 'inaccessible') {
28
62
  inaccessible.push({ ...requirement, slug: status.slug });
29
63
  }
30
64
  }
31
- return { accessible, inaccessible };
65
+ return { accessible, inaccessible, blocked };
32
66
  }
33
67
  export async function createGitHubRepositories(repositories, visibility, runner = realGitHub) {
34
68
  for (const repository of repositories) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wpmoo/toolkit",
3
- "version": "0.9.2",
3
+ "version": "0.9.3",
4
4
  "description": "WPMoo Toolkit for development, staging, and production lifecycle workflows.",
5
5
  "type": "module",
6
6
  "repository": {