@davidvornholt/standards 0.4.0 → 0.5.0
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 +1 -1
- package/src/cli.ts +50 -29
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -81,6 +81,7 @@ type CliOptions = {
|
|
|
81
81
|
readonly consumer: string;
|
|
82
82
|
readonly dryRun: boolean;
|
|
83
83
|
readonly from: string | undefined;
|
|
84
|
+
readonly ref: string | undefined;
|
|
84
85
|
readonly apply: boolean;
|
|
85
86
|
};
|
|
86
87
|
|
|
@@ -163,9 +164,15 @@ const writeLock = async (dir: string, lock: Lock): Promise<void> => {
|
|
|
163
164
|
|
|
164
165
|
// Fetch the template into a working directory. Accepts a local path (used to
|
|
165
166
|
// prove the engine before the public repo exists and in tests), a github:
|
|
166
|
-
// shorthand, or any git URL.
|
|
167
|
-
|
|
167
|
+
// shorthand, or any git URL. Remote sources default to `main`; `ref` pins a
|
|
168
|
+
// tag, branch, or full commit sha instead (`git fetch` accepts all three).
|
|
169
|
+
const resolveSource = (src: string, ref: string | undefined): Source => {
|
|
168
170
|
if (existsSync(src)) {
|
|
171
|
+
if (ref !== undefined) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`--ref requires a git URL source; a local path is used as-is: ${src}`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
169
176
|
let sha = 'local';
|
|
170
177
|
try {
|
|
171
178
|
sha = execFileSync('git', ['-C', src, 'rev-parse', 'HEAD'], {
|
|
@@ -180,18 +187,34 @@ const resolveSource = (src: string): Source => {
|
|
|
180
187
|
const url = src.startsWith(GITHUB_PREFIX)
|
|
181
188
|
? `https://github.com/${src.slice(GITHUB_PREFIX.length)}.git`
|
|
182
189
|
: src;
|
|
190
|
+
const target = ref ?? 'main';
|
|
183
191
|
const dir = mkdtempSync(join(tmpdir(), 'standards-'));
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
192
|
+
const cleanup = (): void => rmSync(dir, { recursive: true, force: true });
|
|
193
|
+
try {
|
|
194
|
+
// init + fetch instead of `clone --branch` so a full commit sha works as a
|
|
195
|
+
// ref, not only tags and branches (GitHub serves reachable sha fetches).
|
|
196
|
+
execFileSync('git', ['init', '--quiet', dir], { stdio: 'ignore' });
|
|
197
|
+
execFileSync(
|
|
198
|
+
'git',
|
|
199
|
+
['-C', dir, 'fetch', '--quiet', '--depth', '1', '--', url, target],
|
|
200
|
+
{ stdio: 'ignore' },
|
|
201
|
+
);
|
|
202
|
+
execFileSync(
|
|
203
|
+
'git',
|
|
204
|
+
['-C', dir, 'checkout', '--quiet', '--detach', 'FETCH_HEAD'],
|
|
205
|
+
{ stdio: 'ignore' },
|
|
206
|
+
);
|
|
207
|
+
} catch (error) {
|
|
208
|
+
cleanup();
|
|
209
|
+
throw new Error(
|
|
210
|
+
`Cannot fetch "${target}" from ${url}; expected a tag, branch, or full commit sha reachable on the remote`,
|
|
211
|
+
{ cause: error },
|
|
212
|
+
);
|
|
213
|
+
}
|
|
187
214
|
const sha = execFileSync('git', ['-C', dir, 'rev-parse', 'HEAD'], {
|
|
188
215
|
encoding: 'utf8',
|
|
189
216
|
}).trim();
|
|
190
|
-
return {
|
|
191
|
-
dir,
|
|
192
|
-
sha,
|
|
193
|
-
cleanup: () => rmSync(dir, { recursive: true, force: true }),
|
|
194
|
-
};
|
|
217
|
+
return { dir, sha, cleanup };
|
|
195
218
|
};
|
|
196
219
|
|
|
197
220
|
// Recursively collect files under `abs`, keyed by their POSIX path relative to
|
|
@@ -474,15 +497,6 @@ const runCheck = async (consumer: string): Promise<boolean> => {
|
|
|
474
497
|
const readTextIfPresent = async (path: string): Promise<string | null> =>
|
|
475
498
|
existsSync(path) ? readFile(path, 'utf8') : null;
|
|
476
499
|
|
|
477
|
-
const hasStandardsImport = (justfile: string): boolean =>
|
|
478
|
-
justfile.split('\n').some((line) => {
|
|
479
|
-
const trimmed = line.trim();
|
|
480
|
-
return (
|
|
481
|
-
trimmed === "import 'standards.just'" ||
|
|
482
|
-
trimmed === 'import "standards.just"'
|
|
483
|
-
);
|
|
484
|
-
});
|
|
485
|
-
|
|
486
500
|
const DEPENDABOT_BASELINE_ECOSYSTEMS = ['bun', 'github-actions'] as const;
|
|
487
501
|
const DEPENDABOT_SCHEDULE_INTERVALS = new Set([
|
|
488
502
|
'daily',
|
|
@@ -679,11 +693,6 @@ const inspectPackageJson = (packageRaw: string): ReadonlyArray<string> => {
|
|
|
679
693
|
|
|
680
694
|
const runDoctor = async (consumer: string): Promise<boolean> => {
|
|
681
695
|
const problems: Array<string> = [];
|
|
682
|
-
const justfile = await readTextIfPresent(join(consumer, 'justfile'));
|
|
683
|
-
if (justfile === null || !hasStandardsImport(justfile)) {
|
|
684
|
-
problems.push("justfile must import 'standards.just'");
|
|
685
|
-
}
|
|
686
|
-
|
|
687
696
|
const biome = await readTextIfPresent(join(consumer, 'biome.jsonc'));
|
|
688
697
|
if (biome === null || !biome.includes('"./biome.base.jsonc"')) {
|
|
689
698
|
problems.push('biome.jsonc must extend "./biome.base.jsonc"');
|
|
@@ -748,6 +757,7 @@ Commands:
|
|
|
748
757
|
Options:
|
|
749
758
|
--dir <path> Consumer directory to operate on (default: current directory)
|
|
750
759
|
--from <src> Upstream override for init/sync (GitHub repo or local path)
|
|
760
|
+
--ref <ref> Upstream tag, branch, or full commit sha for init/sync (remote Git/GitHub sources only; default: main)
|
|
751
761
|
--dry-run Preview a sync without writing anything
|
|
752
762
|
--check With github: compare live settings to the declaration (default)
|
|
753
763
|
--apply With github: converge the live repository (needs admin auth)`;
|
|
@@ -791,6 +801,7 @@ const parseArgs = (argv: ReadonlyArray<string>): CliOptions => {
|
|
|
791
801
|
let consumer = process.cwd();
|
|
792
802
|
let dryRun = false;
|
|
793
803
|
let from: string | undefined;
|
|
804
|
+
let ref: string | undefined;
|
|
794
805
|
let checkFlag = false;
|
|
795
806
|
let apply = false;
|
|
796
807
|
|
|
@@ -814,6 +825,10 @@ const parseArgs = (argv: ReadonlyArray<string>): CliOptions => {
|
|
|
814
825
|
from = nextOptionValue(argv, index);
|
|
815
826
|
index += 1;
|
|
816
827
|
break;
|
|
828
|
+
case '--ref':
|
|
829
|
+
ref = nextOptionValue(argv, index);
|
|
830
|
+
index += 1;
|
|
831
|
+
break;
|
|
817
832
|
case '--help':
|
|
818
833
|
case '-h':
|
|
819
834
|
command = setCommand(command, 'help');
|
|
@@ -832,12 +847,16 @@ const parseArgs = (argv: ReadonlyArray<string>): CliOptions => {
|
|
|
832
847
|
if (apply && checkFlag) {
|
|
833
848
|
throw new Error('github accepts exactly one of --check or --apply');
|
|
834
849
|
}
|
|
850
|
+
if (ref !== undefined && command !== 'init' && command !== 'sync') {
|
|
851
|
+
throw new Error('--ref is only valid with the init and sync commands');
|
|
852
|
+
}
|
|
835
853
|
|
|
836
854
|
return {
|
|
837
855
|
command,
|
|
838
856
|
consumer: resolve(consumer),
|
|
839
857
|
dryRun,
|
|
840
858
|
from,
|
|
859
|
+
ref,
|
|
841
860
|
apply,
|
|
842
861
|
};
|
|
843
862
|
};
|
|
@@ -857,6 +876,7 @@ const runCheckCommand = async (consumer: string): Promise<boolean> => {
|
|
|
857
876
|
const runInitCommand = async (
|
|
858
877
|
consumer: string,
|
|
859
878
|
from: string | undefined,
|
|
879
|
+
ref: string | undefined,
|
|
860
880
|
): Promise<void> => {
|
|
861
881
|
// Refuse before cloning upstream: re-initializing skips the lock, so it
|
|
862
882
|
// would silently overwrite local canonical edits and orphan files that
|
|
@@ -868,7 +888,7 @@ const runInitCommand = async (
|
|
|
868
888
|
process.exitCode = 1;
|
|
869
889
|
return;
|
|
870
890
|
}
|
|
871
|
-
const source = resolveSource(from ?? DEFAULT_UPSTREAM);
|
|
891
|
+
const source = resolveSource(from ?? DEFAULT_UPSTREAM, ref);
|
|
872
892
|
try {
|
|
873
893
|
const manifest = await loadManifest(
|
|
874
894
|
join(source.dir, 'sync-standards.json'),
|
|
@@ -882,12 +902,13 @@ const runInitCommand = async (
|
|
|
882
902
|
const runSyncCommand = async (
|
|
883
903
|
consumer: string,
|
|
884
904
|
from: string | undefined,
|
|
905
|
+
ref: string | undefined,
|
|
885
906
|
dryRun: boolean,
|
|
886
907
|
): Promise<void> => {
|
|
887
908
|
const consumerManifest = await loadManifest(
|
|
888
909
|
join(consumer, 'sync-standards.json'),
|
|
889
910
|
);
|
|
890
|
-
const source = resolveSource(from ?? consumerManifest.upstream);
|
|
911
|
+
const source = resolveSource(from ?? consumerManifest.upstream, ref);
|
|
891
912
|
try {
|
|
892
913
|
const manifest = await loadManifest(
|
|
893
914
|
join(source.dir, 'sync-standards.json'),
|
|
@@ -899,7 +920,7 @@ const runSyncCommand = async (
|
|
|
899
920
|
};
|
|
900
921
|
|
|
901
922
|
const main = async (): Promise<void> => {
|
|
902
|
-
const { command, consumer, dryRun, from, apply } = parseArgs(
|
|
923
|
+
const { command, consumer, dryRun, from, ref, apply } = parseArgs(
|
|
903
924
|
process.argv.slice(2),
|
|
904
925
|
);
|
|
905
926
|
|
|
@@ -940,12 +961,12 @@ const main = async (): Promise<void> => {
|
|
|
940
961
|
}
|
|
941
962
|
|
|
942
963
|
if (command === 'init') {
|
|
943
|
-
await runInitCommand(consumer, from);
|
|
964
|
+
await runInitCommand(consumer, from, ref);
|
|
944
965
|
return;
|
|
945
966
|
}
|
|
946
967
|
|
|
947
968
|
if (command === 'sync') {
|
|
948
|
-
await runSyncCommand(consumer, from, dryRun);
|
|
969
|
+
await runSyncCommand(consumer, from, ref, dryRun);
|
|
949
970
|
}
|
|
950
971
|
};
|
|
951
972
|
|