hikoutei 0.5.11 → 0.5.13
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 +6 -0
- package/dist/cli/args.d.ts +50 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +139 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/confirm.d.ts +31 -0
- package/dist/cli/confirm.d.ts.map +1 -0
- package/dist/cli/confirm.js +29 -0
- package/dist/cli/confirm.js.map +1 -0
- package/dist/cli/errors.d.ts +47 -0
- package/dist/cli/errors.d.ts.map +1 -0
- package/dist/cli/errors.js +42 -0
- package/dist/cli/errors.js.map +1 -0
- package/dist/cli/gcloudRunner.d.ts +35 -0
- package/dist/cli/gcloudRunner.d.ts.map +1 -0
- package/dist/cli/gcloudRunner.js +40 -0
- package/dist/cli/gcloudRunner.js.map +1 -0
- package/dist/cli/setup.d.ts +13 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +74 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/cli/setupFlow.d.ts +137 -0
- package/dist/cli/setupFlow.d.ts.map +1 -0
- package/dist/cli/setupFlow.js +443 -0
- package/dist/cli/setupFlow.js.map +1 -0
- package/dist/cli/sheetsFactory.d.ts +49 -0
- package/dist/cli/sheetsFactory.d.ts.map +1 -0
- package/dist/cli/sheetsFactory.js +71 -0
- package/dist/cli/sheetsFactory.js.map +1 -0
- package/package.json +4 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestration for `hikoutei setup`.
|
|
3
|
+
*
|
|
4
|
+
* `runSetup` is the pure-ish core of the CLI: it receives an injected gcloud
|
|
5
|
+
* runner and spreadsheet creator, drives the bootstrap sequence (preflight,
|
|
6
|
+
* project, API enable, service account, key, spreadsheet, .env), and returns
|
|
7
|
+
* an explicit result union. Nothing here reads process state; the thin entry
|
|
8
|
+
* in `setup.ts` resolves defaults and prints output. `--dry-run` builds the
|
|
9
|
+
* exact command plan without invoking the runner or touching the filesystem.
|
|
10
|
+
*/
|
|
11
|
+
import { type SetupErrorCode } from "./errors.js";
|
|
12
|
+
import type { GcloudRunner } from "./gcloudRunner.js";
|
|
13
|
+
import type { SpreadsheetCreator } from "./sheetsFactory.js";
|
|
14
|
+
/** Default service-account key file name, resolved against the current directory. */
|
|
15
|
+
export declare const DEFAULT_KEY_FILE_NAME = "hikoutei-service-account.json";
|
|
16
|
+
/** Prefix of the default spreadsheet title (`hikoutei-sync-<project>`). */
|
|
17
|
+
export declare const DEFAULT_SPREADSHEET_TITLE_PREFIX = "hikoutei-sync";
|
|
18
|
+
/** Service-account key file permission after setup (owner read/write only). */
|
|
19
|
+
export declare const KEY_FILE_MODE = 384;
|
|
20
|
+
/** Options for one setup run; paths are absolute and resolved by the entry. */
|
|
21
|
+
export interface RunSetupOptions {
|
|
22
|
+
readonly runner: GcloudRunner;
|
|
23
|
+
readonly createSpreadsheet: SpreadsheetCreator;
|
|
24
|
+
/** Existing project id, or undefined to create `hikoutei-<slug>`. */
|
|
25
|
+
readonly projectId: string | undefined;
|
|
26
|
+
readonly saName: string;
|
|
27
|
+
/** Spreadsheet title override; defaults to `hikoutei-sync-<project>`. */
|
|
28
|
+
readonly spreadsheetTitle: string | undefined;
|
|
29
|
+
/** Absolute service-account key path. */
|
|
30
|
+
readonly keyPath: string;
|
|
31
|
+
/** Absolute .env output path. */
|
|
32
|
+
readonly outputPath: string;
|
|
33
|
+
readonly dryRun: boolean;
|
|
34
|
+
}
|
|
35
|
+
/** One planned or executed step of the setup flow. */
|
|
36
|
+
export type PlannedCommand = {
|
|
37
|
+
readonly kind: "gcloud";
|
|
38
|
+
readonly command: readonly string[];
|
|
39
|
+
readonly outcome: string;
|
|
40
|
+
} | {
|
|
41
|
+
readonly kind: "api";
|
|
42
|
+
readonly label: string;
|
|
43
|
+
readonly outcome: string;
|
|
44
|
+
} | {
|
|
45
|
+
readonly kind: "file";
|
|
46
|
+
readonly label: string;
|
|
47
|
+
readonly outcome: string;
|
|
48
|
+
};
|
|
49
|
+
/** Outcome summary returned for a successful (non-dry-run) setup. */
|
|
50
|
+
export interface SetupSummary {
|
|
51
|
+
readonly projectId: string;
|
|
52
|
+
readonly serviceAccountEmail: string;
|
|
53
|
+
readonly keyPath: string;
|
|
54
|
+
readonly spreadsheetId: string;
|
|
55
|
+
readonly spreadsheetUrl: string;
|
|
56
|
+
readonly spreadsheetTitle: string;
|
|
57
|
+
readonly outputPath: string;
|
|
58
|
+
readonly envFileCreated: boolean;
|
|
59
|
+
readonly envFileModified: boolean;
|
|
60
|
+
readonly projectReused: boolean;
|
|
61
|
+
readonly serviceAccountReused: boolean;
|
|
62
|
+
readonly keyReused: boolean;
|
|
63
|
+
}
|
|
64
|
+
/** Discriminated result of a setup run. */
|
|
65
|
+
export type SetupResult = {
|
|
66
|
+
readonly status: "ok";
|
|
67
|
+
readonly dryRun: false;
|
|
68
|
+
readonly summary: SetupSummary;
|
|
69
|
+
readonly commands: readonly PlannedCommand[];
|
|
70
|
+
} | {
|
|
71
|
+
readonly status: "ok";
|
|
72
|
+
readonly dryRun: true;
|
|
73
|
+
readonly commands: readonly PlannedCommand[];
|
|
74
|
+
} | {
|
|
75
|
+
readonly status: "error";
|
|
76
|
+
readonly code: SetupErrorCode;
|
|
77
|
+
readonly message: string;
|
|
78
|
+
};
|
|
79
|
+
/** Derives the service-account email for a name and project. */
|
|
80
|
+
export declare function serviceAccountEmail(saName: string, projectId: string): string;
|
|
81
|
+
/** Default spreadsheet title for a project. */
|
|
82
|
+
export declare function defaultSpreadsheetTitle(projectId: string): string;
|
|
83
|
+
/**
|
|
84
|
+
* Generates a `hikoutei-<timestamp>-<random>` project id.
|
|
85
|
+
*
|
|
86
|
+
* The timestamp base-36 component sorts lexically and the random suffix makes
|
|
87
|
+
* parallel runs collision-resistant; if the id already exists the flow reuses
|
|
88
|
+
* the project instead of failing.
|
|
89
|
+
*/
|
|
90
|
+
export declare function generateProjectId(now?: number, random?: () => number): string;
|
|
91
|
+
/**
|
|
92
|
+
* Builds the exact command plan for a dry run.
|
|
93
|
+
*
|
|
94
|
+
* Pure: does not execute anything and does not touch the filesystem. Each
|
|
95
|
+
* step carries a simulated outcome so `--dry-run` previews both the fresh
|
|
96
|
+
* path and the reuse branches.
|
|
97
|
+
*/
|
|
98
|
+
export declare function planSetupCommands(options: RunSetupOptions, slug: string): readonly PlannedCommand[];
|
|
99
|
+
/** Renders a plan or executed-command list for `--dry-run` output. */
|
|
100
|
+
export declare function formatPlan(commands: readonly PlannedCommand[]): string;
|
|
101
|
+
/** Renders the human summary; never includes key contents or secrets. */
|
|
102
|
+
export declare function formatSummary(summary: SetupSummary): string;
|
|
103
|
+
/**
|
|
104
|
+
* Runs the full setup bootstrap.
|
|
105
|
+
*
|
|
106
|
+
* In dry-run mode returns the command plan without invoking the runner or
|
|
107
|
+
* touching the filesystem. Otherwise the sequence is: preflight (gcloud
|
|
108
|
+
* present, active account), project verify/create, `config set project`,
|
|
109
|
+
* enable the Sheets API, service account list/create, key create (or reuse
|
|
110
|
+
* an existing key file) with chmod 600, spreadsheet creation through the
|
|
111
|
+
* injected creator, and finally the .env write. Every phase maps to a stable
|
|
112
|
+
* error code on failure; key material is never included in messages.
|
|
113
|
+
*/
|
|
114
|
+
export declare function runSetup(options: RunSetupOptions): Promise<SetupResult>;
|
|
115
|
+
/** The two .env keys the setup CLI manages. */
|
|
116
|
+
export declare const SETUP_ENV_KEYS: {
|
|
117
|
+
readonly CREDENTIALS: "GOOGLE_APPLICATION_CREDENTIALS";
|
|
118
|
+
readonly SPREADSHEET_URL: "HIKOUTEI_SYNC_SPREADSHEET_URL";
|
|
119
|
+
};
|
|
120
|
+
/** Result of writing the .env output file. */
|
|
121
|
+
export interface EnvFileWriteResult {
|
|
122
|
+
/** True when the file did not exist before this write. */
|
|
123
|
+
readonly created: boolean;
|
|
124
|
+
/** True when the file content changed (created or keys added/updated). */
|
|
125
|
+
readonly modified: boolean;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Writes or updates the .env output file.
|
|
129
|
+
*
|
|
130
|
+
* Reads the existing file (if any), keeps every unrelated line untouched,
|
|
131
|
+
* replaces any existing managed key lines, appends the two managed keys, and
|
|
132
|
+
* writes back only when the content changed. A missing file is created.
|
|
133
|
+
* Throws on filesystem failure; the caller maps the throw to
|
|
134
|
+
* `output_write_failed`.
|
|
135
|
+
*/
|
|
136
|
+
export declare function writeSetupEnvFile(outputPath: string, credentialsPath: string, spreadsheetUrl: string): EnvFileWriteResult;
|
|
137
|
+
//# sourceMappingURL=setupFlow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setupFlow.d.ts","sourceRoot":"","sources":["../../src/cli/setupFlow.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestration for `hikoutei setup`.
|
|
3
|
+
*
|
|
4
|
+
* `runSetup` is the pure-ish core of the CLI: it receives an injected gcloud
|
|
5
|
+
* runner and spreadsheet creator, drives the bootstrap sequence (preflight,
|
|
6
|
+
* project, API enable, service account, key, spreadsheet, .env), and returns
|
|
7
|
+
* an explicit result union. Nothing here reads process state; the thin entry
|
|
8
|
+
* in `setup.ts` resolves defaults and prints output. `--dry-run` builds the
|
|
9
|
+
* exact command plan without invoking the runner or touching the filesystem.
|
|
10
|
+
*/
|
|
11
|
+
import { chmodSync, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { SETUP_ERROR_CODES } from "./errors.js";
|
|
13
|
+
/** Default service-account key file name, resolved against the current directory. */
|
|
14
|
+
export const DEFAULT_KEY_FILE_NAME = "hikoutei-service-account.json";
|
|
15
|
+
/** Prefix of the default spreadsheet title (`hikoutei-sync-<project>`). */
|
|
16
|
+
export const DEFAULT_SPREADSHEET_TITLE_PREFIX = "hikoutei-sync";
|
|
17
|
+
/** Service-account key file permission after setup (owner read/write only). */
|
|
18
|
+
export const KEY_FILE_MODE = 0o600;
|
|
19
|
+
const AUTH_LIST_ARGS = ["auth", "list", "--filter=status:ACTIVE", "--format=value(account)"];
|
|
20
|
+
/** gcloud error text that signals an already-existing project on create. */
|
|
21
|
+
const PROJECT_ALREADY_EXISTS_MARKER = "already exists";
|
|
22
|
+
/** Derives the service-account email for a name and project. */
|
|
23
|
+
export function serviceAccountEmail(saName, projectId) {
|
|
24
|
+
return `${saName}@${projectId}.iam.gserviceaccount.com`;
|
|
25
|
+
}
|
|
26
|
+
/** Default spreadsheet title for a project. */
|
|
27
|
+
export function defaultSpreadsheetTitle(projectId) {
|
|
28
|
+
return `${DEFAULT_SPREADSHEET_TITLE_PREFIX}-${projectId}`;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Generates a `hikoutei-<timestamp>-<random>` project id.
|
|
32
|
+
*
|
|
33
|
+
* The timestamp base-36 component sorts lexically and the random suffix makes
|
|
34
|
+
* parallel runs collision-resistant; if the id already exists the flow reuses
|
|
35
|
+
* the project instead of failing.
|
|
36
|
+
*/
|
|
37
|
+
export function generateProjectId(now = Date.now(), random = Math.random) {
|
|
38
|
+
const timestampPart = now.toString(36);
|
|
39
|
+
const randomPart = Math.floor(random() * 36 ** 4).toString(36).padStart(4, "0");
|
|
40
|
+
return `hikoutei-${timestampPart}-${randomPart}`;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Builds the exact command plan for a dry run.
|
|
44
|
+
*
|
|
45
|
+
* Pure: does not execute anything and does not touch the filesystem. Each
|
|
46
|
+
* step carries a simulated outcome so `--dry-run` previews both the fresh
|
|
47
|
+
* path and the reuse branches.
|
|
48
|
+
*/
|
|
49
|
+
export function planSetupCommands(options, slug) {
|
|
50
|
+
const projectId = options.projectId ?? slug;
|
|
51
|
+
const email = serviceAccountEmail(options.saName, projectId);
|
|
52
|
+
const title = options.spreadsheetTitle ?? defaultSpreadsheetTitle(projectId);
|
|
53
|
+
const commands = [
|
|
54
|
+
{ kind: "gcloud", command: ["--version"], outcome: "gcloud is installed" },
|
|
55
|
+
{ kind: "gcloud", command: [...AUTH_LIST_ARGS], outcome: "an active account is logged in" },
|
|
56
|
+
];
|
|
57
|
+
if (options.projectId !== undefined) {
|
|
58
|
+
commands.push({
|
|
59
|
+
kind: "gcloud",
|
|
60
|
+
command: ["projects", "describe", options.projectId],
|
|
61
|
+
outcome: "project verified",
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
commands.push({
|
|
66
|
+
kind: "gcloud",
|
|
67
|
+
command: ["projects", "create", slug],
|
|
68
|
+
outcome: "project created (reused when it already exists)",
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
commands.push({
|
|
72
|
+
kind: "gcloud",
|
|
73
|
+
command: ["config", "set", "project", projectId],
|
|
74
|
+
outcome: "default project selected",
|
|
75
|
+
}, {
|
|
76
|
+
kind: "gcloud",
|
|
77
|
+
command: ["services", "enable", "sheets.googleapis.com", "--project", projectId],
|
|
78
|
+
outcome: "Sheets API enabled (idempotent)",
|
|
79
|
+
}, {
|
|
80
|
+
kind: "gcloud",
|
|
81
|
+
command: ["iam", "service-accounts", "list", "--project", projectId, "--format=value(email)"],
|
|
82
|
+
outcome: "no matching service account; create follows",
|
|
83
|
+
}, {
|
|
84
|
+
kind: "gcloud",
|
|
85
|
+
command: [
|
|
86
|
+
"iam",
|
|
87
|
+
"service-accounts",
|
|
88
|
+
"create",
|
|
89
|
+
options.saName,
|
|
90
|
+
"--project",
|
|
91
|
+
projectId,
|
|
92
|
+
"--display-name",
|
|
93
|
+
"hikoutei setup",
|
|
94
|
+
],
|
|
95
|
+
outcome: "service account created (reused when it already exists)",
|
|
96
|
+
}, {
|
|
97
|
+
kind: "gcloud",
|
|
98
|
+
command: [
|
|
99
|
+
"iam",
|
|
100
|
+
"service-accounts",
|
|
101
|
+
"keys",
|
|
102
|
+
"create",
|
|
103
|
+
options.keyPath,
|
|
104
|
+
"--iam-account",
|
|
105
|
+
email,
|
|
106
|
+
"--project",
|
|
107
|
+
projectId,
|
|
108
|
+
],
|
|
109
|
+
outcome: `key written to ${options.keyPath} with mode 600 (reused when the file already exists)`,
|
|
110
|
+
}, {
|
|
111
|
+
kind: "api",
|
|
112
|
+
label: `spreadsheets.create (title "${title}") with the new service-account key`,
|
|
113
|
+
outcome: `spreadsheet owned by ${email}; no sharing step required`,
|
|
114
|
+
}, {
|
|
115
|
+
kind: "file",
|
|
116
|
+
label: `write ${options.outputPath}`,
|
|
117
|
+
outcome: "GOOGLE_APPLICATION_CREDENTIALS and HIKOUTEI_SYNC_SPREADSHEET_URL set; unrelated lines preserved",
|
|
118
|
+
});
|
|
119
|
+
return commands;
|
|
120
|
+
}
|
|
121
|
+
/** Renders a plan or executed-command list for `--dry-run` output. */
|
|
122
|
+
export function formatPlan(commands) {
|
|
123
|
+
return commands
|
|
124
|
+
.map((step) => {
|
|
125
|
+
switch (step.kind) {
|
|
126
|
+
case "gcloud":
|
|
127
|
+
return `$ gcloud ${step.command.join(" ")}\n # ${step.outcome}`;
|
|
128
|
+
case "api":
|
|
129
|
+
return `$ ${step.label}\n # ${step.outcome}`;
|
|
130
|
+
case "file":
|
|
131
|
+
return `# ${step.label}: ${step.outcome}`;
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
.join("\n");
|
|
135
|
+
}
|
|
136
|
+
/** Renders the human summary; never includes key contents or secrets. */
|
|
137
|
+
export function formatSummary(summary) {
|
|
138
|
+
const envState = summary.envFileCreated
|
|
139
|
+
? "created"
|
|
140
|
+
: summary.envFileModified
|
|
141
|
+
? "updated"
|
|
142
|
+
: "unchanged";
|
|
143
|
+
return [
|
|
144
|
+
"Hikoutei setup complete.",
|
|
145
|
+
` project: ${summary.projectId} (${summary.projectReused ? "reused" : "created"})`,
|
|
146
|
+
` service account: ${summary.serviceAccountEmail} (${summary.serviceAccountReused ? "reused" : "created"})`,
|
|
147
|
+
` service account key: ${summary.keyPath} (${summary.keyReused ? "reused" : "created"})`,
|
|
148
|
+
` spreadsheet: ${summary.spreadsheetTitle} (${summary.spreadsheetId})`,
|
|
149
|
+
` spreadsheet URL: ${summary.spreadsheetUrl}`,
|
|
150
|
+
` env file: ${summary.outputPath} (${envState})`,
|
|
151
|
+
].join("\n");
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Runs the full setup bootstrap.
|
|
155
|
+
*
|
|
156
|
+
* In dry-run mode returns the command plan without invoking the runner or
|
|
157
|
+
* touching the filesystem. Otherwise the sequence is: preflight (gcloud
|
|
158
|
+
* present, active account), project verify/create, `config set project`,
|
|
159
|
+
* enable the Sheets API, service account list/create, key create (or reuse
|
|
160
|
+
* an existing key file) with chmod 600, spreadsheet creation through the
|
|
161
|
+
* injected creator, and finally the .env write. Every phase maps to a stable
|
|
162
|
+
* error code on failure; key material is never included in messages.
|
|
163
|
+
*/
|
|
164
|
+
export async function runSetup(options) {
|
|
165
|
+
if (options.dryRun) {
|
|
166
|
+
return {
|
|
167
|
+
status: "ok",
|
|
168
|
+
dryRun: true,
|
|
169
|
+
commands: planSetupCommands(options, options.projectId ?? generateProjectId()),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const executed = [];
|
|
173
|
+
const { runner } = options;
|
|
174
|
+
// Preflight: gcloud must exist and an active account must be logged in.
|
|
175
|
+
const version = await runner.run(["--version"]);
|
|
176
|
+
executed.push({ kind: "gcloud", command: ["--version"], outcome: outcomeOf(version, "gcloud is installed") });
|
|
177
|
+
if (version.status === "not_found") {
|
|
178
|
+
return errorResult(SETUP_ERROR_CODES.GCLOUD_MISSING, "gcloud CLI was not found on PATH; install it from https://cloud.google.com/sdk and try again");
|
|
179
|
+
}
|
|
180
|
+
if (version.status === "failed") {
|
|
181
|
+
return errorResult(SETUP_ERROR_CODES.GCLOUD_MISSING, `gcloud --version failed: ${describeGcloudFailure(version)}`);
|
|
182
|
+
}
|
|
183
|
+
const auth = await runner.run([...AUTH_LIST_ARGS]);
|
|
184
|
+
executed.push({ kind: "gcloud", command: [...AUTH_LIST_ARGS], outcome: outcomeOf(auth, "active account found") });
|
|
185
|
+
if (auth.status !== "ok") {
|
|
186
|
+
return errorResult(SETUP_ERROR_CODES.GCLOUD_NOT_LOGGED_IN, `could not list active gcloud accounts: ${describeGcloudFailure(auth)}`);
|
|
187
|
+
}
|
|
188
|
+
if (auth.stdout.trim() === "") {
|
|
189
|
+
return errorResult(SETUP_ERROR_CODES.GCLOUD_NOT_LOGGED_IN, "no active gcloud account; run `gcloud auth login` first");
|
|
190
|
+
}
|
|
191
|
+
// Project: verify an explicit one, or create `hikoutei-<slug>` idempotently.
|
|
192
|
+
let projectId;
|
|
193
|
+
let projectReused = false;
|
|
194
|
+
if (options.projectId !== undefined) {
|
|
195
|
+
const describe = await runner.run(["projects", "describe", options.projectId]);
|
|
196
|
+
executed.push({
|
|
197
|
+
kind: "gcloud",
|
|
198
|
+
command: ["projects", "describe", options.projectId],
|
|
199
|
+
outcome: outcomeOf(describe, "project verified"),
|
|
200
|
+
});
|
|
201
|
+
if (describe.status !== "ok") {
|
|
202
|
+
return errorResult(SETUP_ERROR_CODES.PROJECT_NOT_FOUND, `project "${options.projectId}" could not be verified: ${describeGcloudFailure(describe)}`);
|
|
203
|
+
}
|
|
204
|
+
projectId = options.projectId;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
projectId = generateProjectId();
|
|
208
|
+
const create = await runner.run(["projects", "create", projectId]);
|
|
209
|
+
if (create.status !== "ok") {
|
|
210
|
+
if (create.status === "failed" && create.stderr.includes(PROJECT_ALREADY_EXISTS_MARKER)) {
|
|
211
|
+
projectReused = true;
|
|
212
|
+
executed.push({ kind: "gcloud", command: ["projects", "create", projectId], outcome: "reused (project already exists)" });
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
return errorResult(SETUP_ERROR_CODES.PROJECT_CREATE_FAILED, `could not create project "${projectId}": ${describeGcloudFailure(create)}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
executed.push({ kind: "gcloud", command: ["projects", "create", projectId], outcome: "created" });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const configSet = await runner.run(["config", "set", "project", projectId]);
|
|
223
|
+
executed.push({ kind: "gcloud", command: ["config", "set", "project", projectId], outcome: outcomeOf(configSet, "default project selected") });
|
|
224
|
+
if (configSet.status !== "ok") {
|
|
225
|
+
return errorResult(SETUP_ERROR_CODES.PROJECT_SELECT_FAILED, `could not select project "${projectId}": ${describeGcloudFailure(configSet)}`);
|
|
226
|
+
}
|
|
227
|
+
const enable = await runner.run(["services", "enable", "sheets.googleapis.com", "--project", projectId]);
|
|
228
|
+
executed.push({
|
|
229
|
+
kind: "gcloud",
|
|
230
|
+
command: ["services", "enable", "sheets.googleapis.com", "--project", projectId],
|
|
231
|
+
outcome: outcomeOf(enable, "Sheets API enabled"),
|
|
232
|
+
});
|
|
233
|
+
if (enable.status !== "ok") {
|
|
234
|
+
return errorResult(SETUP_ERROR_CODES.API_ENABLE_FAILED, `could not enable sheets.googleapis.com: ${describeGcloudFailure(enable)}`);
|
|
235
|
+
}
|
|
236
|
+
// Service account: reuse by email when it already exists.
|
|
237
|
+
const email = serviceAccountEmail(options.saName, projectId);
|
|
238
|
+
let serviceAccountReused = false;
|
|
239
|
+
const saList = await runner.run(["iam", "service-accounts", "list", "--project", projectId, "--format=value(email)"]);
|
|
240
|
+
executed.push({
|
|
241
|
+
kind: "gcloud",
|
|
242
|
+
command: ["iam", "service-accounts", "list", "--project", projectId, "--format=value(email)"],
|
|
243
|
+
outcome: outcomeOf(saList, "service account lookup complete"),
|
|
244
|
+
});
|
|
245
|
+
if (saList.status !== "ok") {
|
|
246
|
+
return errorResult(SETUP_ERROR_CODES.SA_CREATE_FAILED, `could not list service accounts: ${describeGcloudFailure(saList)}`);
|
|
247
|
+
}
|
|
248
|
+
const existingEmails = saList.stdout.split("\n").map((line) => line.trim());
|
|
249
|
+
if (existingEmails.includes(email)) {
|
|
250
|
+
serviceAccountReused = true;
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
const saCreate = await runner.run([
|
|
254
|
+
"iam",
|
|
255
|
+
"service-accounts",
|
|
256
|
+
"create",
|
|
257
|
+
options.saName,
|
|
258
|
+
"--project",
|
|
259
|
+
projectId,
|
|
260
|
+
"--display-name",
|
|
261
|
+
"hikoutei setup",
|
|
262
|
+
]);
|
|
263
|
+
executed.push({
|
|
264
|
+
kind: "gcloud",
|
|
265
|
+
command: [
|
|
266
|
+
"iam",
|
|
267
|
+
"service-accounts",
|
|
268
|
+
"create",
|
|
269
|
+
options.saName,
|
|
270
|
+
"--project",
|
|
271
|
+
projectId,
|
|
272
|
+
"--display-name",
|
|
273
|
+
"hikoutei setup",
|
|
274
|
+
],
|
|
275
|
+
outcome: outcomeOf(saCreate, "service account created"),
|
|
276
|
+
});
|
|
277
|
+
if (saCreate.status !== "ok") {
|
|
278
|
+
return errorResult(SETUP_ERROR_CODES.SA_CREATE_FAILED, `could not create service account "${options.saName}": ${describeGcloudFailure(saCreate)}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// Key: reuse an existing key file instead of overwriting it.
|
|
282
|
+
let keyReused = false;
|
|
283
|
+
if (existsSync(options.keyPath)) {
|
|
284
|
+
keyReused = true;
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
const keyCreate = await runner.run([
|
|
288
|
+
"iam",
|
|
289
|
+
"service-accounts",
|
|
290
|
+
"keys",
|
|
291
|
+
"create",
|
|
292
|
+
options.keyPath,
|
|
293
|
+
"--iam-account",
|
|
294
|
+
email,
|
|
295
|
+
"--project",
|
|
296
|
+
projectId,
|
|
297
|
+
]);
|
|
298
|
+
executed.push({
|
|
299
|
+
kind: "gcloud",
|
|
300
|
+
command: [
|
|
301
|
+
"iam",
|
|
302
|
+
"service-accounts",
|
|
303
|
+
"keys",
|
|
304
|
+
"create",
|
|
305
|
+
options.keyPath,
|
|
306
|
+
"--iam-account",
|
|
307
|
+
email,
|
|
308
|
+
"--project",
|
|
309
|
+
projectId,
|
|
310
|
+
],
|
|
311
|
+
outcome: outcomeOf(keyCreate, "key created"),
|
|
312
|
+
});
|
|
313
|
+
if (keyCreate.status !== "ok") {
|
|
314
|
+
return errorResult(SETUP_ERROR_CODES.KEY_CREATE_FAILED, `could not create a service-account key for ${email}: ${describeGcloudFailure(keyCreate)}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
try {
|
|
318
|
+
chmodSync(options.keyPath, KEY_FILE_MODE);
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
return errorResult(SETUP_ERROR_CODES.KEY_CREATE_FAILED, `could not secure the key file ${options.keyPath}: ${messageOf(error)}`);
|
|
322
|
+
}
|
|
323
|
+
// Spreadsheet: created with the new key, so the SA owns it.
|
|
324
|
+
const title = options.spreadsheetTitle ?? defaultSpreadsheetTitle(projectId);
|
|
325
|
+
let spreadsheet;
|
|
326
|
+
try {
|
|
327
|
+
spreadsheet = await options.createSpreadsheet({ keyPath: options.keyPath, title });
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
return errorResult(SETUP_ERROR_CODES.SHEET_CREATE_FAILED, `could not create spreadsheet "${title}": ${messageOf(error)}`);
|
|
331
|
+
}
|
|
332
|
+
// .env: update only the two managed keys, preserving unrelated lines.
|
|
333
|
+
let envResult;
|
|
334
|
+
try {
|
|
335
|
+
envResult = writeSetupEnvFile(options.outputPath, options.keyPath, spreadsheet.spreadsheetUrl);
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
return errorResult(SETUP_ERROR_CODES.OUTPUT_WRITE_FAILED, `could not write ${options.outputPath}: ${messageOf(error)}`);
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
status: "ok",
|
|
342
|
+
dryRun: false,
|
|
343
|
+
commands: executed,
|
|
344
|
+
summary: {
|
|
345
|
+
projectId,
|
|
346
|
+
serviceAccountEmail: email,
|
|
347
|
+
keyPath: options.keyPath,
|
|
348
|
+
spreadsheetId: spreadsheet.spreadsheetId,
|
|
349
|
+
spreadsheetUrl: spreadsheet.spreadsheetUrl,
|
|
350
|
+
spreadsheetTitle: title,
|
|
351
|
+
outputPath: options.outputPath,
|
|
352
|
+
envFileCreated: envResult.created,
|
|
353
|
+
envFileModified: envResult.modified,
|
|
354
|
+
projectReused,
|
|
355
|
+
serviceAccountReused,
|
|
356
|
+
keyReused,
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
/** The two .env keys the setup CLI manages. */
|
|
361
|
+
export const SETUP_ENV_KEYS = {
|
|
362
|
+
CREDENTIALS: "GOOGLE_APPLICATION_CREDENTIALS",
|
|
363
|
+
SPREADSHEET_URL: "HIKOUTEI_SYNC_SPREADSHEET_URL",
|
|
364
|
+
};
|
|
365
|
+
function isManagedEnvLine(line) {
|
|
366
|
+
return (line.startsWith(`${SETUP_ENV_KEYS.CREDENTIALS}=`) ||
|
|
367
|
+
line.startsWith(`${SETUP_ENV_KEYS.SPREADSHEET_URL}=`));
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Writes or updates the .env output file.
|
|
371
|
+
*
|
|
372
|
+
* Reads the existing file (if any), keeps every unrelated line untouched,
|
|
373
|
+
* replaces any existing managed key lines, appends the two managed keys, and
|
|
374
|
+
* writes back only when the content changed. A missing file is created.
|
|
375
|
+
* Throws on filesystem failure; the caller maps the throw to
|
|
376
|
+
* `output_write_failed`.
|
|
377
|
+
*/
|
|
378
|
+
export function writeSetupEnvFile(outputPath, credentialsPath, spreadsheetUrl) {
|
|
379
|
+
let existing = "";
|
|
380
|
+
let created = false;
|
|
381
|
+
try {
|
|
382
|
+
existing = readFileSync(outputPath, "utf8");
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
386
|
+
created = true;
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
throw error;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const rawLines = existing === "" ? [] : existing.split("\n");
|
|
393
|
+
// A trailing empty element comes from the final newline; drop it so
|
|
394
|
+
// unrelated content is preserved without a spurious blank line.
|
|
395
|
+
const lines = rawLines.length > 0 && rawLines[rawLines.length - 1] === "" ? rawLines.slice(0, -1) : rawLines;
|
|
396
|
+
const next = [
|
|
397
|
+
...lines.filter((line) => !isManagedEnvLine(line)),
|
|
398
|
+
`${SETUP_ENV_KEYS.CREDENTIALS}=${credentialsPath}`,
|
|
399
|
+
`${SETUP_ENV_KEYS.SPREADSHEET_URL}=${spreadsheetUrl}`,
|
|
400
|
+
];
|
|
401
|
+
const content = `${next.join("\n")}\n`;
|
|
402
|
+
const modified = created || content !== existing;
|
|
403
|
+
if (modified) {
|
|
404
|
+
writeFileSync(outputPath, content, "utf8");
|
|
405
|
+
}
|
|
406
|
+
return { created, modified };
|
|
407
|
+
}
|
|
408
|
+
function errorResult(code, message) {
|
|
409
|
+
return { status: "error", code, message };
|
|
410
|
+
}
|
|
411
|
+
function outcomeOf(result, okOutcome) {
|
|
412
|
+
if (result.status === "ok") {
|
|
413
|
+
return okOutcome;
|
|
414
|
+
}
|
|
415
|
+
return `failed: ${describeGcloudFailure(result)}`;
|
|
416
|
+
}
|
|
417
|
+
function describeGcloudFailure(result) {
|
|
418
|
+
if (result.status === "not_found") {
|
|
419
|
+
return "gcloud CLI not found on PATH";
|
|
420
|
+
}
|
|
421
|
+
if (result.status === "failed") {
|
|
422
|
+
return (firstNonEmptyLine(result.stderr) ??
|
|
423
|
+
firstNonEmptyLine(result.stdout) ??
|
|
424
|
+
`gcloud exited with status ${result.code === null ? "unknown" : String(result.code)}`);
|
|
425
|
+
}
|
|
426
|
+
return "unexpected gcloud outcome";
|
|
427
|
+
}
|
|
428
|
+
function firstNonEmptyLine(text) {
|
|
429
|
+
for (const line of text.split("\n")) {
|
|
430
|
+
const trimmed = line.trim();
|
|
431
|
+
if (trimmed !== "") {
|
|
432
|
+
return trimmed;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return undefined;
|
|
436
|
+
}
|
|
437
|
+
function messageOf(error) {
|
|
438
|
+
return error instanceof Error ? error.message : String(error);
|
|
439
|
+
}
|
|
440
|
+
function isNodeError(error) {
|
|
441
|
+
return error instanceof Error && "code" in error;
|
|
442
|
+
}
|
|
443
|
+
//# sourceMappingURL=setupFlow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setupFlow.js","sourceRoot":"","sources":["../../src/cli/setupFlow.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spreadsheet creation for `hikoutei setup`.
|
|
3
|
+
*
|
|
4
|
+
* The setup CLI creates the sync spreadsheet with the freshly provisioned
|
|
5
|
+
* service-account key, so the service account owns the spreadsheet and no
|
|
6
|
+
* sharing step is needed. The production implementation uses
|
|
7
|
+
* google-auth-library plus @googleapis/sheets (already root dependencies);
|
|
8
|
+
* tests inject a fake creator, so unit tests never touch the network.
|
|
9
|
+
*
|
|
10
|
+
* The API response is treated as untrusted input: it is validated by a
|
|
11
|
+
* runtime guard before it is promoted into the internal result shape.
|
|
12
|
+
*/
|
|
13
|
+
/** OAuth scope required to create and write spreadsheets. */
|
|
14
|
+
export declare const SPREADSHEETS_SCOPE = "https://www.googleapis.com/auth/spreadsheets";
|
|
15
|
+
/** Template for the human-facing spreadsheet edit URL. */
|
|
16
|
+
export declare const SPREADSHEET_EDIT_URL_TEMPLATE = "https://docs.google.com/spreadsheets/d/<id>/edit";
|
|
17
|
+
export interface SpreadsheetCreateRequest {
|
|
18
|
+
/** Absolute path to the service-account key JSON file. */
|
|
19
|
+
readonly keyPath: string;
|
|
20
|
+
/** Spreadsheet title to create. */
|
|
21
|
+
readonly title: string;
|
|
22
|
+
}
|
|
23
|
+
export interface SpreadsheetCreateResult {
|
|
24
|
+
readonly spreadsheetId: string;
|
|
25
|
+
readonly spreadsheetUrl: string;
|
|
26
|
+
}
|
|
27
|
+
/** Creates a spreadsheet as the service account identified by `keyPath`. */
|
|
28
|
+
export type SpreadsheetCreator = (request: SpreadsheetCreateRequest) => Promise<SpreadsheetCreateResult>;
|
|
29
|
+
/**
|
|
30
|
+
* Production spreadsheet creator.
|
|
31
|
+
*
|
|
32
|
+
* Authenticates with the given service-account key file and calls
|
|
33
|
+
* `spreadsheets.create`. Throws on failure; the setup flow maps any throw to
|
|
34
|
+
* the `sheet_create_failed` error code. The key file path is never included
|
|
35
|
+
* in error output.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createGoogleSheetsSpreadsheetCreator(): SpreadsheetCreator;
|
|
38
|
+
/** Builds the public edit URL for a spreadsheet id. */
|
|
39
|
+
export declare function spreadsheetEditUrl(spreadsheetId: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Validates the raw `spreadsheets.create` payload and promotes it into the
|
|
42
|
+
* internal result shape.
|
|
43
|
+
*
|
|
44
|
+
* The response is untrusted SDK data, so `spreadsheetId` must be proven to be
|
|
45
|
+
* a non-empty string. When the API omits `spreadsheetUrl` (it can), the URL
|
|
46
|
+
* is derived from the id deterministically.
|
|
47
|
+
*/
|
|
48
|
+
export declare function extractSpreadsheetCreateResult(data: unknown): SpreadsheetCreateResult;
|
|
49
|
+
//# sourceMappingURL=sheetsFactory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sheetsFactory.d.ts","sourceRoot":"","sources":["../../src/cli/sheetsFactory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,6DAA6D;AAC7D,eAAO,MAAM,kBAAkB,iDAAiD,CAAC;AAEjF,0DAA0D;AAC1D,eAAO,MAAM,6BAA6B,qDAAqD,CAAC;AAEhG,MAAM,WAAW,wBAAwB;IACvC,0DAA0D;IAC1D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,mCAAmC;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,4EAA4E;AAC5E,MAAM,MAAM,kBAAkB,GAAG,CAC/B,OAAO,EAAE,wBAAwB,KAC9B,OAAO,CAAC,uBAAuB,CAAC,CAAC;AAKtC;;;;;;;GAOG;AACH,wBAAgB,oCAAoC,IAAI,kBAAkB,CAezE;AAED,uDAAuD;AACvD,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAEhE;AAMD;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,OAAO,GAAG,uBAAuB,CAYrF"}
|