@gadmin2n/cli 0.0.112 → 0.0.113

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.
@@ -19,7 +19,6 @@ const child_process_1 = require("child_process");
19
19
  const abstract_action_1 = require("./abstract.action");
20
20
  const self_update_action_1 = require("./self-update.action");
21
21
  const version_check_1 = require("../lib/version-check");
22
- const schematics_root_1 = require("../lib/schematics-root");
23
22
  const executable_paths_1 = require("../lib/executable-paths");
24
23
  const merge3way_1 = require("../lib/template-merge/merge3way");
25
24
  const binary_detect_1 = require("../lib/template-merge/binary-detect");
@@ -48,28 +47,6 @@ function formatMaterializeError(schematicsVersion, err) {
48
47
  const m = (_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : String(err);
49
48
  return `\n❌ Failed to materialize base @ schematics ${schematicsVersion}: ${m}\n`;
50
49
  }
51
- /* ------------------------------------------------------------------ *
52
- * Template config / path resolution
53
- * ------------------------------------------------------------------ */
54
- function resolveTemplatePath(templateName) {
55
- const schematicsRoot = (0, schematics_root_1.findSchematicsRoot)();
56
- const nameVariants = [templateName];
57
- if (templateName.startsWith('gadmin2n-')) {
58
- nameVariants.push(templateName.replace('gadmin2n-', 'gadmin2-'));
59
- }
60
- else if (templateName.startsWith('gadmin2-')) {
61
- nameVariants.push(templateName.replace('gadmin2-', 'gadmin2n-'));
62
- }
63
- for (const name of nameVariants) {
64
- const distPath = path.join(schematicsRoot, 'dist/lib/application/files', name);
65
- const srcPath = path.join(schematicsRoot, 'src/lib/application/files', name);
66
- if (fs.existsSync(distPath))
67
- return distPath;
68
- if (fs.existsSync(srcPath))
69
- return srcPath;
70
- }
71
- throw new Error(`Template directory not found for "${templateName}" in ${schematicsRoot}`);
72
- }
73
50
  /* ------------------------------------------------------------------ *
74
51
  * .claude context sync (from remote git repo)
75
52
  * ------------------------------------------------------------------ */
@@ -789,7 +766,17 @@ class UpdateAction extends abstract_action_1.AbstractAction {
789
766
  ...((_b = cfg.templateExcludes) !== null && _b !== void 0 ? _b : []),
790
767
  ...((_d = (_c = cfg.templateUpdate) === null || _c === void 0 ? void 0 : _c.excludes) !== null && _d !== void 0 ? _d : []),
791
768
  ];
792
- const theirsRoot = resolveTemplatePath(cfg.template);
769
+ let theirsHandle;
770
+ try {
771
+ theirsHandle = (0, materialize_1.materializeLatest)(cfg.template);
772
+ }
773
+ catch (err) {
774
+ console.error(chalk.red(`\n❌ ${err.message}\n`));
775
+ process.exit(1);
776
+ return; // unreachable; satisfies TS
777
+ }
778
+ const theirsRoot = theirsHandle.dir;
779
+ const theirsVersion = theirsHandle.version;
793
780
  const metaExists = fs.existsSync((0, base_store_1.metaPath)(instanceDir));
794
781
  let baseHandle = null;
795
782
  let baseRoot;
@@ -801,6 +788,7 @@ class UpdateAction extends abstract_action_1.AbstractAction {
801
788
  baseRoot = baseHandle.dir;
802
789
  }
803
790
  catch (err) {
791
+ theirsHandle.cleanup();
804
792
  console.error(chalk.red(formatMaterializeError((_e = meta === null || meta === void 0 ? void 0 : meta.schematicsVersion) !== null && _e !== void 0 ? _e : '?', err)));
805
793
  process.exit(1);
806
794
  }
@@ -811,7 +799,7 @@ class UpdateAction extends abstract_action_1.AbstractAction {
811
799
  baseRoot = path.join(instanceDir, '.gadmin/__nonexistent__');
812
800
  }
813
801
  try {
814
- console.info(`\n${chalk.gray('Template:')} ${theirsRoot}`);
802
+ console.info(`\n${chalk.gray('Template:')} @gadmin2n/schematics@${theirsVersion} (latest)`);
815
803
  console.info(`${chalk.gray('Instance:')} ${instanceDir}`);
816
804
  console.info(`${chalk.gray('Base: ')} ${metaExists
817
805
  ? `(materialized from schematics ${meta.schematicsVersion})`
@@ -857,6 +845,7 @@ class UpdateAction extends abstract_action_1.AbstractAction {
857
845
  finally {
858
846
  if (baseHandle)
859
847
  baseHandle.cleanup();
848
+ theirsHandle.cleanup();
860
849
  }
861
850
  });
862
851
  }
@@ -890,13 +879,14 @@ class UpdateAction extends abstract_action_1.AbstractAction {
890
879
  const allExcludes = [...excludes_1.HARDCODED_EXCLUDES, ...excludes_1.DEFAULT_EXCLUDES, ...userExcludes];
891
880
  const instanceFiles = (0, excludes_1.collectFiles)(instanceDir, allExcludes);
892
881
  const residual = (0, residual_scan_1.scanResidualMarkers)(instanceDir, instanceFiles);
893
- let theirsRoot;
882
+ let theirsHandle;
894
883
  try {
895
- theirsRoot = resolveTemplatePath(cfg.template);
884
+ theirsHandle = (0, materialize_1.materializeLatest)(cfg.template);
896
885
  }
897
886
  catch (err) {
898
887
  console.error(chalk.red(err.message + '\n'));
899
888
  process.exit(2);
889
+ return; // unreachable
900
890
  }
901
891
  let baseHandle = null;
902
892
  try {
@@ -907,7 +897,7 @@ class UpdateAction extends abstract_action_1.AbstractAction {
907
897
  console.error(chalk.red(formatMaterializeError(meta.schematicsVersion, err)));
908
898
  process.exit(2);
909
899
  }
910
- const plan = (0, classify_1.classifyAll)(baseHandle.dir, instanceDir, theirsRoot, userExcludes);
900
+ const plan = (0, classify_1.classifyAll)(baseHandle.dir, instanceDir, theirsHandle.dir, userExcludes);
911
901
  const structural = [
912
902
  ...plan.addAddConflict.map((p) => ({ kind: 'add/add', path: p })),
913
903
  ...plan.modifyDeleteConflict.map((p) => ({ kind: 'modify/delete', path: p })),
@@ -943,6 +933,7 @@ class UpdateAction extends abstract_action_1.AbstractAction {
943
933
  finally {
944
934
  if (baseHandle)
945
935
  baseHandle.cleanup();
936
+ theirsHandle.cleanup();
946
937
  }
947
938
  });
948
939
  }
@@ -956,13 +947,15 @@ class UpdateAction extends abstract_action_1.AbstractAction {
956
947
  const templateEnabled = ((_e = options.find((o) => o.name === 'template')) === null || _e === void 0 ? void 0 : _e.value) !== false;
957
948
  const claudeEnabled = ((_f = options.find((o) => o.name === 'coding-context')) === null || _f === void 0 ? void 0 : _f.value) !== false;
958
949
  if (!templateEnabled && !claudeEnabled) {
959
- console.error(chalk.red('\nNothing to do: both --no-template and --no-coding-context were specified.\n'));
950
+ // Should never happen under the opt-in selector — kept as a safety net.
951
+ console.error(chalk.red('\nNothing to do: neither stage was selected.\n'));
960
952
  process.exit(1);
961
953
  }
962
954
  const mergeOpts = { ours, theirs };
963
955
  let pkgSnapshots = null;
964
956
  let preflight = null;
965
957
  let baseHandle = null;
958
+ let theirsHandle = null;
966
959
  try {
967
960
  if (templateEnabled) {
968
961
  try {
@@ -973,8 +966,20 @@ class UpdateAction extends abstract_action_1.AbstractAction {
973
966
  process.exit(1);
974
967
  }
975
968
  const { instanceDir, templateName, userExcludes } = preflight;
976
- const theirsRoot = resolveTemplatePath(templateName);
977
- console.info(`\n${chalk.gray('Template:')} ${theirsRoot}`);
969
+ // Pull the latest schematics tarball from npm to use as `theirs`.
970
+ // Fail-hard: if the registry is unreachable, abort the update — we
971
+ // don't want to silently fall back to a stale local copy.
972
+ try {
973
+ theirsHandle = (0, materialize_1.materializeLatest)(templateName);
974
+ }
975
+ catch (err) {
976
+ console.error(chalk.red(`\n❌ ${err.message}\n`));
977
+ process.exit(1);
978
+ return; // unreachable
979
+ }
980
+ const theirsRoot = theirsHandle.dir;
981
+ const schematicsVersion = theirsHandle.version;
982
+ console.info(`\n${chalk.gray('Template:')} @gadmin2n/schematics@${schematicsVersion} (latest)`);
978
983
  console.info(`${chalk.gray('Instance:')} ${instanceDir}\n`);
979
984
  // Snapshot package.json before any writes (for stage 3 install)
980
985
  pkgSnapshots = [
@@ -982,11 +987,10 @@ class UpdateAction extends abstract_action_1.AbstractAction {
982
987
  snapshotPackageJson(path.join(instanceDir, 'web'), 'web'),
983
988
  snapshotPackageJson(path.join(instanceDir, 'server'), 'server'),
984
989
  ];
985
- const schematicsVersion = (0, schematics_root_1.getSchematicsVersion)(instanceDir);
986
990
  // First-time bootstrap: meta absent → write meta + README, exit.
987
991
  if (!fs.existsSync((0, base_store_1.metaPath)(instanceDir))) {
988
992
  if (dryRun) {
989
- console.info(chalk.gray('(dry-run: would write .gadmin/.meta.json pinned to current schematics version, then exit)\n'));
993
+ console.info(chalk.gray('(dry-run: would write .gadmin/.meta.json pinned to latest schematics version, then exit)\n'));
990
994
  }
991
995
  else {
992
996
  (0, base_store_1.writeMeta)(instanceDir, {
@@ -995,7 +999,7 @@ class UpdateAction extends abstract_action_1.AbstractAction {
995
999
  });
996
1000
  (0, base_store_1.writeReadme)(instanceDir);
997
1001
  console.info(chalk.green('✅ Baseline established at .gadmin/.meta.json'));
998
- console.info(chalk.gray(` Pinned to schematics ${schematicsVersion}.`));
1002
+ console.info(chalk.gray(` Pinned to schematics ${schematicsVersion} (latest from npm).`));
999
1003
  console.info(chalk.gray(' The next `gadmin2 update` will perform a real 3-way merge.'));
1000
1004
  }
1001
1005
  // Skip the rest of stage 1 — nothing to merge yet.
@@ -1085,7 +1089,7 @@ class UpdateAction extends abstract_action_1.AbstractAction {
1085
1089
  }
1086
1090
  }
1087
1091
  else {
1088
- console.info(chalk.gray('\nSkipping template update (--no-template).\n'));
1092
+ console.info(chalk.gray('\nSkipping template update (not selected).\n'));
1089
1093
  }
1090
1094
  // Stage 2: .claude context sync — UNCHANGED.
1091
1095
  if (claudeEnabled) {
@@ -1096,11 +1100,11 @@ class UpdateAction extends abstract_action_1.AbstractAction {
1096
1100
  yield syncClaudeContext(process.cwd(), dryRun, claudeSource);
1097
1101
  }
1098
1102
  else {
1099
- console.info(chalk.gray('\nSkipping .claude context sync (per user choice / --no-coding-context).\n'));
1103
+ console.info(chalk.gray('\nSkipping .claude context sync (per user choice).\n'));
1100
1104
  }
1101
1105
  }
1102
1106
  else {
1103
- console.info(chalk.gray('\nSkipping .claude context sync (--no-coding-context).\n'));
1107
+ console.info(chalk.gray('\nSkipping .claude context sync (not selected).\n'));
1104
1108
  }
1105
1109
  // Stage 3: Install changed deps — UNCHANGED.
1106
1110
  if (pkgSnapshots) {
@@ -1110,6 +1114,8 @@ class UpdateAction extends abstract_action_1.AbstractAction {
1110
1114
  finally {
1111
1115
  if (baseHandle)
1112
1116
  baseHandle.cleanup();
1117
+ if (theirsHandle)
1118
+ theirsHandle.cleanup();
1113
1119
  if (preflight)
1114
1120
  preflight.releaseLock();
1115
1121
  }
@@ -25,23 +25,32 @@ class UpdateCommand extends abstract_command_1.AbstractCommand {
25
25
  ' Conflict resolution:\n' +
26
26
  ' Conflicts are written into source files as <<<<<<< INSTANCE / >>>>>>> TEMPLATE markers.\n' +
27
27
  ' Resolve them in your editor, verify with `gadmin2 update status`, then commit.\n\n' +
28
- ' Stage toggles:\n' +
29
- ' --no-template Skip template merge + auto-install.\n' +
30
- ' --no-coding-context Skip .claude context sync.\n')
28
+ ' Stage selection (opt-in selector — when omitted, both stages run):\n' +
29
+ ' --template Run only the template merge + deps install stage.\n' +
30
+ ' --coding-context Run only the .claude context sync stage.\n' +
31
+ ' (pass both / neither) Run both stages.\n')
31
32
  .option('--no-content', '[diff] Only show file list, skip inline diff content.')
32
33
  .option('-d, --dry-run', '[update] Show what would be updated without making changes.')
33
- .option('-y, --yes', '[update] Skip interactive prompts (currently used only for .claude protocol).')
34
+ .option('-y, --yes', '[update] Skip interactive prompts (template confirm, .claude protocol).')
34
35
  .option('--ours', '[update] Resolve all conflicts by keeping the instance side (mutually exclusive with --theirs).')
35
36
  .option('--theirs', '[update] Resolve all conflicts by taking the template side (mutually exclusive with --ours).')
36
37
  .option('--no-version-check', '[update | diff] Skip the npm registry version check at startup.')
37
38
  .option('--coding-context-repo <url>', '[update] Fully override the .claude context repo URL.')
38
39
  .option('--coding-context-protocol <proto>', '[update] One of: https | ssh | skip.')
39
- .option('--no-coding-context', '[update] Skip the .claude context sync stage.')
40
- .option('--no-template', '[update] Skip the template merge + auto-install stages.')
40
+ .option('--template', '[update] Run only the template merge + deps install stage.')
41
+ .option('--coding-context', '[update] Run only the .claude context sync stage.')
41
42
  .action((subcommand, arg, command) => __awaiter(this, void 0, void 0, function* () {
42
43
  const inputs = [];
43
44
  inputs.push({ name: 'subcommand', value: subcommand || '' });
44
45
  inputs.push({ name: 'subcommand-arg', value: arg || '' });
46
+ // Opt-in stage selector: when neither --template nor --coding-context
47
+ // is passed, run both stages (default). When at least one is passed,
48
+ // run only the stage(s) explicitly requested.
49
+ const tplFlag = !!command.template;
50
+ const ctxFlag = !!command.codingContext;
51
+ const anySelector = tplFlag || ctxFlag;
52
+ const templateEnabled = anySelector ? tplFlag : true;
53
+ const codingContextEnabled = anySelector ? ctxFlag : true;
45
54
  const options = [];
46
55
  options.push({ name: 'no-content', value: !command.content });
47
56
  options.push({ name: 'dry-run', value: !!command.dryRun });
@@ -51,8 +60,8 @@ class UpdateCommand extends abstract_command_1.AbstractCommand {
51
60
  options.push({ name: 'version-check', value: command.versionCheck !== false });
52
61
  options.push({ name: 'coding-context-repo', value: command.codingContextRepo || '' });
53
62
  options.push({ name: 'coding-context-protocol', value: command.codingContextProtocol || '' });
54
- options.push({ name: 'coding-context', value: command.codingContext !== false });
55
- options.push({ name: 'template', value: command.template !== false });
63
+ options.push({ name: 'coding-context', value: codingContextEnabled });
64
+ options.push({ name: 'template', value: templateEnabled });
56
65
  yield this.action.handle(inputs, options);
57
66
  }));
58
67
  }
@@ -4,6 +4,10 @@ export interface MaterializedBase {
4
4
  /** Idempotent cleanup of any temp dirs created. */
5
5
  cleanup: () => void;
6
6
  }
7
+ export interface MaterializedLatest extends MaterializedBase {
8
+ /** Resolved version string (whatever the registry / env override returned). */
9
+ version: string;
10
+ }
7
11
  /**
8
12
  * Materialize a base reference for 3-way merge.
9
13
  *
@@ -16,3 +20,26 @@ export interface MaterializedBase {
16
20
  * Returned `cleanup()` removes any tmp dir we created.
17
21
  */
18
22
  export declare function materializeBase(schematicsVersion: string, templateName: string): MaterializedBase;
23
+ /**
24
+ * Resolve the latest published version of `@gadmin2n/schematics`.
25
+ *
26
+ * Sources, in priority order:
27
+ * 1. `GADMIN2_LATEST_VERSION` env var — used by tests and `--use-version=X`
28
+ * style overrides.
29
+ * 2. `npm view @gadmin2n/schematics version --json` — production. Hard
30
+ * timeout via npm's own behaviour; we do not catch ENOENT here because a
31
+ * missing `npm` is a fatal config error.
32
+ *
33
+ * Throws on any failure (fail-hard per design decision: theirs must always be
34
+ * truly latest, callers should not silently fall back to a stale local copy).
35
+ */
36
+ export declare function resolveLatestVersion(): string;
37
+ /**
38
+ * Materialize the LATEST published template as the `theirs` side of a
39
+ * 3-way merge. Equivalent to `materializeBase(resolveLatestVersion(), ...)`
40
+ * with the resolved version returned alongside the handle.
41
+ *
42
+ * Per design (fail-hard on network failure): if either step throws, the
43
+ * caller is expected to surface the error and abort the update.
44
+ */
45
+ export declare function materializeLatest(templateName: string): MaterializedLatest;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.materializeBase = void 0;
3
+ exports.materializeLatest = exports.resolveLatestVersion = exports.materializeBase = void 0;
4
4
  // cli/lib/template-merge/materialize.ts
5
5
  const fs = require("fs");
6
6
  const os = require("os");
@@ -127,3 +127,60 @@ function copyTreeRecursive(src, dst) {
127
127
  // symlinks / sockets: skip
128
128
  }
129
129
  }
130
+ /**
131
+ * Resolve the latest published version of `@gadmin2n/schematics`.
132
+ *
133
+ * Sources, in priority order:
134
+ * 1. `GADMIN2_LATEST_VERSION` env var — used by tests and `--use-version=X`
135
+ * style overrides.
136
+ * 2. `npm view @gadmin2n/schematics version --json` — production. Hard
137
+ * timeout via npm's own behaviour; we do not catch ENOENT here because a
138
+ * missing `npm` is a fatal config error.
139
+ *
140
+ * Throws on any failure (fail-hard per design decision: theirs must always be
141
+ * truly latest, callers should not silently fall back to a stale local copy).
142
+ */
143
+ function resolveLatestVersion() {
144
+ var _a, _b, _c, _d;
145
+ const envOverride = process.env.GADMIN2_LATEST_VERSION;
146
+ if (envOverride)
147
+ return envOverride.trim();
148
+ try {
149
+ const out = (0, child_process_1.execFileSync)('npm', ['view', SCHEMATICS_PKG, 'version', '--json'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
150
+ const trimmed = out.trim();
151
+ // `--json` wraps the value in quotes for plain strings.
152
+ const parsed = trimmed.startsWith('"') ? JSON.parse(trimmed) : trimmed;
153
+ if (typeof parsed !== 'string' || parsed.length === 0) {
154
+ throw new Error(`unexpected npm view output: ${out}`);
155
+ }
156
+ return parsed;
157
+ }
158
+ catch (err) {
159
+ if (err && err.code === 'ENOENT') {
160
+ throw new Error('npm not in PATH — required to resolve the latest @gadmin2n/schematics version. ' +
161
+ 'Install Node.js / npm and retry.');
162
+ }
163
+ const stderr = (_c = (_b = (_a = err === null || err === void 0 ? void 0 : err.stderr) === null || _a === void 0 ? void 0 : _a.toString) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : '';
164
+ const message = (_d = err === null || err === void 0 ? void 0 : err.message) !== null && _d !== void 0 ? _d : String(err);
165
+ throw new Error(`Failed to resolve latest @gadmin2n/schematics version from npm registry.\n` +
166
+ ` ${message.split('\n').join('\n ')}` +
167
+ (stderr ? `\n ${stderr.trim().split('\n').join('\n ')}` : '') +
168
+ `\n Check your network / npm registry config and retry, ` +
169
+ `or set GADMIN2_LATEST_VERSION=<version> to pin a specific version.`);
170
+ }
171
+ }
172
+ exports.resolveLatestVersion = resolveLatestVersion;
173
+ /**
174
+ * Materialize the LATEST published template as the `theirs` side of a
175
+ * 3-way merge. Equivalent to `materializeBase(resolveLatestVersion(), ...)`
176
+ * with the resolved version returned alongside the handle.
177
+ *
178
+ * Per design (fail-hard on network failure): if either step throws, the
179
+ * caller is expected to surface the error and abort the update.
180
+ */
181
+ function materializeLatest(templateName) {
182
+ const version = resolveLatestVersion();
183
+ const handle = materializeBase(version, templateName);
184
+ return Object.assign(Object.assign({}, handle), { version });
185
+ }
186
+ exports.materializeLatest = materializeLatest;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gadmin2n/cli",
3
- "version": "0.0.112",
3
+ "version": "0.0.113",
4
4
  "description": "Gadmin - modern, fast, powerful node.js web framework (@cli)",
5
5
  "publishConfig": {
6
6
  "access": "public"